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

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:

  • What's a safe and portable way to split a string in shell programming?
  • Understanding "IFS= read -r line"
  • Why not use "which"? What to use then?

Don't use shell loops to process text.

Instead, use awk, or tr, or even sed.

printf %s\\n "$PATH" | tr ':' '\n'

printf %s "$PATH" | awk 'BEGIN {RS=":"}; 1'

Or, since this is a shell variable you are processing, just use bash pattern substitution:

echo "${PATH//:/
}"

(See LESS=+/parameter/pattern man bash.)

Tags:

String

Shell

Bash