test(1)How do I tell whether a program is installed and is executable from within a script?
-x file True if file exists and is executable. True indicates only
that the execute flag is on. If file is a directory, true
indicates that file can be searched.
#!/bin/sh
if [ -x /usr/local/bin/vim ]; then
alias vi=vim
fi
# Or
[ -x /usr/local/bin/vim ] && alias vi=vim
hash <program> 2>/dev/null || { echo "Script requires <program> but it's not installed. Aborting."; exit 1; }
It's not going to check and follow PATH. elgrande has the right idea, use which(1). Or whereis(1).test -x hexdump || echo exists
~ > command -v pkg
/usr/sbin/pkg
~ > if command -v pkg > /dev/null ; then echo "installed" ; else echo "not installed" ; fi
installed
-s
on my part though.~ > which -s ls
/bin/ls -> /bin/ls
~ > which -s cp
cp: aliased to cp -v
~ > which -s apropos
/usr/bin/apropos -> /usr/bin/apropos
zsh
and I guess the behaviour is bit different with it, when I did a try this time with sh
it worked as intended. ~ $ which ls
/bin/ls
~ $ which -s ls
~ $ echo $SHELL
/bin/sh
$ test -x ksh ||echo exists
exists
$ test -x nosuchfile ||echo exists
exists
$ test -x $(which ksh) ||echo exists
$ test -x $(which nosuchfile) ||echo exists
$
$ [[ -f $(which ksh) && -x $(which ksh) ]] && echo yep || echo nope
yep
$ [[ -f $(which nosuchfile) && -x $(which nosuchfile) ]] && echo yep || echo nope
nope
$ touch bin/iamafile
$ chmod 444 bin/iamafile
$ ls -l bin/iamafile
-r--r--r-- 1 foghorn leghorn 0 Feb 15 08:03 bin/iamafile
$ [[ -f $(which iamafile) && -x $(which iamafile) ]] && echo yep || echo nope
nope
$ chmod 744 bin/iamafile
$ ls -l bin/iamafile
-rwxr--r-- 1 foghorn leghorn 0 Feb 15 08:03 bin/iamafile
$ [[ -f $(which iamafile) && -x $(which iamafile) ]] && echo yep || echo nope
yep
Wrong, the echo is executed if the test fails.Code:$ test -x ksh ||echo exists exists
false || echo "is false"
true || echo "is true"
false && echo "is false"
true && echo "is true"
arg1 || arg2
and arg1 && arg2
arg1 || arg2
arg2 is executed if arg1 returns false (rc != 0) arg1 && arg2
arg2 is executed if arg1 returns true (rc==0)[[ "$(type -p procs)" != "" ]] && alias ps="$(type -p procs)"
[[
is a bash comparison that does not launch a new program. Now, in bash [
is also a shell built-in, so it doesn't invoke a new program. But in some cases (often with /bin/sh
instead of bash), writing if [ blah ]
means invoking a whole separate program (/usr/bin/[
).if [[ "$(type -p exa)" != "" ]]
then
alias ls="$(type -p exa)"
export EXA_COLORS="blah blah blah"
fi
type -p
because bash returns the path to the program. (e.g., type -p exa
returns /usr/local/bin/exa
) That way my alias command turns into alias ls=/usr/local/bin/exa
. I could just do alias ls=exa
and bash would find whatever version of exa
is somewhere in my PATH
. It would work, and that's normal. I'm being overzealous by hard-wiring the path (/usr/local/bin/exa
) so that the PATH
doesn't have to be searched. Probably a needless optimisation, but that's me in a nutshell. $ whereis echo
/usr/bin/echo
$ ls -l /usr/bin/echo
echo
, hexdump
, test
, you name it. whereis -b echo
would return a path to the executable, so there's really no need to check if it's executable or not.#!/bin/sh
ECHO=$(whereis -b echo)
if [ -x "$ECHO" ]; then
$ECHO "Hello World!"
fi
# this will do the exact same thing
[ -x "$ECHO" ] && $ECHO "Hello World!"
Of course it does, Sherlock. That's what the OR means.Wrong, the echo is executed if the test fails.
Since I've been doing UNIX professionally for 30 years, meaning getting paid to do it, I fully understand the difference between OR and AND.You really need to understand the difference betweenarg1 || arg2
andarg1 && arg2
The[[
is a bash comparison that does not launch a new program. Now, in bash[
is also a shell built-in, so it doesn't invoke a new program. But in some cases (often with/bin/sh
instead of bash), writingif [ blah ]
means invoking a whole separate program (/usr/bin/[
).
you want whereis -bqEhmwhereis -b echo
would return a path to the executable, so there's really no need to check if it's executable or not.
But if you must;
Code:#!/bin/sh ECHO=$(whereis -b echo) if [ -x "$ECHO" ]; then $ECHO "Hello World!" fi # this will do the exact same thing [ -x "$ECHO" ] && $ECHO "Hello World!"
which
, whereis
and friends are all not really portable, and you might even get different results with shells having them as builtins.command -v
.#!/bin/sh
CMD="$1"
IFS=:
set -- ${PATH}
while [ -n "$1" ]; do
if [ -x "$1/${CMD}" ]; then
printf "%s\n" "$1/${CMD}"
exit 0;
fi
shift
done
printf >&2 "%s: not found\n" "${CMD}"
exit 1;
How do I tell whether a program is installed and is executable from within a script? […]
‑e
flag, see sh(1):#!/bin/sh -e
foo
bar
bar
made)foo: not found
source
the script.Not necessarily. The hint is a good one though, you should never rely on something to "just work" later, just because you checked earlier. And if that's the only purpose of your check, it's indeed pointless.You don’t. It’s a potential TOCTOU hence unnecessary overhead.
#!/bin/sh
# A "which"-like function strictly only looking for executables in a
# given path
_which()
{
# check arguments and save the command to look for
[ $# -lt 1 ] || [ $# -gt 2 ] && return 1
_w_cmd="$1"
_w_rc=1
# replace positional parameters by path entries of the path passed,
# falling back to ${PATH} from the environment.
# Also disable pathname expansion (set -f), we want to check literal path
# names in the following loop.
_w_ifs=${IFS}
_w_set=$(set +o)
set -f
IFS=:
set -- ${2:-${PATH}}
# check for executable in each path entry, stop indicating success on
# first hit, ignore empty path entries
for p; do
if [ -n "$p" ] && [ -f "$p/${_w_cmd}" ] && [ -x "$p/${_w_cmd}" ]; then
echo "$p/${_w_cmd}"
_w_rc=0
break;
fi
done
# reset IFS and shell options and return success/failure
eval ${_w_set}
IFS=${_w_ifs}
return ${_w_rc}
}
# Formatted output
alias pf='printf "%10s: %s\n"'
# First walk the PATH from the environment
echo
echo full path:
echo
for c; do
result=$(_which "${c}") \
&& pf $c "${result}" \
|| pf $c "** not found!"
done
echo
# 'getconf PATH' gives a minimal PATH to find all POSIX tools
echo POSIX path:
echo
for c; do
result=$(_which "${c}" "$(getconf PATH)") \
&& pf $c "${result}" \
|| pf $c "** not found!"
done
echo
# Using "command -v", we also get functions, aliases and builtins (which then
# "shadow" any tools that might be available in the path).
# The output format is standardized in POSIX, so we can reliably interpret it.
# We *could* use a custom path here as well using env, e.g. for the POSIX path:
# $(env "PATH=$(getconf PATH)" command -v "${c}")
echo full path + builtins:
echo
for c; do
result=$(command -v "${c}") \
&& case "${result}" in
"alias "*) pf $c "<shell alias>";;
/*) pf $c "${result}";;
*) pf $c "<shell builtin or function>";;
esac \
|| pf $c "** not found!"
done
echo
$ ./testcmds.sh sh zsh test pf awk
full path:
sh: /bin/sh
zsh: /usr/local/bin/zsh
test: /bin/test
pf: ** not found!
awk: /usr/bin/awk
POSIX path:
sh: /bin/sh
zsh: ** not found!
test: /bin/test
pf: ** not found!
awk: /usr/bin/awk
full path + builtins:
sh: /bin/sh
zsh: /usr/local/bin/zsh
test: <shell builtin or function>
pf: <shell alias>
awk: /usr/bin/awk
$
which
. They aren't standardized, so even if present, might behave differently, e.g. it's unspecified how they would deal with shell builtins and so on.Just noticed this option. It looks ideal for my needs, although it doesn't seem to provide a return code, so I can't see how to use it for testing the existence of a particular binary.you want whereis -bq
I have always been using `which meh > /dev/null 2> /dev/null` and didn't notice any problems on the platforms I use.
root@thinkpad:/usr/src # which which > /dev/null 2> /dev/null
Ambiguous output redirect.
root@thinkpad:/usr/src # uname -a
FreeBSD thinkpad 13.2-RELEASE FreeBSD 13.2-RELEASE releng/13.2-n254617-525ecfdad 597 GENERIC amd64
Because in 13.2 the root's shell is csh, and its syntax is different:Code:root@thinkpad:/usr/src # which which > /dev/null 2> /dev/null Ambiguous output redirect. root@thinkpad:/usr/src # uname -a FreeBSD thinkpad 13.2-RELEASE FreeBSD 13.2-RELEASE releng/13.2-n254617-525ecfdad 597 GENERIC amd64
% which which >& /dev/null
>& name
....
The file name is used as standard output.
....
The forms involving `&' route the diagnostic output into the
specified file as well as the standard output.
csh
is certainly not a good example, it doesn't follow bourne/POSIX syntax....I have always been using `which meh > /dev/null 2> /dev/null` and didn't notice any problems on the platforms I use.
zsh
(and then switching to base sh
):$ command -v which
which
$ command -v test
test
$ which test
test: shell built-in command
$ sh
$ command -v which
/usr/bin/which
$ command -v test
test
$ which test
/bin/test
$
which
when you know the environment and shell your script will run on.which
. It isn't exactly specified what it does, and it isn't even guaranteed to exist. Therefore, for portable scripts:command -v
if you want to cover all sorts of "command-like" things in a shell. Thanks to the standardized output format, you can tell what you got (an actual path, a function or builtin, or an alias).set -f
to disable pathname expansion, otherwise it can give wrong results when that path or binary name contain special shell pattern characters... (edit: I just updated my example above to also temporarily set -f
to be really sure it always works)A little nitpick on this (it's correct if nothing else preceeds it as written), just because I've seen related confusion on these forums about it:-arg1 || arg2
arg2 is executed if arg1 returns false (rc != 0)
-arg1 && arg2
arg2 is executed if arg1 returns true (rc==0)
||
is evaluated if $?
is currently non-zero&&
is evaluated if $?
is currently zero$?
was last set||
and &&
, they are just "batch processed" from left to right, deciding whether or not to evaluate the following code each time ||
or &&
is encountered$ false && echo "true" || echo "false"
false
$ false || echo "false" && echo "true"
false
true
&&
taking precedence over ||
, but no, there isn't, see:$ false && echo "true" || echo "false" || echo "true" && echo "false"
false
false
false
-> $?
becomes 1
&& echo "true"
-> skip this, $?
remains 1
|| echo "false"
-> evaluate and execute, $?
becomes 0
|| echo "true"
-> skip this, $?
remains 0
&& echo "false"
-> evaluate and execute