从Bash脚本检查程序是否存在
我将如何validation程序是否存在,以何种方式返回错误并退出,还是继续执行脚本?
这似乎应该很容易,但它一直困在我身上。
回答
兼容POSIX:
command -v <the_command>
对于bash
特定的环境:
hash <the_command> # For regular commands. Or... type <the_command> # To check built-ins and keywords
说明
避免which
。 它不仅是一个外部过程,你只需要做很less的事情(意思就是像hash
, type
或者command
那样的内buildhash
是便宜的),你也可以依靠内build函数实际做你想做的事,而外部命令的影响可以从系统到系统很容易变化。
为什么要关心
- 许多操作系统有一个甚至没有设置退出状态 ,这意味着
if which foo
甚至不会在那里工作,并且总是会报告foo
存在,即使它不存在(注意一些POSIX shell似乎做这也是hash
)。 - 许多操作系统使得自定义和恶意的东西,如改变输出,甚至挂钩到包pipe理器。
所以,不要使用which
。 而是使用其中之一:
$ command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; } $ type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; } $ hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
(小调 – 注意:有些人会build议2>&-
是相同的2>/dev/null
但是更短 – 这是不真实的 2>&-
closuresFD 2,当它试图写入stderr时, ,这与成功写入并丢弃输出(和危险的!)非常不同
如果你的hash bang是/bin/sh
那么你应该关心POSIX所说的。 type
和hash
的退出代码不是由POSIX很好地定义的,并且在命令不存在的情况下hash
可以成功退出(还没有看到这种type
)。 command
的退出状态是由POSIX定义的,所以一个可能是最安全的使用。
如果您的脚本使用bash
,POSIX规则不再是真正的问题,并且type
和hash
变得非常安全。 type
现在有一个-P
来searchPATH
, hash
有副作用,命令的位置将被散列(为了下一次使用它,更快的查找),这通常是一件好事,因为你可能检查它的存在为了实际使用它。
作为一个简单的例子,如果它存在,这是一个运行gdate
的函数,否则为date
:
gnudate() { if hash gdate 2>/dev/null; then gdate "$@" else date "$@" fi }
我同意lhunath劝阻使用,而且他的解决scheme对于BASH用户是完全有效的 。 但是,为了更便于携带,应该使用command -v
来代替:
$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed. Aborting." >&2; exit 1; }
命令command
是POSIX兼容的,请参阅这里的规范: http : //pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html
注意: type
是POSIX兼容的,但是type -P
不是。
以下是一个可移植的方法来检查命令是否存在于$PATH
并且是可执行的:
[ -x "$(command -v foo)" ]
例:
if ! [ -x "$(command -v git)" ]; then echo 'Error: git is not installed.' >&2 exit 1 fi
可执行检查是必需的,因为如果在$PATH
找不到具有该名称的可执行文件,则bash将返回一个不可执行的文件。
还要注意,如果在$PATH
早些时候存在与可执行文件同名的不可执行文件,则即使后者被执行,破折号也会返回前者。 这是一个错误,违反了POSIX标准。 [ 错误报告 ] [ 标准 ]
另外,如果您正在查找的命令已被定义为别名,则这将失败。
我有一个在我的.bashrc中定义的函数,使这更容易。
command_exists () { type "$1" &> /dev/null ; }
下面是一个如何使用它的例子(来自我的.bash_profile
。)
if command_exists mvim ; then export VISUAL="mvim --nofork" fi
这取决于您是否想知道它是否存在于$PATH
variables中的某个目录中,或者您是否知道它的绝对位置。 如果你想知道它是否在$PATH
variables,使用
if which programname >/dev/null; then echo exists else echo does not exist fi
否则使用
if [ -x /path/to/programname ]; then echo exists else echo does not exist fi
在第一个例子中,redirect到/dev/null/
将抑制which
程序的输出。
扩展@ lhunath's和@ GregV的答案,下面是希望轻松地将该检查放入if
语句的人员的代码:
exists() { command -v "$1" >/dev/null 2>&1 }
以下是如何使用它:
if exists bash; then echo 'Bash exists!' else echo 'Your system does not have Bash' fi
尝试使用:
test -x filename
要么
[ -x filename ]
根据条件expression式的bash manpage:
-x file True if file exists and is executable.
如@lhunath所示 ,在一个bash脚本中使用hash
:
hash foo &> /dev/null if [ $? -eq 1 ]; then echo >&2 "foo not found." fi
这个脚本运行hash
,然后检查最近的命令的退出代码,存储在$?
的值 ,等于1
。 如果hash
没有findfoo
,则退出代码将是1
。 如果foo
存在,则退出码将为0
。
&> /dev/null
redirect标准错误和标准输出,使其不会出现在屏幕上, echo >&2
将消息写入标准错误。
我从来没有得到上述解决scheme在我有权访问的框中工作。 首先,types已经安装(做更多的事情)。 所以内build指令是需要的。 这个命令适用于我:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
如果你检查程序是否存在,你可能会在晚些时候运行它。 为什么不尝试运行呢?
if foo --version >/dev/null 2>&1; then echo Found else echo Not found fi
程序运行比单纯查看PATH目录和文件权限更值得信赖。
另外你可以从你的程序中得到一些有用的结果,比如它的版本。
当然,缺点是一些程序可能很繁重,有些程序不能立即(并成功)退出。
如果可以的话,为什么不使用Bash buildins?
which programname
…
type -P programname
对于那些感兴趣的,如果你想检测一个已安装的库,上面的方法都没有工作。 我想你不是在物理上检查path(可能是用于头文件等),或者是类似的东西(如果你是在一个基于Debian的发行版):
dpkg --status libdb-dev | grep -q not-installed if [ $? -eq 0 ]; then apt-get install libdb-dev fi
从上面可以看出,查询中的“0”答案表示软件包未安装。 这是“grep”的function – “0”表示find匹配,“1”表示找不到匹配。
检查多个依赖关系并将状态通知最终用户
for cmd in "latex" "pandoc"; do printf "%-10s" "$cmd" if hash "$cmd" 2>/dev/null; then printf "OK\n"; else printf "missing\n"; fi done
示例输出:
latex OK pandoc missing
调整10
到最大的命令长度。 不是自动的,因为我没有看到一个非详细的方式来做到这一点。
which
命令可能是有用的。 男人哪
如果find可执行文件,则返回0;如果找不到或不可执行,则返回1:
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 exe- cutable 2 if an invalid option is specified
好的事情是,它会发现如果可执行文件在运行的环境中可用 – 保存一些问题…
-亚当
我会说没有便携和100%可靠的方式由于悬挂alias
。 例如:
alias john='ls --color' alias paul='george -F' alias george='ls -h' alias ringo=/
当然,只有最后一个是有问题的(Ringo没有冒犯!)但是从command -v
的angular度来看,它们都是有效的alias
。
为了拒绝像ringo
这样的悬挂类,我们必须parsingshell内build的alias
命令的输出并recursion到它们中( command -v
在此没有优于alias
)。没有可移植的解决scheme,甚至是Bash具体的解决scheme相当繁琐。
注意像这样的解决scheme将无条件地拒绝alias ls='ls -F'
test() { command -v $1 | grep -qv alias }
要模仿Bash的type -P cmd
我们可以使用POSIX兼容的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; }
哈希variables有一个缺陷:在命令行上,你可以input例如
one_folder/process
执行进程。 为此,one_folder的父文件夹必须位于$ PATH中 。 但是当你试图散列这个命令时,它总是会成功的:
hash one_folder/process; echo $? # will always output '0'
我第二次使用“command -v”。 像这样:
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
hash foo 2>/dev/null
:与zsh,bash,dash和ash一起使用。
type -p foo
:它似乎与zsh,bash和ash(busybox)一起工作,但不是短划线(它将-p
解释为参数)。
command -v foo
:使用zsh,bash,破折号,但不是灰(busybox)(- -ash: command: not found
)。
另外请注意, builtin
ash
和dash
不可用。
如果没有可用的外部type
命令(我们认为这是理所当然的),我们可以使用符合POSIX的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
至less在Mac OS X 10.6.8上使用Bash 4.2.24(2) command -v ls
与移动的/bin/ls-temp
不匹配。
如果你们不能把上面/下面的东西拿来工作,把头发拉出来,试着用bash -c
来运行相同的命令。 看看这个梦幻般的deli妄,当你运行$(sub-command)时,这就是真正发生的事情:
第一。 它可以给你完全不同的输出。
$ command -v ls alias ls='ls --color=auto' $ bash -c "command -v ls" /bin/ls
第二。 它可以给你没有输出。
$ command -v nvm nvm $ bash -c "command -v nvm" $ bash -c "nvm --help" bash: nvm: command not found
checkexists() { while [ -n "$1" ]; do [ -n "$(which "$1")" ] || echo "$1": command not found shift done }
我的设置为debian服务器。 我有一个问题,当多个包包含相同的名称。 例如apache2。 所以这是我的解决scheme。
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 }
我使用这个,因为它很容易:
if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists;else echo "not exists";fi
要么
if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists else echo "not exists" fi
它使用shell内置和程序回显状态标准输出,而另一方面,如果没有发现一个命令stderr,它只响应stderr状态。
如果你想检查一个程序是否存在,并且确实是一个程序,而不是一个bash内置命令 ,那么command
, type
和hash
都不适合testing,因为它们都返回0退出状态。
例如,有时间程序比时间内置命令提供更多的function。 要检查程序是否存在,我会build议使用如下例所示:
# 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
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
脚本
#!/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"'
结果
✘ [ABRT]: notacmd: command does not exist hits command 0 /usr/bin/bash Fin.
我必须检查git
是否作为部署CI服务器的一部分来安装。 我最后的bash脚本如下(Ubuntu服务器):
if ! builtin type -p git &>/dev/null; then sudo apt-get -y install git-core fi
希望这可以帮助别人!
我不能得到一个解决scheme的工作,但编辑一点后,我想出了这个。 哪些适合我:
dpkg --get-selections | grep -q linux-headers-$(uname -r) if [ $? -eq 1 ]; then apt-get install linux-headers-$(uname -r) fi