tim tran
tim tran

Reputation: 755

Looping through the elements of a path variable in Bash

I want to loop through a path list that I have gotten from an echo $VARIABLE command.

For example:

echo $MANPATH will return

/usr/lib:/usr/sfw/lib:/usr/info

So that is three different paths, each separated by a colon. I want to loop though each of those paths. Is there a way to do that? Thanks.

Thanks for all the replies so far, it looks like I actually don't need a loop after all. I just need a way to take out the colon so I can run one ls command on those three paths.

Upvotes: 43

Views: 25817

Answers (10)

Adam Smooch
Adam Smooch

Reputation: 1322

OP's update wants to ls the resulting folders, and has pointed out that ls only requires a space-separated list.

ls $(echo $PATH | tr ':' ' ') is nice and simple and should fit the bill nicely.

Upvotes: 0

Walker Hale IV
Walker Hale IV

Reputation: 3328

Combining ideas from:

code:

PATHVAR='foo:bar baz:spam:eggs:'  # demo path with space and empty
printf '%s:\0' "$PATHVAR" | while IFS=: read -d: -r p; do
    echo $p
done | cat -n

output:

1   foo
2   bar baz
3   spam
4   eggs
5   

Upvotes: 1

Alexander
Alexander

Reputation: 10376

This can also be solved with Python, on the command line:

python -c "import os,sys;[os.system(' '.join(sys.argv[1:]).format(p)) for p in os.getenv('PATH').split(':')]" echo {}

Or as an alias:

alias foreachpath="python -c \"import os,sys;[os.system(' '.join(sys.argv[1:]).format(p)) for p in os.getenv('PATH').split(':')]\""

With example usage:

foreachpath echo {}

The advantage to this approach is that {} will be replaced by each path in succession. This can be used to construct all sorts of commands, for instance to list the size of all files and directories in the directories in $PATH. including directories with spaces in the name:

foreachpath 'for e in "{}"/*; do du -h "$e"; done'

Here is an example that shortens the length of the $PATH variable by creating symlinks to every file and directory in the $PATH in $HOME/.allbin. This is not useful for everyday usage, but may be useful if you get the too many arguments error message in a docker container, because bitbake uses the full $PATH as part of the command line...

mkdir -p "$HOME/.allbin"
python -c "import os,sys;[os.system(' '.join(sys.argv[1:]).format(p)) for p in os.getenv('PATH').split(':')]" 'for e in "{}"/*; do ln -sf "$e" "$HOME/.allbin/$(basename $e)"; done'
export PATH="$HOME/.allbin"

This should also, in theory, speed up regular shell usage and shell scripts, since there are fewer paths to search for every command that is executed. It is pretty hacky, though, so I don't recommend that anyone shorten their $PATH this way.

The foreachpath alias might come in handy, though.

Upvotes: 1

Acid
Acid

Reputation: 33

IFS=:
arr=(${MANPATH})
for path in "${arr[@]}" ; do # <- quotes required
   echo $path
done

... it does take care of spaces :o) but also adds empty elements if you have something like:

:/usr/bin::/usr/lib:

... then index 0,2 will be empty (''), cannot say why index 4 isnt set at all

Upvotes: 1

Yi H.
Yi H.

Reputation: 309

In this way you can safely go through the $PATH with a single loop, while $IFS will remain the same inside or outside the loop.

while IFS=: read -d: -r path; do # `$IFS` is only set for the `read` command
    echo $path
done <<< "${PATH:+"${PATH}:"}"   # append an extra ':' if `$PATH` is set

You can check the value of $IFS,

IFS='xxxxxxxx'
while IFS=: read -d: -r path; do
    echo "${IFS}${path}"
done <<< "${PATH:+"${PATH}:"}"

and the output will be something like this.

xxxxxxxx/usr/local/bin
xxxxxxxx/usr/bin
xxxxxxxx/bin

Reference to another question on StackExchange.

Upvotes: 8

gniourf_gniourf
gniourf_gniourf

Reputation: 46823

The canonical way to do this, in Bash, is to use the read builtin appropriately:

IFS=: read -r -d '' -a path_array < <(printf '%s:\0' "$MANPATH")

This is the only robust solution: will do exactly what you want: split the string on the delimiter : and be safe with respect to spaces, newlines, and glob characters like *, [ ], etc. (unlike the other answers: they are all broken).

After this command, you'll have an array path_array, and you can loop on it:

for p in "${path_array[@]}"; do
    printf '%s\n' "$p"
done

Upvotes: 28

choroba
choroba

Reputation: 241828

You can set the Internal Field Separator:

( IFS=:
  for p in $MANPATH; do
      echo "$p"
  done
)

I used a subshell so the change in IFS is not reflected in my current shell.

Upvotes: 67

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84343

You can use Bash's pattern substitution parameter expansion to populate your loop variable. For example:

MANPATH=/usr/lib:/usr/sfw/lib:/usr/info

# Replace colons with spaces to create list.
for path in ${MANPATH//:/ }; do
    echo "$path"
done

Note: Don't enclose the substitution expansion in quotes. You want the expanded values from MANPATH to be interpreted by the for-loop as separate words, rather than as a single string.

Upvotes: 18

rayd09
rayd09

Reputation: 1897

for p in $(echo $MANPATH | tr ":" " ") ;do
    echo $p
done

Upvotes: 1

Hunter McMillen
Hunter McMillen

Reputation: 61512

You can use Bash's for X in ${} notation to accomplish this:

for p in ${PATH//:/$'\n'} ; do
    echo $p;
done

Upvotes: 0

Related Questions