How to find a file on the $PATH in Bash?
Asked Answered
V

5

6

The utility which will find a program on the path but what about an arbitrary data file?

I must be searching for the wrong stuff but I can't find any way to do this with a standard utility so I thought of writing a little Bash script to do it.

The $PATH environment variable is separated by colons, but trying to set IFS=':' and then iterate over the results is not working for me. Here's what I've got so far:

IFS=':' DIRS=($PATH)
for d in $DIRS; do echo $d; done

At this point it only outputs the first entry in my path rather than all of them.

Thoughts? If there's already a standard command that does this then there's no reason to write a script ...

Venosity answered 27/3, 2019 at 16:49 Comment(1)
Arbitrary data files probably shouldn't be in directories lists in PATH.Mucus
G
2

A few of things going on here:

  1. IFS=':' DIRS=($PATH)

    bash will expand the variable before setting IFS and DIRS, so it's too late by then. You'll need something crazy like

    dirs=()
    while IFS= read -r -d: dir || [ "$dir" ]; do dirs+=("$dir"); done <<<"$PATH"
    

-- nope, that was wrong. First the $PATH variable is expanded, then IFS is set, then the DIRS element is split using IFS because it is unquoted.

  1. for d in $DIRS; do echo $d; done

    To iterate over all the elements of an array, you need

    for d in "${dirs[@]}" ...
    

    With an array, $DIRS is equal to ${DIRS[0]}, i.e. the first entry.

  2. Don't use ALLCAPS varnames. It's too easy to overwrite a crucial system variable.

  3. Quote your variables unless you know exactly what will happen if you don't.

Gilus answered 27/3, 2019 at 16:49 Comment(0)
G
12

Using this, using bash parameter expansions and find:

find ${PATH//:/\/ } -name 'file'
Gossipry answered 27/3, 2019 at 16:58 Comment(6)
Suppose there's a directory in the PATH that has a space in it. Or a file in a directory in the PATH with a space in the filename.Gilus
You're also not expanding the files in the last PATH entry.Gilus
This solution would be great if I could use find in a way that searches the $PATH but replacing colons with spaces isn't going to workVenosity
@GillesQuenot I've upvoted your find solution because I like it but it won't work when there's spaces in directories that are in $PATHVenosity
I never use spaces especially for a PATH directory, and I like my solution too ^^Evelyne
If you do have spaces in your $PATH but have managed to split the entries into an array, simply use find "${array[@]}" -name "$file"Andvari
A
5

The error is not with the assignment, but with not looping over the array.

IFS=: dirs=($PATH)
for dir in "${dirs[@]}"; do
    echo "$dir"
done

works for me; or more succinctly

printf '%s\n' "${dirs[@]}"

Like you discovered, just $dirs only returns the first item from the array.

To actually traverse these directories looking for a particular file, maybe try

desired_file_name=$1  # or whatever
for dir in "${dirs[@]}"; do
    test -e "$dir/$desired_file_name" || continue
    echo "$0: found $dir/$desired_file_name" >&2
done

Another approach is

find "${dirs[@]}" -name "$desired_file_name"

(You should prefer lower case for your private variables, so I changed DIRS to dirs. Less shouting is good for the eyes, too.)

Andvari answered 27/3, 2019 at 17:7 Comment(1)
Huh, I guess I didn't test what I wrote.Gilus
B
3

To find a file named $name on your PATH using a bash loop:

oldIFS=$IFS
IFS=:
for d in $PATH
do
    [ -f "$d/$name" ] && echo "Found $d/$name"
done
IFS=$oldIFS

On many systems, such as debian, which is just a shell script and it uses a very similar loop. Have a look at less /usr/bin/which.

Borkowski answered 27/3, 2019 at 17:4 Comment(1)
Works great for searching any colon-delimited path, not just PATH. Thanks for the tip.Culinary
G
2

A few of things going on here:

  1. IFS=':' DIRS=($PATH)

    bash will expand the variable before setting IFS and DIRS, so it's too late by then. You'll need something crazy like

    dirs=()
    while IFS= read -r -d: dir || [ "$dir" ]; do dirs+=("$dir"); done <<<"$PATH"
    

-- nope, that was wrong. First the $PATH variable is expanded, then IFS is set, then the DIRS element is split using IFS because it is unquoted.

  1. for d in $DIRS; do echo $d; done

    To iterate over all the elements of an array, you need

    for d in "${dirs[@]}" ...
    

    With an array, $DIRS is equal to ${DIRS[0]}, i.e. the first entry.

  2. Don't use ALLCAPS varnames. It's too easy to overwrite a crucial system variable.

  3. Quote your variables unless you know exactly what will happen if you don't.

Gilus answered 27/3, 2019 at 16:49 Comment(0)
H
0

On MacOS the following will work with zsh

echo "$PATH" | tr ':' '\n' | while read -r dir; do
    [ -d "$dir" ] && find "$dir" -type f -name 'file' 2>/dev/null
done
Hendrickson answered 26/9, 2024 at 12:31 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.