shell语法 - 从Bash脚本检查程序是否存在
shell编程 (20)
我将如何验证程序是否存在,以何种方式返回错误并退出,还是继续执行脚本?
它似乎应该很容易,但它一直困扰着我。
回答
兼容POSIX:
command -v <the_command>
对于bash
特定的环境:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
说明
避免which
。 它不仅仅是一个外部过程,你只需要做很少的事情(意思是像hash
, type
或command
这样的内建hash
便宜),你也可以依靠内建函数实际做你想做的事,而外部命令的影响可以从系统到系统很容易变化。
为什么在意?
- 许多操作系统有一个甚至没有设置退出状态 ,这意味着
if which foo
甚至不会在那里工作,并且总是会报告foo
存在,即使它不存在(注意一些POSIX shell似乎做了这也是hash
)。 - 许多操作系统会自定义和恶意的东西,比如改变输出甚至挂钩到包管理器。
所以,不要使用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; }
(小调 - 注意:有些人会建议2>&-
是相同的2>/dev/null
但更短 - 这是不真实的 2>&-
关闭FD 2,当它试图写入stderr时, ,这与成功写入并丢弃输出(和危险!)非常不同)
如果你的hash bang是/bin/sh
那么你应该关心POSIX的说法。 type
和hash
的退出代码不是由POSIX很好地定义的,并且在命令不存在的情况下hash
可以成功退出(尚未见到这种type
)。 command
的退出状态由POSIX很好地定义,所以一个可能是最安全的使用。
如果你的脚本使用bash
,POSIX规则就不再重要了,而且type
和hash
变得非常安全。 type
现在有一个-P
来搜索PATH
而hash
具有副作用,即命令的位置将被散列(以便下一次使用它时进行更快的查找),这通常是一件好事,因为您可能检查其存在为了实际使用它。
作为一个简单的例子,这里有一个运行gdate
的函数(如果存在的话),否则为date
:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "[email protected]"
else
date "[email protected]"
fi
}
which
命令可能有用。 男人哪
如果找到可执行文件,则返回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
好的事情是,它会发现如果可执行文件在运行的环境中可用 - 可以节省一些问题...
-亚当
检查多个依赖关系并将状态通知最终用户
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
调整为最大命令长度。 不是自动的,因为我没有看到一个非冗长的方式来做到这一点。
以下是检查命令是否存在于$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标准。 [ 错误报告 ] [ Standard ]
另外,如果您正在查找的命令已被定义为别名,则这将失败。
哈希变量有一个缺陷:在命令行中,例如可以输入
one_folder/process
执行进程。 为此,one_folder的父文件夹必须位于$ PATH中 。 但是当你试图散列这个命令时,它总是会成功的:
hash one_folder/process; echo $? # will always output '0'
在@ 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
如果你想检查一个程序是否存在并且确实是一个程序,而不是bash内置命令 ,那么command
, type
和hash
不适合测试,因为它们都返回0退出状态以用于内置命令。
例如,有时间程序提供比内置命令更多的功能。 要检查程序是否存在,我会建议使用如下例所示:
# 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
如果你检查程序是否存在,你可能会在晚些时候运行它。 为什么不尝试首先运行它?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
这是程序运行的更可靠的检查,而不仅仅是查看PATH目录和文件权限。
另外你可以从你的程序中获得一些有用的结果,比如它的版本。
当然缺点是一些程序可能很难启动,有些程序没有--version
选项来立即(并成功)退出。
如果没有可用的外部type
命令( share理所当然),我们可以使用符合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
至少在使用Bash 4.2.24(2)的Mac OS X 10.6.8上, command -v ls
与移动的/bin/ls-temp
不匹配。
它取决于您是否想知道它是否存在于$PATH
变量中的某个目录中,或者您是否知道它的绝对位置。 如果你想知道它是否在$PATH
变量中,请使用
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
在第一个示例中,重定向到/dev/null/
将抑制which
程序的输出。
尝试使用:
test -x filename
要么
[ -x filename ]
从条件表达式下的bash manpage:
-x file True if file exists and is executable.
我从来没有得到上述解决方案在我有权访问的框中工作。 首先,类型已经安装(做更多的事情)。 所以内建指令是必需的。 这个命令适用于我:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; 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内建和程序回显状态来输出标准输出,而不会发生任何错误,它只会响应标准错误。
我同意lhunath不鼓励使用which
,而且他的解决方案对于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 : http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html
注意: type
是POSIX兼容的,但是type -P
不是。
我找不到其中的一种解决方案,但编辑一下后,我提出了这个问题。 哪些适合我:
dpkg --get-selections | grep -q linux-headers-$(uname -r)
if [ $? -eq 1 ]; then
apt-get install linux-headers-$(uname -r)
fi
我有一个在我的.bashrc中定义的函数,这使得这更容易。
command_exists () {
type "$1" &> /dev/null ;
}
这是一个如何使用它的例子(来自我的.bash_profile
。)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
我第二次使用“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
脚本
#!/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=/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
checkexists() {
while [ -n "$1" ]; do
[ -n "$(which "$1")" ] || echo "$1": command not found
shift
done
}