213

I am writing my first shell script. In my script I would like to check if a certain command exists, and if not, install the executable. How would I check if this command exists?

if # Check that foobar command doesnt exist
then
    # Now install foobar
fi
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andrew
  • 227,796
  • 193
  • 515
  • 708
  • 9
    Just happened to come across. I think this is the same question as: http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script , but it gives much more details. – Jerry Tian Oct 23 '12 at 04:14
  • 2
    Also relevant is [How to 'hash -r' and refresh all shells?](https://unix.stackexchange.com/q/398028/56041) and [When to rehash executables in $PATH with bash?](https://superuser.com/q/999439/173513) – jww Jan 30 '19 at 05:11
  • 1
    This was not "already answered" because it asks about shell scripts, which minimally means `sh` while the referred question is specific to `bash`. – monokrome Mar 19 '20 at 22:44

8 Answers8

316

In general, that depends on your shell, but if you use bash, zsh, ksh or sh (as provided by dash), the following should work:

if ! type "$foobar_command_name" > /dev/null; then
  # install foobar here
fi

For a real installation script, you'd probably want to be sure that type doesn't return successfully in the case when there is an alias foobar. In bash you could do something like this:

if ! foobar_loc="$(type -p "$foobar_command_name")" || [[ -z $foobar_loc ]]; then
  # install foobar here
fi
Ivan Tarasov
  • 7,038
  • 5
  • 27
  • 23
  • 1
    I like this answer. I tried not to be too swayed by ivants' image which I like even more ;) – Michael Durrant Sep 23 '11 at 00:21
  • 5
    hmm...when I change it to say `if ! type "foo" > /dev/null;` then I get the output on the screen "myscript.sh: line 12: type: foo: not found", however, it still seems to work because when I say `if ! type "ls" > /dev/null;` there is no output and the if statement does not get executed (since it returns true). How can I silence the output when the command doesnt exist? – Andrew Sep 23 '11 at 20:37
  • 16
    Andrew, try `if ! type "foo" > /dev/null 2>&1;` – Ivan Tarasov Sep 26 '11 at 21:47
  • 11
    `> /dev/null 2>&1` is the same as `&> /dev/null` – Iulius Curt Sep 05 '12 at 08:54
  • The man page for `type` is very poor on my distro (Arch Linux). The help text is better, albeit only slightly. `type: usage: type [-afptP] name [name ...] `. The man page shows "options" as "none". – sherrellbc Jul 11 '16 at 13:24
  • As recommended [here](https://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script) `hash` maybe the better type especially if you use the command afterwards. – lony Jun 20 '17 at 12:03
  • @sherrellbc `type` is typically a shell builtin, so instead of trying to find a manual among the system manuals open your shell manual, e.g. `man bash` or `man zshbuiltins`, and search for `^ +type `. – Ivan Tarasov Mar 25 '18 at 22:04
  • @IvanTarasov, what do I do if I have alias the same name as the program name to add some options automatically? Do I still use `-p` flag? Should I not use it? –  Feb 15 '19 at 04:28
  • @user9429402 What is your intention here? Check that the alias exists? Or check that the program exists? – Ivan Tarasov Apr 17 '19 at 00:33
  • Using `zsh` on a Mac, I had to remove the $ to get it working. – X99 Feb 11 '20 at 17:10
55

Five ways, 4 for bash and 1 addition for zsh:

  • type foobar &> /dev/null
  • hash foobar &> /dev/null
  • command -v foobar &> /dev/null
  • which foobar &> /dev/null
  • (( $+commands[foobar] )) (zsh only)

You can put any of them to your if clause. According to my tests (https://www.topbug.net/blog/2016/10/11/speed-test-check-the-existence-of-a-command-in-bash-and-zsh/), the 1st and 3rd method are recommended in bash and the 5th method is recommended in zsh in terms of speed.

xuhdev
  • 8,018
  • 2
  • 41
  • 69
  • [Check if a program exists from a Bash script](https://stackoverflow.com/q/592620/608639) specifically advises against using `which`. – jww Oct 16 '17 at 04:06
  • Also `echo =foobar` in zsh. Note that your `zsh` solution only returns true/false, but doesn't print the pathname if existant. Also `echo $commands[foo]` won't return non-zero if `foo` doesn't exist. For a function that works in both shells, see [here](https://stackoverflow.com/a/52127608/5353461) – Tom Hale Sep 01 '18 at 11:30
  • The 5th command returns nothing in `zsh` in macOS Catalina. `zsh` version is `zsh 5.7.1 (x86_64-apple-darwin19.0)` – Raptor Oct 13 '20 at 14:58
  • 2
    @Raptor `if` examines exit code. – xuhdev Oct 13 '20 at 18:43
  • 1
    the third option seems to be the only one with same behaviour in zsh and bash. For the others, zsh writes to stdout on error while bash writes to stderr. – xeruf Aug 11 '22 at 11:32
  • `(( $+commands[foobar] ))` doesn't work, for anyone else reading this. Probably why the blog poster recognized it as the "fastest" one to respond. Either way, it seems `echo =cmd` is probably what you want these days, it's a lot easier to use. – tubbo Oct 01 '22 at 20:27
  • 1
    @tubbo `(( $+commands[foobar] ))` works for me. Could you describe a scenario in which it fails? – xuhdev Oct 06 '22 at 20:05
41

Try using type:

type foobar

For example:

$ type ls
ls is aliased to `ls --color=auto'

$ type foobar
-bash: type: foobar: not found

This is preferable to which for a few reasons:

  1. The default which implementations only support the -a option that shows all options, so you have to find an alternative version to support aliases

  2. type will tell you exactly what you are looking at (be it a Bash function or an alias or a proper binary).

  3. type doesn't require a subprocess

  4. type cannot be masked by a binary (for example, on a Linux box, if you create a program called which which appears in path before the real which, things hit the fan. type, on the other hand, is a shell built-in (yes, a subordinate inadvertently did this once).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Foo Bah
  • 25,660
  • 5
  • 55
  • 79
30

The question doesn't specify a shell, so for those using fish (friendly interactive shell):

if command -v foo > /dev/null
  echo exists
else
  echo does not exist
end

For basic POSIX compatibility, we use the -v flag which is an alias for --search or -s.

Mmmh mmh
  • 5,334
  • 3
  • 21
  • 29
Dennis
  • 56,821
  • 26
  • 143
  • 139
  • doesn't work for aliases – IC_ Apr 30 '20 at 10:20
  • @Herrgott that's because `command -v/-s` searches in the `$PATH`. Aliases in fish are functions, and functions are searched for in the `$fish_function_path`. Many ways to check if an alias exists, e.g. `if alias | grep -w 'myalias' >/dev/null; echo ok; end`. – Dennis Apr 30 '20 at 15:49
  • Doesn't this execute the command too? – Aaron Franke Jun 18 '20 at 17:33
  • @AaronFranke it only searches for the executable, doesn't execute it. – Dennis Jun 20 '20 at 16:40
  • You can use `--query` and avoid the redirect: http://fishshell.com/docs/current/cmds/command.html. While not POSIX-compliant, `command` is built into Fish. – Brett Cannon Aug 16 '21 at 17:48
22

Check if a program exists from a Bash script covers this very well. In any shell script, you're best off running command -v $command_name for testing if $command_name can be run. In bash you can use hash $command_name, which also hashes the result of any path lookup, or type -P $binary_name if you only want to see binaries (not functions etc.)

Community
  • 1
  • 1
Morten
  • 634
  • 5
  • 13
3

A function which works in both bash and zsh:

# Return the first pathname in $PATH for name in $1
function cmd_path () {
  if [[ $ZSH_VERSION ]]; then
    whence -cp "$1" 2> /dev/null
  else  # bash
     type -P "$1"  # No output if not in $PATH
  fi
}

Non-zero is returned if the command is not found in $PATH.

Tom Hale
  • 40,825
  • 36
  • 187
  • 242
1

A function I have in an install script made for exactly this

function assertInstalled() {
    for var in "$@"; do
        if ! which $var &> /dev/null; then
            echo "Install $var!"
            exit 1
        fi
    done
}

example call:

assertInstalled zsh vim wget python pip git cmake fc-cache
mgild
  • 774
  • 6
  • 13
1

which <cmd>

also see options which supports for aliases if applicable to your case.

Example

$ which foobar
which: no foobar in (/usr/local/bin:/usr/bin:/cygdrive/c/Program Files (x86)/PC Connectivity Solution:/cygdrive/c/Windows/system32/System32/WindowsPowerShell/v1.0:/cygdrive/d/Program Files (x86)/Graphviz 2.28/bin:/cygdrive/d/Program Files (x86)/GNU/GnuPG
$ if [ $? -eq 0 ]; then echo "foobar is found in PATH"; else echo "foobar is NOT found in PATH, of course it does not mean it is not installed."; fi
foobar is NOT found in PATH, of course it does not mean it is not installed.
$

PS: Note that not everything that's installed may be in PATH. Usually to check whether something is "installed" or not one would use installation related commands relevant to the OS. E.g. rpm -qa | grep -i "foobar" for RHEL.

Kashyap
  • 15,354
  • 13
  • 64
  • 103
  • `which` has other pitfalls as well – Foo Bah Sep 22 '11 at 23:53
  • I was just starting to read `man type` to see how it's better.. may be you can save me some time by posting it here.. :) – Kashyap Sep 22 '11 at 23:55
  • The real trouble with `which` is that it is an *external* command, and won't be able to deal with the specifics of the current shell session internals, as it can have no knowledge of them. – Henk Langeveld Feb 16 '14 at 12:04
  • [Check if a program exists from a Bash script](https://stackoverflow.com/q/592620/608639) specifically advises against using `which`. – jww Oct 16 '17 at 04:06
  • it's best to not use which here, other answers are safer – monokrome Mar 19 '20 at 22:48