Reliably Finding Files in $PATH

Christian Emmer
Christian Emmer
Aug 27, 2021 · 4 min read
Reliably Finding Files in $PATH

Most built-in commands commonly used to find files in $PATH don't always work quite as expected, or are shell-specific.

Jump to the bottom of the article for a function definition that looks for files in $PATH and is shell-agnostic, or keep reading for a full explanation of why some built-in commands don't work as desired.

The use case

I tend to not leave Docker running on my MacBook, it tends to eat battery and slow down every other process including web browsing. Because of that, I'm also tired of seeing:

$ docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

I wanted to create a function in my dotfiles to override the docker command, and that function would ensure Docker Desktop is running before executing the docker command. But I had an issue with finding the actual location of the docker executable once it was obscured by the function.

This article catalogs my findings while trying to solve that use case.

The problem with which

The problem with which is it's affected by aliases and functions, and will prefer those over searching in $PATH.

If an executable has been overridden with an alias, which will print the alias:

$ which grep
/usr/bin/grep

$ alias grep='grep --color=auto'

$ which grep
grep: aliased to grep --color=auto

If an executable has been overridden with a function, which will print the function:

$ which sed
/usr/bin/sed

$ sed() { echo "sed?" }

$ which sed
sed () {
    echo "sed?"
}

which has similar behavior for aliases and functions that don't override an executable:

$ which foo
foo not found

$ foo() { echo "bar" }

$ which foo
foo () {
    echo "bar"
}

The problem with type and whence

The problem with type -P and whence -p is they're shell-specific, and I'd prefer a shell-agnostic command or function.

type -P doesn't exist in Zsh:

$ echo $0
/bin/zsh

$ which cat
/bin/cat

$ type -P cat
type: bad option: -P

And whence -p doesn't exist in Bash:

$ echo $0
/bin/bash

$ which cat
/bin/cat

$ whence -p cat
bash: whence: command not found

The solution

The solution is fairly straightforward - if we want to find files in $PATH, then let's write a function to look in $PATH and only $PATH. Here's a shell-agnostic function dubbed pinpoint that's easy to add to dotfiles:

pinpoint() {
    while read -r DIR; do
        if [[ -f "${DIR}/$1" ]]; then
            echo "${DIR}/$1"
            return 0
        fi
    done <<< "$(echo "${PATH}" | tr ':' '\n')"
    return 1
}

And here's some example usage of the function:

$ pinpoint grep
/usr/bin/grep

$ alias grep='grep --color=auto'

$ pinpoint grep
/usr/bin/grep
$ pinpoint sed
/usr/bin/sed

$ sed() { echo "sed?" }

$ pinpoint sed
/usr/bin/sed
$ pinpoint foo || echo "not in path"
not in path

$ foo() { echo "bar" }

$ pinpoint foo || echo "not in path"
not in path

Happy searching!