Reliably Detecting Command Existence in Bash

Christian Emmer
Christian Emmer
Dec 7, 2024 · 4 min read
Reliably Detecting Command Existence in Bash

You'll find quite a few different methods suggested on the internet, and they all have their own problems.

Before I go into the details, this is the syntax you want:

if command -v <command_name> &> /dev/null; then
    echo "command exists"
fi

or alternatively:

command -v <command_name> &> /dev/null && echo "command exists"

Explanation

Here's the man page for command -v command_name:

-v       Write a string to standard output that indicates the
         pathname or command that will be used by the shell, in
         the current shell execution environment (see Section
         2.12, Shell Execution Environment), to invoke
         command_name, but do not invoke command_name.

          *  Utilities, regular built-in utilities,
             command_names including a <slash> character, and
             any implementation-defined functions that are found
             using the PATH variable (as described in Section
             2.9.1.1, Command Search and Execution), shall be
             written as absolute pathnames.

          *  Shell functions, special built-in utilities,
             regular built-in utilities not associated with a
             PATH search, and shell reserved words shall be
             written as just their names.

          *  An alias shall be written as a command line that
             represents its alias definition.

          *  Otherwise, no output shall be written and the exit
             status shall reflect that the name was not found.

The main takeaway here is this method will work for executables in $PATH, functions, and aliases. Other methodologies only work for a subset of those commands.

The command command is also POSIX-compliant, meaning it should work consistently across different UNIX variants.

The problem with which

One method you'll see suggested in help forums is which <program>. This method works for both functions and aliases, but it doesn't define a consistent exit code behavior. which will exit with a non-zero status code on most distros, but this isn't a guarantee.

which foo &> /dev/null && echo "'foo' maybe exists?"

The problem with if [[ -x file ]]

One of the more common methods I've seen suggested is:

if [[ -x "$(command -v <command_name>)" ]]; then
    echo "executable exists"
fi

This will test if the output of the command -v <command_name> is an executable file or not, which won't work for aliases. If you want to check for the existence of executables, there are better options such as find <dir> -type f -executable or the pinpoint function from "Reliably Finding Executables in $PATH".

Reliably Finding Executables in $PATH
Reliably Finding Executables in $PATH
Aug 27, 2021 · 4 min read

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

if [[ -x file ]] doesn't work for aliases, which may be used to intentionally shadow executables:

$ if [[ -x "$(command -v grep)" ]]; then echo "grep exists"; fi
grep exists

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

$ if [[ -x "$(command -v grep)" ]]; then echo "grep exists"; fi

but it does work for functions, which may cause confusion:

$ if [[ -x "$(command -v docker)" ]]; then echo "docker exists"; fi
docker exists

$ docker() { echo "do some prework"; command docker "$@" }

$ if [[ -x "$(command -v docker)" ]]; then echo "docker exists"; fi
docker exists

See "Automatically Execute Code Before & After Unix Commands" for more tricks on using functions to shadow executables.

Automatically Execute Code Before & After Unix Commands
Automatically Execute Code Before & After Unix Commands
Jan 19, 2023 · 4 min read

It can be helpful to run some code automatically before or after calling a command, and it is easy to accomplish with shadowing functions.

Example usages

To check for the nonexistence of a command you can use ! command -v <command_name> syntax:

if ! command -v beep &> /dev/null; then
    alias beep="echo -ne '\007'"
fi

For a real world example, I have my macOS dotfiles install any missing tools I use frequently using Homebrew :

if command -v brew &> /dev/null; then
    command -v gawk > /dev/null || brew install gawk
    command -v gsed > /dev/null || brew install gnu-sed
    command -v jq   > /dev/null || brew install jq
    command -v tree > /dev/null || brew install tree
    command -v wget > /dev/null || brew install wget
fi

I also alias pip3 to pip when pip doesn't exist because the version suffix on Python commands is wildly inconsistent across distros and package managers:

if ! command -v pip &> /dev/null && command -v pip3 &> /dev/null; then
    alias pip=pip3
fi

You can also mix command -v <command_name> conditionals with other Bash conditionals like this :

if ! command -v brew &> /dev/null && [[ -f /opt/homebrew/bin/brew ]]; then
    eval "$(/opt/homebrew/bin/brew shellenv)"
fi