Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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 am 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?

share|improve this question
6  
Why not use type -a? – Stéphane Chazelas yesterday
    
@StéphaneChazelas because I didn't know it existed. :/ – Gregory Nisbet yesterday
2  
See also Debian's which script for how to loop over $PATH properly. – Stéphane Chazelas yesterday
    
@StéphaneChazelas I see so if you use IFS=':' ; for x in $PATH ; do echo "$x" ; done, then the loop variable will be set to each element of $PATH in turn. Why do the for and while loop treat IFS differently? – Gregory Nisbet yesterday
2  
@GregoryNisbet it's not while that's doing anything with IFS in this case, it's read, which reads a whole line at once, regardless of IFS—as Stephane explained, actually. – Wildcard yesterday
up vote 5 down vote accepted

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.

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

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

Or you could use standard word splitting:

case $PATH in
  (*:|"") p=${PATH}:;;
  (*) p=$PATH;;
esac
IFS=:; set -f
for dir in $p; 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:

share|improve this answer

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.)

share|improve this answer
1  
Why the downvote? I see three other answers have been given downvotes just now with no comments...why? – Wildcard 15 hours ago

Shell utils:

echo $PATH | tr ':' '\n'
share|improve this answer

Pure bash, it's the same thing as Wildcard's 2nd method earlier, but this is on one line:

echo -e "${PATH//:/"\n"}"
share|improve this answer

This script works similar to the command where

#!/bin/bash
fn="foo"
for i in `echo $PATH|tr ':' '\n'`
do
  if [[ -e "$i/$fn" ]] ; then
    echo $i
  fi
done

$fn is something you want to find.

$i/$fn may be $i$fn sometimes.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.