[bash] How can I check if a program exists from a Bash script?

How would I validate that a program exists, in a way that will either return an error and exit, or continue with the script?

It seems like it should be easy, but it's been stumping me.

This question is related to bash

The answer is


I have a function defined in my .bashrc that makes this easier.

command_exists () {
    type "$1" &> /dev/null ;
}

Here's an example of how it's used (from my .bash_profile.)

if command_exists mvim ; then
    export VISUAL="mvim --nofork"
fi

Expanding on @lhunath's and @GregV's answers, here's the code for the people who want to easily put that check inside an if statement:

exists()
{
  command -v "$1" >/dev/null 2>&1
}

Here's how to use it:

if exists bash; then
  echo 'Bash exists!'
else
  echo 'Your system does not have Bash'
fi

Late answer but this is what I ended up doing.

I just check if the command I execute returns an error code. If it returns 0 it means program is installed. Moreover you can use this to check the output of a script. Take for instance this script.

foo.sh

#!/bin/bash
echo "hello world"
exit 1 # throw some error code

Examples:

# outputs something bad... and exits
bash foo.sh $? -eq 0 || echo "something bad happened. not installed" ; exit 1

# does NOT outputs nothing nor exits because dotnet is installed on my machine
dotnet --version $? -eq 0 || echo "something bad happened. not installed" ; exit 1

Basically all this is doing is checking the exit code of a command run. the most accepted answer on this question will return true even if the command exit code is not 0.


Use Bash builtins if you can:

which programname

...

type -P programname

hash foo 2>/dev/null: works with Z shell (Zsh), Bash, Dash and ash.

type -p foo: it appears to work with Z shell, Bash and ash (BusyBox), but not Dash (it interprets -p as an argument).

command -v foo: works with Z shell, Bash, Dash, but not ash (BusyBox) (-ash: command: not found).

Also note that builtin is not available with ash and Dash.


If you check for program existence, you are probably going to run it later anyway. Why not try to run it in the first place?

if foo --version >/dev/null 2>&1; then
    echo Found
else
    echo Not found
fi

It's a more trustworthy check that the program runs than merely looking at PATH directories and file permissions.

Plus you can get some useful result from your program, such as its version.

Of course the drawbacks are that some programs can be heavy to start and some don't have a --version option to immediately (and successfully) exit.


It depends on whether you want to know whether it exists in one of the directories in the $PATH variable or whether you know the absolute location of it. If you want to know if it is in the $PATH variable, use

if which programname >/dev/null; then
    echo exists
else
    echo does not exist
fi

otherwise use

if [ -x /path/to/programname ]; then
    echo exists
else
    echo does not exist
fi

The redirection to /dev/null/ in the first example suppresses the output of the which program.


To use hash, as @lhunath suggests, in a Bash script:

hash foo &> /dev/null
if [ $? -eq 1 ]; then
    echo >&2 "foo not found."
fi

This script runs hash and then checks if the exit code of the most recent command, the value stored in $?, is equal to 1. If hash doesn't find foo, the exit code will be 1. If foo is present, the exit code will be 0.

&> /dev/null redirects standard error and standard output from hash so that it doesn't appear onscreen and echo >&2 writes the message to standard error.


If you guys/gals can't get the things in answers here to work and are pulling hair out of your back, try to run the same command using bash -c. Just look at this somnambular delirium. This is what really happening when you run $(sub-command):

First. It can give you completely different output.

$ command -v ls
alias ls='ls --color=auto'
$ bash -c "command -v ls"
/bin/ls

Second. It can give you no output at all.

$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found

To mimic Bash's type -P cmd, we can use the POSIX compliant env -i type cmd 1>/dev/null 2>&1.

man env
# "The option '-i' causes env to completely ignore the environment it inherits."
# In other words, there are no aliases or functions to be looked up by the type command.

ls() { echo 'Hello, world!'; }

ls
type ls
env -i type ls

cmd=ls
cmd=lsx
env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }

The which command might be useful. man which

It returns 0 if the executable is found and returns 1 if it's not found or not executable:

NAME

       which - locate a command

SYNOPSIS

       which [-a] filename ...

DESCRIPTION

       which returns the pathnames of the files which would
       be executed in the current environment, had its
       arguments been given as commands in a strictly
       POSIX-conformant shell. It does this by searching
       the PATH for executable files matching the names
       of the arguments.

OPTIONS

       -a     print all matching pathnames of each argument

EXIT STATUS

       0      if all specified commands are 
              found and executable

       1      if one or more specified commands is nonexistent
              or not executable

       2      if an invalid option is specified

The nice thing about which is that it figures out if the executable is available in the environment that which is run in - it saves a few problems...


checkexists() {
    while [ -n "$1" ]; do
        [ -n "$(which "$1")" ] || echo "$1": command not found
        shift
    done
}

