Bash while loop read from colon-delimited list of paths using IFS

bashshellstring

I am trying to write a bash function that behaves similarly to the where builtin in tcsh. In tcsh, where lists all the builtins, aliases, and the absolute paths to executables on the PATH with a given name, even if they are shadowed, e.g.

tcsh> where tcsh
/usr/bin/tcsh
/bin/tcsh

As part of this I want to loop over everything in the $PATH and see if an executable file with the appropriate name exists.

The following bash snippet is intended to loop over a colon-delimited list of paths and print each component followed by a newline, however, it just seems to print the entire contents of $PATH all on one line

#!/bin/bash
while IFS=':' read -r line; do
    printf "%s\n" "$line"
done <<< "$PATH"

As is stands now, bash where and ./where just print /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

So, how do I set up my while loop so that the value of the loop variable is each segment of the colon-separated list of paths in turn?

Best Answer

read uses IFS to separate the words in the line it reads, it doesn't tell read to read until the first occurrence of any of the characters in it.

IFS=: read -r a b

Would read one line, put the part before the first : in $a, and the rest in $b.

IFS=: read -r a

would put the whole line (the rest) in $a (except if that line contains only one : and it's the last character on the line).

If you wanted to read until the first :, you'd use read -d: instead (ksh93, zsh or bash only).

printf %s "$PATH" | while IFS= read -rd: dir || [ -n "$dir" ]; do
  ...
done

(we're not using <<< as that adds an extra newline character).

Or you could use standard word splitting:

IFS=:; set -o noglob
for dir in $PATH""; do
  ...
done

Now beware of few caveats:

  • An empty $PATH component means the current directory.
  • An empty $PATH means the current directory (that is, $PATH contains one component which is the current directory, so the while read -d: loop would be wrong in that case).
  • //file is not necessary the same as /file on some system, so if $PATH contains /, you need to be careful with things like $dir/$file.
  • An unset $PATH means a default search path is to be used, it's not the same as a set but empty $PATH.

Now, if it's only the equivalent of tcsh/zsh's where command, you could use bash's type -a.

More reading:

Related Question