6

If I have a colon-separated path list, much like $PATH, but not neccessarily $PATH.

I want to search that list for a specific file name. However, I only want the first matching path.

I have considered the following linux commands:

  • which: only works for binary, and only works with $PATH variable
  • whereis: works with particular kinds of files, and only works with $PATH variable
  • find: does not support colon-separated path lists, and returns multiple results

Here are some things I have tried:

  1. I have tried to use whereis by the following strategy

    env WHEREIS="`which whereis`" PATH="$MY_PATH_LIST" $WHEREIS "$TARGET_FILE"
    

    and this almost works. However, it does not seem to return results for arbitrary file types. It also returns multiple results, and in an awkward format.

  2. I could get which to work by

    env WHICH="`which which`" PATH="$MY_PATH_LIST" $WHICH "$MY_TARGET_FILE"
    

    if there was a command-line option to force it to allow non-executables.

  3. I then tried to solve the problem with find. First I used regular expressions to expand the path list (I replace colons with spaces). Then I invoke find, and it works correctly. However, it searches all of the paths. I cannot seem to find a way to tell it to stop the search early if it finds a good result.

    I did get this to work

     find ${MY_PATH_LIST//[:]/ } -name "$MY_TARGET_FILE" | head -n 1
    

    but it takes a long time to complete, because find is still doing an exhaustive search.

    I need this to execute faster (exit on first result), because it would be run many times, with different parameters.

Does anyone have a better solution to suggest?

Note that if all fails, I can write a non-bash solution. Write now I'm hoping for a simple solution using existing tools.

4 Answers 4

4

You could just use a script that simply tests (-e) for existence of a file and stops when the first one has been found:

#!/bin/bash
[[ $# -gt 0 ]] || { echo "Usage: $0 <filename> [pathspec]" >&2 ; exit 1 ; }
if [[ $# -gt 1 ]] ; then
        P="$2"
else
        P="$PATH"
fi

IFS=:
for DIR in $P ; do
        if [[ -e "$DIR/$1" ]] ; then
                echo "$DIR/$1"
                exit 0
        fi
done

Example:

$ ./search.sh 
Usage: ./search.sh <filename> [pathspec]

$ ./search.sh ls
/Users/danielbeck/bin/ls

$ ./search.sh pwd
/bin/pwd

$ ./search.sh ls /bin
/bin/ls

$ ./search.sh ls /usr/bin:/bin
/bin/ls
1
  • Thanks, Daniel. This works. I am surprised that there isn't a built-in command for this purpose. I'm going to wait a bit to see if there are any interesting comments. So far, it looks like this one will be going to you. :-) Commented Dec 20, 2012 at 11:35
0

Use head to get the first result of the find command.

find `echo colon:separated:directories | sed 's/:/ /g'` -name filename | head -n1

Or in a function:

$ function findin { find `echo $1 | sed 's/:/ /g'` -name $2 | head -n1; }
$ findin this:that:other filename
0

You can use the which command, and set the PATH variable just for the scope of that command. For example

PATH="/path1:/path2" which "file1.txt"

will return /path1/file1.txt if it exists, otherwise /path2/file1.txt if it exists.

1
  • 1
    Actually no it only works for executable files, as you said. Commented Jun 16, 2021 at 13:00
0

Here's how to fix your attempt at using find to avoid descending into subdirectories and stop when one file is found. The trick is to use -maxdepth and -quit to deal with the problems you were having. If you in addition want to ignore subdirectories that match the name being searched, you can insert -type f before the -print.

find ${MY_PATH_LIST//[:]/ } -maxdepth 1 -name "$MY_TARGET_FILE" -print -quit

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .