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.
which
The problem with 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?"
if [[ -x file ]]
The problem with 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
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
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