I'll add a solution using the null byte (\0
) as a delimiter. This strategy is often used when there are "any weird characters like space and the like." @umläute, you did mention -print0
in a comment (archived), so I'm guessing there has been some thought about null bytes. However, we're talking about input to find
, so -print0
won't help us. Also, directly using IFS='\0'
(or even IFS=$'\0'
) is problematic in bash
due to its internal handling of C-style strings. This source (archived) gives a very good explanation of the problems.
A quick disclaimer: Now that I've seen it, I would use the ( IFS=:; find -H $PATH -name "gcc-*" )
solution as given in the answer (archived) by @Stéphane-Chazelas, along with the comment (archived) by @terdon-♦. My first instinct in situations like this, however, is to null-separate things. Because of this, I've posted my take on that approach.
Edit: I've learned a lot by answering this question and receiving great suggestions about how to fix problems and make things better. I've left some of my "learning" here in Notes after the answer, but I'm taking out some extraneous stuff. Hopefully everything remaining will be useful.
An example
Here's one example that shows the concept. I give the input/output from my system1. Note that an extended solution that takes care of some often-unwanted error reports is at the end of this section (right before the <hr/>
horizontal line).
Edit: As @Raffa was so kind as to point out in the comments, there are a couple problems with my first version. I discuss these in Note 3.
$ printf $PATH | tr ':' '\0' |
xargs -I'{}' -0 find "{}" -maxdepth 1 -type f -name "gcc-*"'
Edited. Credit goes to @Stéphane-Chazelas for warning about using -maxdepth 1
without -mindepth 1
as well as for giving better usage of printf
.
$ printf %s "$PATH" | tr ':' '\0' |
xargs -0 sh -c 'find "$@" -mindepth 1 -maxdepth 1 -type f -name "gcc-*"' sh
/usr/bin/gcc-ar.exe
/usr/bin/gcc-nm.exe
/usr/bin/gcc-ranlib.exe
The command only gives output as it does (without warnings or errors), because my PATH
doesn't contain any non-existent directories. This is a good time to highlight that I've used printf
instead of echo
to avoid errors such as
find: ‘/usr/local/bin\n’: No such file or directory
This is a legitimate report, as there is a very real possibility that a gcc-*
file could be in /usr/local/bin
. However, there can be multiple other (likely unwanted) No such
errors, as noted in the solution of @Chris-Davies (archived),
[Y]ou have to discard stderr completely in order to avoid outputting errors for directories in $PATH
that don't exist.
There's also a real possibility of getting Permission denied
errors. If you want to get all output from stderr
other than the No such
and Permission denied
errors, you can do some process substitution along with using file descriptors. I've used this pattern for a while, and it's due to an answer (archived) to a find
question, the answer given by @mklement0.
Edit: The same issues that @Raffa so kindly pointed out (see Note 3) are taken care of with the unwanted-error-filtering code, below.
printf %s "$PATH" | tr ':' '\0' |
xargs -0 sh -c '
find "$@" -mindepth 1 -maxdepth 1 -type f -name "gcc-*" 2>
>( grep -v "Permission denied\|No such" >&2)
' sh
The use of sh -c
after xargs
allows me to use the in-my-opinion nicer structure from a comment by @gniourf-gniourf (archived).
printf %s "$PATH" | tr ':' '\0' |
xargs -0 sh -c '
{ find "$@" -mindepth 1 -maxdepth 1 -type f \
-name "gcc-*" 2>&1 >&3 |
grep -v "Permission denied\|No such" >&2; } 3>&1' sh
I like that plumbing approach better, as I think it more cleanly takes care of streams and allows a simple pipe to grep
, but it is a bit more opaque—harder to read and to understand. In the same page as the code from @mklement0 and @gniourf-gniourf, you can see discussion of a similar pattern mentioned in an answer from @wjordan.
Edited. Another shout-out to @Stéphane-Chazelas for sorting out some of my plumbing problems.
Some thoughts about weird characters
I got thinking of how robust this is. There is this annoying little thing that my bash
1 just let me do from my $HOME
directory (without any complaint).
$ cd # make sure I'm $HOME
$ mkdir -p "abc:def"
$ cd abc\:def
$ cat > test_executable <<'EOF'
#!/usr/bin/env bash
echo "A colon in the file path?"
EOF
$ chmod a+x test_executable
$ ./test_executable
A colon in the file path?
There are problems with this idea of a colon in the path to an executable, specifically with using said path in the PATH
environment variable as in PATH="/home/bballdave025/abc\:def:$PATH"
. In fact, there are enough problems that I've decided to take this part of my solution out, since the problem isn't really ... real. However, I leave a reference to Note 3, in case someone has no control over a colon being in the path of an executable.
The not-real-ness of the problem has to do with the way PATH
is implemented and documented. Some explanations come from this answer (archived) on U&L as well as this other answer (archived) on SO, respectively, that
The POSIX standard explicitly mentions that it's impossible to use directories with :
in their names in the PATH
variable's value.
This [escaping a colon in PATH on UNIX] is impossible according to the POSIX standard. This is not a function of a specific shell, PATH handling is done within the execvp
function in the C library. There is no provision for any kind of quoting.
Things are more clear and concise in a comment (archived) on this (my current) answer by
@Stéphane-Chazelas,
PATH="/home/bballdave025/abc\:def"
means a $PATH
with two directories: /home/bballdave025/abc\
and def
, there's not point trying to treat that \:
specially.
In the interest of history, I do have the previously-existing section as a gist (archived) on my GitHub.
Notes:
[1]
My system:
$ uname -a
CYGWIN_NT-10.0-19045 MY-MACHINE 3.6.3-1.x86_64 2025-06-05 11:45 UTC x86_64 Cygwin
$ bash --version | head -n 1
GNU bash, version 5.2.21(1)-release (x86_64-pc-cygwin)
[2]
If you need to work around this, you could soft-link the directory to a non-colon-containing name, e.g.
cd
ln -s abc\:def abc_def
export PATH="/home/bballdave025/abc_def:$PATH"
cf. this SO post (archived).
Note that I've also seen solutions where one mounts the colon-containing directory with a non-colon-containing name, but unless said directory is on another file system, I don't like doing this.
[3]
I always appreciate comments that help me learn more, and I'm indebted to @Raffa for the comments that allowed me to give an answer that works with desired characteristics and that works better. There were two issues pointed out, but before I go into them, I'll put the old code here for inspection/comparison.
The original versions of the code were as follows:
Initial
$ printf $PATH | tr ':' '\0' |
xargs -I'{}' -0 find "{}" -maxdepth 1 -type f -name "gcc-*"
With suppression of unwanted errors/warnings
$ printf $PATH | tr ':' '\0' |
xargs -I'{}' -0 find "{}" -maxdepth 1 -type f -name "gcc-*" 2> \
>(grep -v 'Permission denied\|No such' >&2)
The first issue is a loss in efficiency due to the -I'{}'
passed to xargs
. From man xargs | cat | grep -A 4
and man xargs | cat | grep -E -A3 "^\s+[-]L"
, respectively,
-I replace‐str
Replace occurrences of replace‐str in the initial‐arguments with
names read from standard input. Also, unquoted blanks do not
terminate input items; instead the separator is the newline char‐
acter. Implies -x and -L 1.
-L max‐lines
Use at most max‐lines nonblank input lines per command line.
Trailing blanks cause an input line to be logically continued on
the next input line. Implies -x.
(-x
just specifies some exit conditions). Using at most 1
input line per command line has the possibility to cause significant slowdown.
The second issue is that not having $PATH
in double quotes leads to an expansion that will break paths containing spaces and other "weird characters". Since this is one of the initial goals of the OP, the fix is very important. The new command has passed tested with a PATH
variable containing a space.
glob
in this case. While the answers to the potential duplicate discussglob
in the answers, that other question doesn't mention globbing as part of the main goal. @umläute has made it an integral part of this question.sh
but since you're almost certainly not actually using the ancient bourne shell, it would be good if you could specify what/bin/sh
is pointing to on your system.