我将如何以返回错误并退出或继续执行脚本的方式来验证程序是否存在?
看起来应该很容易,但是一直让我很沮丧。
我将如何以返回错误并退出或继续执行脚本的方式来验证程序是否存在?
看起来应该很容易,但是一直让我很沮丧。
Answers:
兼容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
更便宜的内建函数),还可以依靠内建函数来实际执行所需的操作,而外部命令的效果很容易因系统之间。
为什么要在乎?
which
这甚至不设置退出状态,这意味着if which foo
甚至不会在那里工作,并会始终报告foo
存在,即使它没有(注意,一些POSIX炮弹似乎对这样做hash
太)。which
做一些自定义和邪恶的事情,例如更改输出甚至挂接到包管理器。因此,请勿使用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时在程序中导致错误,这与成功写入并丢弃输出有很大不同。 (很危险!)
如果您的哈希爆炸是,/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 "$@"
else
date "$@"
fi
}
2>&-
(“ close输出文件描述符2”,其为stderr)具有与2> /dev/null
; 相同的结果;2)>&2
是的快捷方式1>&2
,您可能将其识别为“将stdout重定向到stderr”。有关更多信息,请参见Advanced Bash脚本指南的I / O重定向页面。
while read element ; do .. done <<< $(echo ${ArrayVar[*]})
,for word in $(fgrep -l $ORIGINAL *.txt)
,ls -l "$directory" | sed 1d
,{{对于在seq $BEGIN $END
}},......许多人都试图联系作者,并提出改进,但它没有wiki和请求都落在聋子的耳朵。
2>&-
是不一样的2>/dev/null
。前者关闭文件描述符,而后者仅将其重定向到/dev/null
。您可能不会看到错误,因为程序会尝试在stderr上通知您stderr已关闭。
以下是检查命令是否存在$PATH
以及是否可执行的可移植方式:
[ -x "$(command -v foo)" ]
例:
if ! [ -x "$(command -v git)" ]; then
echo 'Error: git is not installed.' >&2
exit 1
fi
需要执行可执行检查,因为如果在中找不到具有该名称的可执行文件,则bash返回一个非可执行文件$PATH
。
还要注意,如果在较早的版本中存在与可执行文件同名的不可执行文件,则$PATH
dash会返回前者,即使后者将被执行。这是一个错误,并且违反了POSIX标准。[ 错误报告 ] [ 标准 ]
此外,如果您要查找的命令已定义为别名,则此操作将失败。
command -v
甚至会为非可执行文件生成路径吗?也就是说,-x真的必要吗?
-x
测试文件是否可执行,这就是问题所在。
command
本身会对其可执行性进行测试-是吗?
$PATH
执行命令时都会跳过不可执行的文件。但是,的行为command -v
非常不一致。在破折号中,它将返回中的第一个匹配文件$PATH
,无论它是否可执行。在bash中,它返回中的第一个可执行文件匹配项$PATH
,但是如果不存在,则可以返回一个非可执行文件。并且在zsh中,它将永远不会返回不可执行的文件。
dash
是三分之一中唯一不符合POSIX标准的产品;[ -x "$(command -v COMMANDNAME)"]
将在其他两个中工作。似乎已经报告了该错误,但尚未得到任何答复:bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264
我同意不鼓励使用它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。有关其规格,请参见此处:命令-执行一个简单的命令
注意:type
兼容POSIX,但type -P
不兼容。
exit 1;
杀死xterm(如果从那里调用)。
&>/dev/null
。但是,我同意你的意见,真正重要的是可移植性,我已经使用标准的sh redirect相应地编辑了答案>/dev/null 2>&1
。
我在.bashrc中定义了一个函数,可以使此操作更加容易。
command_exists () {
type "$1" &> /dev/null ;
}
这是一个用法示例(来自我的.bash_profile
。)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
&>
办?
&>
重定向标准输出和标准错误一起。
&>
在您的Bash版本中可能不可用。Marcello的代码应该可以正常工作;它做同样的事情。
这取决于您是否想知道它是否存在于$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
程序的输出。
扩展@lhunath和@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
command
即使对于别名也可以成功,这可能有点违反直觉。与将其移动到脚本中相比,检查交互式外壳中是否存在将得到不同的结果。
shopt -u expand_aliases
忽略/隐藏别名(如alias ls='ls -F'
另一个答案中提到的),shopt -s expand_aliases
并通过解析了它们command -v
。因此,也许它应该在检查之前设置,而在检查之后取消设置,尽管如果您未捕获并显式返回命令调用的输出,则可能会影响函数的返回值。
尝试使用:
test -x filename
要么
[ -x filename ]
在条件表达式下的Bash联机帮助页中:
-x file True if file exists and is executable.
要使用hash
,如@lhunath表明,在bash脚本:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
该脚本将运行hash
,然后检查最新命令(存储在中的值)的退出代码$?
是否等于1
。如果hash
找不到foo
,则退出代码为1
。如果foo
存在,则退出代码为0
。
&> /dev/null
重定向标准错误和来自的标准输出,hash
以便它不会出现在屏幕上,echo >&2
并将消息写入标准错误。
if hash foo &> /dev/null; then ...
呢?
我从来没有得到以前的答案才能在我可以访问的盒子上工作。对于一个,type
已经安装(正在做什么more
)。因此需要内置指令。此命令对我有用:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
if
语法的一部分,只需使用即可if builtin type -p vim; then ...
。反引号实际上是古老且过时的语法,$()
即使sh
在所有现代系统上也都支持。
检查多个依赖项并将状态通知最终用户
for cmd in latex pandoc; do
printf '%-10s' "$cmd"
if hash "$cmd" 2>/dev/null; then
echo OK
else
echo missing
fi
done
样本输出:
latex OK
pandoc missing
将调整10
为最大命令长度。它不是自动的,因为我没有看到一种非冗长的POSIX方法:
如何在Bash中对齐以空格分隔的表的列?
检查是否apt
安装了某些软件包,dpkg -s
否则请安装。
请参阅:检查是否已安装apt-get软件包,如果不在Linux上,请先安装它
以前在以下地方提到过:如何检查Bash脚本中是否存在程序?
column -t
(util-linux的一部分)。
如果可以,请使用Bash内置函数:
which programname
...
type -P programname
which
不是内置的Bash。
-P
不是POSIX。为什么是type -P
首选?
-v
如果将POSIX_BUILTINS选项设置为<command>
要测试,则该命令可以正常工作,但如果未设置则失败。(它对我有用了很多年,但是最近我遇到了一个不起作用的地方。)
我发现以下内容更加可靠:
test -x $(which <command>)
由于它测试了三件事:路径,存在和执行权限。
test -x $(which ls)
返回0,一样test -x $(which sudo)
,即使ls
已安装并运行的和sudo
甚至没有,我在跑泊坞窗容器内安装。
test -x "$(which <command>)"
ls
是别名?我认为如果命令具有参数,它将不会起作用。
对于那些感兴趣的人,如果您想检测已安装的库,则先前答案中的任何方法均无效。我想象您要么实际检查路径(可能检查头文件等),要么像这样(如果您使用的是基于Debian的发行版):
dpkg --status libdb-dev | grep -q not-installed
if [ $? -eq 0 ]; then
apt-get install libdb-dev
fi
从上面可以看到,查询中的“ 0”答案表示未安装该软件包。这是“ grep”的功能-“ 0”表示找到了匹配项,“ 1”表示没有找到匹配项。
cmd; if [ $? -eq 0 ]; then
应重构为if cmd; then
dpkg
或apt
我想说由于alias
es 悬空,没有任何可移植且100%可靠的方式。例如:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
当然,只有最后一个是有问题的(林戈没有冒犯!)。但从alias
的观点来看,所有这些都是有效的command -v
。
为了拒绝像这样的悬空命令ringo
,我们必须解析shell内置alias
命令的输出并将其递归到其中(command -v
这不是alias
这里的优点。)没有任何可移植的解决方案,甚至没有Bash-具体的解决方案很繁琐。
请注意,这样的解决方案将无条件拒绝alias ls='ls -F'
:
test() { command -v $1 | grep -qv alias }
shopt -u expand_aliases
忽略/隐藏这些别名,shopt -s expand_aliases
并通过显示它们command -v
。
该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 executable
2 if an invalid option is specified
有趣的which
是,它可以确定可执行文件在which
运行环境中是否可用-它可以节省一些问题...
我的Debian服务器设置:
当多个软件包包含相同名称时,我遇到了问题。
例如apache2
。所以这是我的解决方案:
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
}
如果您无法在这里得到答案的答案,并且无法将头发拉出后背,请尝试使用来运行相同的命令bash -c
。只要看看这个so妄。这是在运行$(子命令)时真正发生的情况:
第一。它可以为您提供完全不同的输出。
$ 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
.bashrc
有一个[ -z "$PS1" ] && return
由前缀# If not running interactively, don't do anything
,所以我想这是一个原因,在非交互模式下的.bashrc甚至明确采购于事无补。可以通过使用ss64.com/bash/source.html点运算符调用脚本来解决此问题,. ./script.sh
但这不是人们想记住的每次键入的内容。
这将根据位置判断程序是否存在:
if [ -x /usr/bin/yum ]; then
echo "This is Centos"
fi
我必须检查是否在部署CI服务器时安装了Git 。我最后的Bash脚本如下(Ubuntu服务器):
if ! builtin type -p git &>/dev/null; then
sudo apt-get -y install git-core
fi
sudo
:没有条件,它将始终停止并要求输入密码(除非您最近进行了sudo操作)。顺便说一句,这样做可能很有用,sudo -p "Type your password to install missing git-core: "
这样提示不会突然出现。
为了模仿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; }
type
似乎是builtin
在大多数炮弹所以这不行,因为env
使用execvp
运行command
所以command
不可能是一个builtin
(和builtin
将始终是相同的环境中运行)。这种失败对我来说在bash
,ksh93
,zsh
,busybox [a]sh
和dash
所有这些都提供type
一个shell内建。
如果没有任何外部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
至少在使用Bash 4.2.24(2)的Mac OS X v10.6.8(Snow Leopard)command -v ls
上,与moved不匹配/bin/ls-temp
。
如果你想检查一个程序中存在并且是一个真正的程序,而不是一击内置命令,然后command
,type
和hash
不适合在内置命令测试,因为它们都返回0退出状态的。
例如,有一个时间程序,它提供了比内置时间命令更多的功能。要检查程序是否存在,建议which
在下面的示例中使用:
# 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
脚本
#!/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.
假设您已经遵循安全的shell做法:
set -eu -o pipefail
shopt -s failglob
./dummy --version 2>&1 >/dev/null
假定可以以某种方式(几乎)不执行任何命令来调用该命令,例如报告其版本或显示帮助。
如果dummy
找不到该命令,则Bash退出并显示以下错误...
./my-script: line 8: dummy: command not found
command -v
由于错误消息是自动生成的,并且还包含相关的行号,因此它比其他(和类似的)答案更有用且更省时。
dummy
存在测试失败的分支是无法控制的脚本的作者。另外,它也没有解释它是如何工作的以及它如何更改执行环境的设置。
which
为此返回true。type
如果不带参数,则保留字和shell内置函数将另外返回true。如果“程序”的意思是“可执行文件$PATH
”,那么请参见此答案。