我将如何验证程序是否存在,以返回错误并退出或继续执行脚本的方式?
看起来应该很容易,但它一直困扰着我。
which
为这些返回 true。不带参数的 type
将另外为保留字和 shell 内置返回 true。如果“程序”的意思是“在 $PATH
中可执行”,则参见 this answer。
which
的哪个实现;它不是由 Bash 提供的,而是由例如 Debian 的 debianutils 提供的。
回答
POSIX 兼容:
command -v <the_command>
示例使用:
if ! command -v <the_command> &> /dev/null
then
echo "<the_command> could not be found"
exit
fi
对于 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 shell 似乎可以这也是哈希)。
许多操作系统都会做一些自定义和邪恶的事情,比如改变输出,甚至挂钩到包管理器。
所以,不要使用 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
}
具有完整功能集的替代方案
您可以使用 scripts-common 来满足您的需求。
要检查是否安装了某些东西,您可以执行以下操作:
checkBin <the_command> || errorMessage "This tool requires <the_command>. Install it please, and then run this tool again."
以下是检查命令是否存在于 $PATH
和 中的可移植方法:
[ -x "$(command -v foo)" ]
例子:
if ! [ -x "$(command -v git)" ]; then
echo 'Error: git is not installed.' >&2
exit 1
fi
需要执行可执行检查,因为如果在 $PATH
中找不到具有该名称的可执行文件,bash 将返回一个不可执行的文件。
另请注意,如果在 $PATH
的前面存在与可执行文件同名的不可执行文件,则 dash 返回前者,即使后者会被执行。这是一个错误,违反了 POSIX 标准。 [Bug report] [Standard]
此外,如果您要查找的命令已被定义为别名,这将失败。
我同意 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。有关其规范,请参见此处:command - execute a simple command
注意:type
符合 POSIX,但 type -P
不符合。
exit 1;
如果从那里调用,则会杀死一个 xterm。
&>/dev/null
。但是,我同意你的看法,真正重要的是可移植性,我已经相应地编辑了我的答案,现在使用标准 sh 重定向 >/dev/null 2>&1
。
这取决于您是否想知道它是否存在于 $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
程序的输出。
我在我的 .bashrc 中定义了一个函数,使这更容易。
command_exists () {
type "$1" &> /dev/null ;
}
这是一个如何使用它的示例(来自我的 .bash_profile
。)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
&>
有什么作用?
&>
redirects both stdout and stderr 在一起。
&>
在您的 Bash 版本中可能不可用。 Marcello 的代码应该可以正常工作;它做同样的事情。
扩展@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
也会成功,这可能有点违反直觉。在交互式 shell 中检查是否存在与将其移动到脚本时会给出不同的结果。
shopt -u expand_aliases
忽略/隐藏别名(如另一个答案中提到的 alias ls='ls -F'
)和 shopt -s expand_aliases
通过 command -v
解决它们。因此,也许它应该在检查之前设置并在之后取消设置,尽管如果您不明确捕获并返回命令调用的输出,它可能会影响函数返回值。
if exists conda; then
上不起作用,即使安装了 anaconda 并在终端中输入 conda
时返回:usage: conda [-h] [-V] command...
? (请注意,我已验证您的答案适用于 Ubuntu 20 操作系统上的 if exists bash; then
。)
which conda
command -v
还显示内置函数、函数甚至关键字,并为它们返回 0!
尝试使用:
test -x filename
或者
[ -x filename ]
在 Conditional Expressions 下的 Bash 手册页中:
-x 文件 如果文件存在且可执行,则为真。
要在 Bash 脚本中使用 hash
、as @lhunath suggests:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
此脚本运行 hash
,然后检查最近命令的退出代码(存储在 $?
中的值)是否等于 1
。如果 hash
未找到 foo
,则退出代码将为 1
。如果存在 foo
,则退出代码将为 0
。
&> /dev/null
从 hash
重定向 standard error 和 standard output,使其不会出现在屏幕上,并且 echo >&2
将消息写入标准错误。
if hash foo &> /dev/null; then ...
?
set -e
,则永远不会运行 if...
部分。
这里有很多选择,但我很惊讶没有快速的单线。这是我在脚本开始时使用的:
[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }
[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }
这是基于此处选择的答案和另一个来源。
[[
和命令替换在这里没有用。如果未找到 mvn
,只需 command -v mvn || exit
将 exit
;但是有了这个固定,这基本上只是重复了接受的答案。
如果为 <command>
设置了 POSIX_BUILTINS 选项以进行测试,则命令 -v
可以正常工作,但如果没有,它可能会失败。 (它已经为我工作了多年,但我最近遇到了一个它不起作用的地方。)
我发现以下内容更防故障:
test -x "$(which <command>)"
因为它测试三件事:路径、存在和执行权限。
test -x $(which ls)
与 test -x $(which sudo)
一样返回 0,即使 ls
已安装且可运行,并且 sudo
甚至未安装在我正在运行的 docker 容器中。
test -x "$(which <command>)"
ls
是别名?如果命令有参数,我认为它不会起作用。
$ test -x $(which absent_cmd)
返回 test: too many arguments
,而 test -x "$(which absent_cmd)"
已正确解析并导致退出代码 1。
test: too many arguments
建议 which absent_cmd
返回多字错误消息。可能存在与消息同名的可执行文件。 😁 以这种方式使用 which
是个坏主意。
如果您检查程序是否存在,您可能会在以后运行它。为什么不首先尝试运行它?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
与仅查看 PATH 目录和文件权限相比,检查程序是否运行更可靠。
另外,您可以从您的程序中获得一些有用的结果,例如它的版本。
当然,缺点是有些程序启动起来很繁重,有些程序没有立即(并成功)退出的 --version
选项。
检查多个依赖项并将状态通知最终用户
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 方式来做到这一点:How can I align the columns of a space separated table in Bash?
检查某些 apt
软件包是否与 dpkg -s
一起安装,否则安装它们。
请参阅:Check if an apt-get package is installed and then install it if it's not on Linux
之前在以下位置提到过:How can I check if a program exists from a Bash script?
column -t
(util-linux 的一部分)。
我从来没有得到以前的答案来处理我可以访问的盒子。一方面,已安装 type
(执行 more
的操作)。所以需要内置指令。这个命令对我有用:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
if
语法的一部分,只需使用 if builtin type -p vim; then ...
。并且反引号是非常古老且已弃用的语法,在所有现代系统上,甚至 sh
都支持 $()
。
builtin type -p if
不输出任何内容并返回 0 无论 if
是否存在。
if builtin type -p if
或 if [ `builtin type -p if` ]
没有区别,除了后者不必要地冗长并使用不推荐使用的语法。
if
的程序是否存在时,它们都无用。
hash foo 2>/dev/null
:适用于 Z shell (Zsh)、Bash、Dash 和 ash。
type -p foo
:它似乎适用于 Z shell、Bash 和 ash (BusyBox),但不适用于 Dash(它将 -p
解释为参数)。
command -v foo
:适用于 Z shell、Bash、Dash,但不适用于 ash (BusyBox) (-ash: command: not found
)。
另请注意,builtin
不适用于 ash 和 Dash。
command -v foo
上工作于 busybox sh
(BusyBox v1.22.1 (Debian 1:1.22.0-19+b3) 内置 shell (ash))。当 foo 未找到时,它会以 127 正确失败,如果找到则打印路径。
type -p if
不起作用。
我想回答同样的问题,但要在 Makefile 中运行。
install:
@if [[ ! -x "$(shell command -v ghead)" ]]; then \
echo 'ghead does not exist. Please install it.'; \
exit -1; \
fi
[[
,因此依赖 Bash 作为 shell。
$(shell ...)
几乎总是错误的。接受的答案也可以在 Makefile
中正常工作。 exit -1
没有明确定义,条件无论如何也没用。
which
命令可能有用。 man 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
运行的环境中是否可用 - 它节省了一些问题...
如果可以,请使用 Bash 内置函数:
which programname
...
type -P programname
which
不是 Bash 内置。
-P
不是 POSIX。为什么首选 type -P
?
它可能更简单,只是:
#!/usr/bin/env bash
set -x
# if local program 'foo' returns 1 (doesn't exist) then...
if ! type -P foo; then
echo 'crap, no foo'
else
echo 'sweet, we have foo!'
fi
将 foo
更改为 vi
以触发另一个条件。
set -x
)
set -x
是 bash 调试器。它有助于说明当人们只是复制/粘贴时发生的事情->从这样的网站执行解决方案;就这样。如果您已完成调试,请注释/删除该行。
仅zsh
,但对 zsh
脚本非常有用(例如,在编写完成脚本时):
zsh/parameter
模块提供对内部 commands
哈希表等的访问权限。从 man zshmodules
:
THE ZSH/PARAMETER MODULE
The zsh/parameter module gives access to some of the internal hash ta‐
bles used by the shell by defining some special parameters.
[...]
commands
This array gives access to the command hash table. The keys are
the names of external commands, the values are the pathnames of
the files that would be executed when the command would be in‐
voked. Setting a key in this array defines a new entry in this
table in the same way as with the hash builtin. Unsetting a key
as in `unset "commands[foo]"' removes the entry for the given
key from the command hash table.
虽然它是一个可加载的模块,但它似乎是默认加载的,只要 zsh
不与 --emulate
一起使用。
例子:
martin@martin ~ % echo $commands[zsh]
/usr/bin/zsh
要快速检查某个命令是否可用,只需检查该键是否存在于哈希中:
if (( ${+commands[zsh]} ))
then
echo "zsh is available"
fi
请注意,哈希将包含 $PATH
文件夹中的任何文件,无论它们是否可执行。为了绝对确定,您必须为此花费 stat
调用:
if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]
then
echo "zsh is available"
fi
对于那些感兴趣的人,如果您希望检测已安装的库,则以前的答案中的任何方法都不起作用。我想你要么在物理上检查路径(可能是头文件等),要么像这样(如果你在基于 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
安装的库
这将根据位置告诉程序是否存在:
if [ -x /usr/bin/yum ]; then
echo "This is Centos"
fi
我会说由于悬而未决alias
,没有任何便携且 100% 可靠的方法。例如:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
当然,只有最后一个是有问题的(没有冒犯林戈!)。但从 command -v
的角度来看,它们都是有效的alias
。
为了拒绝像 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
显示它们。
如果你们无法让答案中的内容正常工作并且正在从背后拉扯头发,请尝试使用 bash -c
运行相同的命令。看看这个梦魇谵妄。这是运行 $(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
.bashrc
在 # If not running interactively, don't do anything
前面有一个 [ -z "$PS1" ] && return
,所以我想这就是为什么即使在非交互模式下显式采购 bashrc 也无济于事的原因。该问题可以通过调用带有 ss64.com/bash/source.html 点运算符 . ./script.sh
的脚本来解决,但这不是人们希望每次都输入的内容。
hash-variant 有一个缺陷:例如,在命令行中,您可以输入
one_folder/process
执行流程。为此,one_folder 的父文件夹必须在 $PATH 中。但是当你尝试散列这个命令时,它总是会成功:
hash one_folder/process; echo $? # will always output '0'
$PATH
中”——这是完全不准确的。试试看。为此,one_folder 必须位于 当前目录 中。
我第二次使用“命令-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
我必须检查是否在部署我们的 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
在大多数 shell 中似乎是 builtin
,所以这不起作用,因为 env
使用 execvp
运行 command
,所以 command
不能是 builtin
(并且 builtin
将始终在同一环境中运行)。这对我来说在 bash
、ksh93
、zsh
、busybox [a]sh
和 dash
中都失败了,它们都将 type
作为 shell 内置。
如果没有任何可用的外部 type
命令(理所当然的 here),我们可以使用符合 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
与移动的 /bin/ls-temp
不匹配。
我的 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 内置命令,那么 command
、type
和 hash
不适合测试,因为它们所有内置命令都返回 0 退出状态。
例如,time 程序提供了比 time 内置命令更多的功能。要检查程序是否存在,我建议使用 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
hash
告诉错误存在以给定参数的名称存在的可执行文件吗?
&>/dev/null
和>&2
部分的用途吗?没有它们,这条线似乎也能正常工作。谢谢。2>&-
("close output file descriptor 2", which is stderr) 与2> /dev/null
具有相同的结果; 2)>&2
是1>&2
的快捷方式,您可以将其识别为“将标准输出重定向到标准错误”。有关详细信息,请参阅 Advanced Bash Scripting Guide i/o redirection page。