GIT=/usr/bin/git                     # STORE THE RELATIVE PATH
# GIT=$(which git)                   # USE THIS COMMAND TO SEARCH FOR THE RELATIVE PATH

if [[ ! -e $GIT ]]; then             # CHECK IF THE FILE EXISTS
    echo "PROGRAM DOES NOT EXIST."
    exit 1                           # EXIT THE PROGRAM IF IT DOES NOT
fi

# DO SOMETHING ...

exit 0                               # EXIT THE PROGRAM IF IT DOES

I wanted the same question answered but to run within a Makefile.

install:
    @if [[ ! -x "$(shell command -v ghead)" ]]; then \
        echo 'ghead does not exist. Please install it.'; \
        exit -1; \
    fi

I never did get the previous answers to work on the box I have access to. For one, type has been installed (doing what more does). So the builtin directive is needed. This command works for me:

if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi

If there isn't any external type command available (as taken for granted here), we can use POSIX compliant env -i sh -c 'type cmd 1>/dev/null 2>&1':

# Portable version of Bash's type -P cmd (without output on stdout)
typep() {
   command -p env -i PATH="$PATH" sh -c '
      export LC_ALL=C LANG=C
      cmd="$1"
      cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`"
      [ $? != 0 ] && exit 1
      case "$cmd" in
        *\ /*) exit 0;;
            *) printf "%s\n" "error: $cmd" 1>&2; exit 1;;
      esac
   ' _ "$1" || exit 1
}

# Get your standard $PATH value
#PATH="$(command -p getconf PATH)"
typep ls
typep builtin
typep ls-temp

At least on Mac OS X v10.6.8 (Snow Leopard) using Bash 4.2.24(2) command -v ls does not match a moved /bin/ls-temp.


This will tell according to the location if the program exist or not:

    if [ -x /usr/bin/yum ]; then
        echo "This is Centos"
    fi

In case you want to check if a program exists and is really a program, not a Bash built-in command, then command, type and hash are not appropriate for testing as they all return 0 exit status for built-in commands.

For example, there is the time program which offers more features than the time built-in command. To check if the program exists, I would suggest using which as in the following example:

# First check if the time program exists
timeProg=`which time`
if [ "$timeProg" = "" ]
then
  echo "The time program does not exist on this system."
  exit 1
fi

# Invoke the time program
$timeProg --quiet -o result.txt -f "%S %U + p" du -sk ~
echo "Total CPU time: `dc -f result.txt` seconds"
rm result.txt

For those interested, none of the methodologies in previous answers work if you wish to detect an installed library. I imagine you are left either with physically checking the path (potentially for header files and such), or something like this (if you are on a Debian-based distribution):

dpkg --status libdb-dev | grep -q not-installed

if [ $? -eq 0 ]; then
    apt-get install libdb-dev
fi

As you can see from the above, a "0" answer from the query means the package is not installed. This is a function of "grep" - a "0" means a match was found, a "1" means no match was found.


Use:

if [[ `command --help` ]]; then
  echo "This command exists"
else
  echo "This command does not exist";
fi

Put in a working switch, such as "--help" or "-v" in the if check: if [[ command --help ]]; then


Try using:

test -x filename

or

[ -x filename ]

From the Bash manpage under Conditional Expressions:

 -x file
          True if file exists and is executable.

Script

#!/bin/bash

# Commands found in the hash table are checked for existence before being
# executed and non-existence forces a normal PATH search.
shopt -s checkhash

function exists() {
 local mycomm=$1; shift || return 1

 hash $mycomm 2>/dev/null || \
 printf "\xe2\x9c\x98 [ABRT]: $mycomm: command does not exist\n"; return 1;
}
readonly -f exists

exists notacmd
exists bash
hash
bash -c 'printf "Fin.\n"'

Result

? [ABRT]: notacmd: command does not exist
hits    command
   0    /usr/bin/bash
Fin.

The following is a portable way to check whether a command exists in $PATH and is executable:

[ -x "$(command -v foo)" ]

Example:

if ! [ -x "$(command -v git)" ]; then
  echo 'Error: git is not installed.' >&2
  exit 1
fi

The executable check is needed because bash returns a non-executable file if no executable file with that name is found in $PATH.

Also note that if a non-executable file with the same name as the executable exists earlier in $PATH, dash returns the former, even though the latter would be executed. This is a bug and is in violation of the POSIX standard. [Bug report] [Standard]

In addition, this will fail if the command you are looking for has been defined as an alias.


The hash-variant has one pitfall: On the command line you can for example type in

one_folder/process

to have process executed. For this the parent folder of one_folder must be in $PATH. But when you try to hash this command, it will always succeed:

hash one_folder/process; echo $? # will always output '0'

There are a ton of options here, but I was surprised no quick one-liners. This is what I used at the beginning of my scripts:

[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }
[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }

This is based on the selected answer here and another source.


I use this, because it's very easy:

if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists;else echo "not exists";fi

or

if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then
echo exists
else echo "not exists"
fi

It uses shell builtins and programs' echo status to standard output and nothing to standard error. On the other hand, if a command is not found, it echos status only to standard error.


I had to check if Git was installed as part of deploying our CI server. My final Bash script was as follows (Ubuntu server):

if ! builtin type -p git &>/dev/null; then
  sudo apt-get -y install git-core
fi

I agree with lhunath to discourage use of which, and his solution is perfectly valid for Bash users. However, to be more portable, command -v shall be used instead:

$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed.  Aborting." >&2; exit 1; }

Command command is POSIX compliant. See here for its specification: command - execute a simple command

Note: type is POSIX compliant, but type -P is not.


I second the use of "command -v". E.g. like this:

md=$(command -v mkdirhier) ; alias md=${md:=mkdir}  # bash

emacs="$(command -v emacs) -nw" || emacs=nano
alias e=$emacs
[[ -z $(command -v jed) ]] && alias jed=$emacs

I would just try and call the program with for example --version or --help and check if the command succeeded or failed

Used with set -e, the script will exit if the program is not found, and you will get a meaningful error message:

#!/bin/bash
set -e
git --version >> /dev/null

I couldn't get one of the solutions to work, but after editing it a little I came up with this. Which works for me:

dpkg --get-selections | grep -q linux-headers-$(uname -r)

if [ $? -eq 1 ]; then
        apt-get install linux-headers-$(uname -r)
fi

My setup for a Debian server:

I had the problem when multiple packages contained the same name.

For example apache2. So this was my solution:

function _apt_install() {
    apt-get install -y $1 > /dev/null
}

function _apt_install_norecommends() {
    apt-get install -y --no-install-recommends $1 > /dev/null
}
function _apt_available() {
    if [ `apt-cache search $1 | grep -o "$1" | uniq | wc -l` = "1" ]; then
        echo "Package is available : $1"
        PACKAGE_INSTALL="1"
    else
        echo "Package $1 is NOT available for install"
        echo  "We can not continue without this package..."
        echo  "Exitting now.."
        exit 0
    fi
}
function _package_install {
    _apt_available $1
    if [ "${PACKAGE_INSTALL}" = "1" ]; then
        if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
             echo  "package is already_installed: $1"
        else
            echo  "installing package : $1, please wait.."
            _apt_install $1
            sleep 0.5
        fi
    fi
}

function _package_install_no_recommends {
    _apt_available $1
    if [ "${PACKAGE_INSTALL}" = "1" ]; then
        if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
             echo  "package is already_installed: $1"
        else
            echo  "installing package : $1, please wait.."
            _apt_install_norecommends $1
            sleep 0.5
        fi
    fi
}

I'd say there isn't any portable and 100% reliable way due to dangling aliases. For example:

alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/

Of course, only the last one is problematic (no offence to Ringo!). But all of them are valid aliases from the point of view of command -v.

In order to reject dangling ones like ringo, we have to parse the output of the shell built-in alias command and recurse into them (command -v isn't a superior to alias here.) There isn't any portable solution for it, and even a Bash-specific solution is rather tedious.

Note that a solution like this will unconditionally reject alias ls='ls -F':

test() { command -v $1 | grep -qv alias }

It could be simpler, just:

#!/usr/bin/env bash                                                                
set -x                                                                             

# if local program 'foo' returns 1 (doesn't exist) then...                                                                               
if ! type -P foo; then                                                             
    echo 'crap, no foo'                                                            
else                                                                               
    echo 'sweet, we have foo!'                                                    
fi                                                                                 

Change foo to vi to get the other condition to fire.


Command -v works fine if the POSIX_BUILTINS option is set for the <command> to test for, but it can fail if not. (It has worked for me for years, but I recently ran into one where it didn't work.)

I find the following to be more failproof:

test -x "$(which <command>)"

Since it tests for three things: path, existence and execution permission.


Check for multiple dependencies and inform status to end users

for cmd in latex pandoc; do
  printf '%-10s' "$cmd"
  if hash "$cmd" 2>/dev/null; then
    echo OK
  else
    echo missing
  fi
done

Sample output:

latex     OK
pandoc    missing

Adjust the 10 to the maximum command length. It is not automatic, because I don't see a non-verbose POSIX way to do it: How can I align the columns of a space separated table in Bash?

Check if some apt packages are installed with dpkg -s and install them otherwise.

See: Check if an apt-get package is installed and then install it if it's not on Linux

It was previously mentioned at: How can I check if a program exists from a Bash script?


Assuming you are already following safe shell practices:

set -eu -o pipefail
shopt -s failglob

./dummy --version 2>&1 >/dev/null

This assumes the command can be invoked in such a way that it does (almost) nothing, like reporting its version or showing help.

If the dummy command is not found, Bash exits with the following error...

./my-script: line 8: dummy: command not found

This is more useful and less verbose than the other command -v (and similar) answers because the error message is auto generated and also contains a relevant line number.