如何从Bash脚本检查程序是否存在?

如何验证程序是否存在,以返回错误并退出或继续执行脚本的方式?

这似乎很容易,但它一直困扰着我。

1028917 次浏览

which命令可能有用。的人

如果找到可执行文件,则返回0,如果未找到或不可执行,则返回1:

NAME
which - locate a command
SYNOPSIS
which [-a] filename ...
DESCRIPTION
which returns the pathnames of the files which wouldbe executed in the current environment, had itsarguments been given as commands in a strictlyPOSIX-conformant shell. It does this by searchingthe PATH for executable files matching the namesof the arguments.
OPTIONS
-a     print all matching pathnames of each argument
EXIT STATUS
0      if all specified commands arefound and executable
1      if one or more specified commands is nonexistentor not executable
2      if an invalid option is specified

which的好处是它可以确定可执行文件是否在which运行的环境中可用-它节省了一些问题…

尝试使用:

test -x filename

[ -x filename ]

条件表达式下的Bash manpage:

 -x fileTrue if file exists and is executable.

这取决于您是否想知道它是否存在于$PATH变量中的某个目录中,或者您是否知道它的绝对位置。如果您想知道它是否在$PATH变量中,请使用

if which programname >/dev/null; thenecho existselseecho does not existfi

否则使用

if [ -x /path/to/programname ]; thenecho existselseecho does not existfi

第一个示例中重定向到/dev/null/会抑制which程序的输出。

如果可以,请使用Bash内置程序:

which programname

type -P programname

答案

POSIX兼容:

command -v <the_command>

示例使用:

if ! command -v <the_command> &> /dev/nullthenecho "<the_command> could not be found"exitfi

对于Bash特定环境:

hash <the_command> # For regular commands. Or...type <the_command> # To check built-ins and keywords

补充说明

避免which。它不仅是一个你启动的外部进程,做的很少(意味着像hashtypecommand这样的内置程序要便宜得多),你也可以依靠内置程序来实际做你想做的事情,而外部命令的效果很容易因系统而异。

为什么关心?

  • 许多操作系统都有which甚至没有设置退出状态,这意味着if which foo甚至不能在那里工作,总是会报告foo存在,即使它不存在(请注意,一些POSIX shell似乎也为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>&-关闭FD2,当它尝试写入stderr时,会导致程序中的错误,这与成功写入它并丢弃输出非常不同(而且很危险!))

typehash的退出代码在POSIX中没有很好地定义,当命令不存在时,hash被认为成功退出(还没有在type中看到这一点)。

如果你的脚本使用bash,POSIX规则不再重要,typehash都变得非常安全,type现在有一个-P来搜索PATHhash的副作用是命令的位置将被散列(以便下次使用它时更快地查找),这通常是一件好事,因为你可能会检查它的存在以便实际使用它。

作为一个简单的例子,这里有一个函数,如果存在,则运行gdate,否则运行date

gnudate() {if hash gdate 2>/dev/null; thengdate "$@"elsedate "$@"fi}

具有完整功能集的替代品

您可以使用脚本-通用来满足您的需求。

要检查是否安装了某些东西,您可以执行以下操作:

checkBin <the_command> || errorMessage "This tool requires <the_command>. Install it please, and then run this tool again."

我从来没有得到以前的答案来处理我可以访问的盒子。首先,type已经安装(做#1做的事情)。所以需要内置指令。这个命令对我有用:

if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi

对于那些感兴趣的人来说,如果你想检测安装的库,前面答案中的方法都不起作用。我想你要么需要物理检查路径(可能是头文件等),要么是这样的(如果你是基于Debian的发行版):

dpkg --status libdb-dev | grep -q not-installed
if [ $? -eq 0 ]; thenapt-get install libdb-devfi

如上所述,查询中的“0”答案意味着未安装软件包。这是“grep”的函数-“0”表示找到匹配项,“1”表示未找到匹配项。

我无法获得其中一个解决方案,但在对其进行了一点编辑后,我想到了这个。这对我有用:

dpkg --get-selections | grep -q linux-headers-$(uname -r)
if [ $? -eq 1 ]; thenapt-get install linux-headers-$(uname -r)fi

我在我的. bashrc中定义了一个函数,它使这变得更容易。

command_exists () {type "$1" &> /dev/null ;}

这是一个如何使用它的例子(来自我的.bash_profile)。

if command_exists mvim ; thenexport VISUAL="mvim --nofork"fi

我同意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。请参阅此处了解其规范:命令-执行一个简单的命令

注意:type是POSIX兼容的,但type -P不是。

我必须检查Git是否作为部署CI服务器的一部分安装。我的最终Bash脚本如下(Ubuntu服务器):

if ! builtin type -p git &>/dev/null; thensudo apt-get -y install git-corefi

为了模仿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!'; }
lstype lsenv -i type ls
cmd=lscmd=lsxenv -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }

要在Bash脚本中使用hash正如@lhunath所暗示的

hash foo &> /dev/nullif [ $? -eq 1 ]; thenecho >&2 "foo not found."fi

此脚本运行hash,然后检查最近命令的退出代码(存储在$?中的值)是否等于1。如果hash找不到foo,退出代码将是1。如果存在foo,退出代码将是0

&> /dev/nullhash重定向标准误差标准输出,使其不会出现在屏幕上,echo >&2将消息写入标准错误。

哈希变体有一个缺陷:例如,在命令行上,您可以输入

one_folder/process

执行进程。为此,one_folder的父文件夹必须在$PATH中。但是当您尝试散列此命令时,它总是会成功:

hash one_folder/process; echo $? # will always output '0'
checkexists() {while [ -n "$1" ]; do[ -n "$(which "$1")" ] || echo "$1": command not foundshiftdone}

如果您检查程序是否存在,您可能会在稍后运行它。为什么不首先尝试运行它呢?

if foo --version >/dev/null 2>&1; thenecho Foundelseecho Not foundfi

与仅仅查看PATH目录和文件权限相比,程序运行的检查更值得信赖。

此外,您可以从程序中获得一些有用的结果,例如其版本。

当然,缺点是有些程序启动起来可能很重,有些程序没有--version选项可以立即(成功)退出。

我支持使用“命令-v”。例如。像这样:

md=$(command -v mkdirhier) ; alias md=${md:=mkdir}  # bash
emacs="$(command -v emacs) -nw" || emacs=nanoalias e=$emacs[[ -z $(command -v jed) ]] && alias jed=$emacs

检查多个依赖项并通知最终用户状态

for cmd in latex pandoc; doprintf '%-10s' "$cmd"if hash "$cmd" 2>/dev/null; thenecho OKelseecho missingfidone

示例输出:

latex     OKpandoc    missing

10调整为最大命令长度。它不是自动的,因为我没有看到非冗长的POSIX方法来做到这一点:如何在Bash中对齐空格分隔表的列?

检查某些#0软件包是否与#1一起安装,否则安装它们

见:检查是否安装了apt-get包,如果没有安装,则安装Linux

之前提到过:如何从Bash脚本检查程序是否存在?

#0:适用于Z shell(Zsh)、Bash、Dash灰烬

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。

以下是检查$PATH中是否存在可执行命令的可移植方法:

[ -x "$(command -v foo)" ]

示例:

if ! [ -x "$(command -v git)" ]; thenecho 'Error: git is not installed.' >&2exit 1fi

需要可执行文件检查,因为如果在$PATH中找不到具有该名称的可执行文件,bash会返回一个不可执行文件。

另请注意,如果在#0前面存在与可执行文件同名的非可执行文件,则dash返回前者,即使后者将被执行。这是一个bug,违反了POSIX标准。[Bug Report][Standard
编辑:这似乎是固定的破折号0.5.11(Debian 11)。

此外,如果您要查找的命令已定义为别名,这将失败。

如果没有任何外部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=Ccmd="$1"cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`"[ $? != 0 ] && exit 1case "$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 lstypep builtintypep ls-temp

至少在Mac OS X v10.6.8(雪豹)上使用Bash 4.2.24(2)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" ]; thenecho "Package is available : $1"PACKAGE_INSTALL="1"elseecho "Package $1 is NOT available for install"echo  "We can not continue without this package..."echo  "Exitting now.."exit 0fi}function _package_install {_apt_available $1if [ "${PACKAGE_INSTALL}" = "1" ]; thenif [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; thenecho  "package is already_installed: $1"elseecho  "installing package : $1, please wait.."_apt_install $1sleep 0.5fifi}
function _package_install_no_recommends {_apt_available $1if [ "${PACKAGE_INSTALL}" = "1" ]; thenif [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; thenecho  "package is already_installed: $1"elseecho  "installing package : $1, please wait.."_apt_install_norecommends $1sleep 0.5fifi}

如果你们不能让答案中的东西起作用,并且正在把头发从你的背上拔出来,试着使用bash -c运行相同的命令。看看这个躯体精神错乱。这就是当你运行$(子命令)时真正发生的事情:

它可以给你完全不同的输出。

$ command -v lsalias ls='ls --color=auto'$ bash -c "command -v ls"/bin/ls

第二。它可以给你任何输出。

$ command -v nvmnvm$ bash -c "command -v nvm"$ bash -c "nvm --help"bash: nvm: command not found

我使用这个,因为它很容易:

if [ $(LANG=C type example 2>/dev/null | wc -l) = 1 ]; thenecho exists;elseecho "not exists";fi

if [ $(LANG=C type example 2>/dev/null | wc -l) = 1 ]; thenecho existselseecho "not exists"fi

它使用shell内置程序和程序的回显状态到标准输出,而不是标准错误。另一方面,如果找不到命令,它只将状态回显到标准错误。

扩展@lhunath和@GregV的答案,以下是希望轻松将该检查放入if语句中的人的代码:

exists(){command -v "$1" >/dev/null 2>&1}

以下是如何使用它:

if exists bash; thenecho 'Bash exists!'elseecho 'Your system does not have Bash'fi

我想说,由于悬挂alias,没有任何便携式和100%可靠的方法。例如:

alias john='ls --color'alias paul='george -F'alias george='ls -h'alias ringo=/

当然,只有最后一个是有问题的(没有冒犯Ringo!)。但从command -v的角度来看,所有这些都是有效的。

为了拒绝像ringo这样的悬空命令,我们必须解析shell内置的alias命令的输出并递归到它们中(command -v在这里并不优于alias)。

请注意,这样的解决方案将无条件拒绝alias ls='ls -F'

test() { command -v $1 | grep -qv alias }

如果您想检查程序是否存在实际上是一个程序,而不是Bash内置命令,那么commandtypehash不适合测试,因为它们都为内置命令返回0退出状态。

例如,有时间程序提供了比时间内置命令更多的功能。为了检查该程序是否存在,我建议使用which,如下例所示:

# First check if the time program existstimeProg=`which time`if [ "$timeProg" = "" ]thenecho "The time program does not exist on this system."exit 1fi
# 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 EXISTSecho "PROGRAM DOES NOT EXIST."exit 1                           # EXIT THE PROGRAM IF IT DOES NOTfi
# 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 notacmdexists bashhashbash -c 'printf "Fin.\n"'

结果

✘ [ABRT]: notacmd: command does not existhits    command0    /usr/bin/bashFin.

这里有很多选项,但我很惊讶没有快速的一行。这是我在脚本开头使用的:

[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }

这是基于这里选择的答案和另一个来源。

如果为要测试的<command>设置了POSIX_BUILTINS选项,命令-v可以正常工作,但如果没有,它可能会失败。(它对我来说已经工作了很多年,但我最近遇到了一个不起作用的地方。)

我发现以下内容更加可靠:

test -x "$(which <command>)"

因为它测试三件事:路径、存在和执行权限。

我会尝试调用程序,例如--version--help检查命令是成功还是失败

#0一起使用,如果找不到程序,脚本将退出,您将收到一条有意义的错误消息:

#!/bin/bashset -egit --version >> /dev/null

这将根据程序是否存在的位置来判断:

    if [ -x /usr/bin/yum ]; thenecho "This is Centos"fi

假设你已经以下安全外壳做法

set -eu -o pipefailshopt -s failglob
./dummy --version 2>&1 >/dev/null

这假设可以以这样一种方式调用命令,它(几乎)什么都不做,比如报告其版本或显示帮助。

如果找不到dummy命令,Bash会退出并出现以下错误…

./my-script: line 8: dummy: command not found

这比其他command -v(和类似的)答案更有用,也更少冗长,因为错误消息是自动生成的,并且还包含相关的行号。

我想回答同样的问题,但在Makefile中运行。

install:@if [[ ! -x "$(shell command -v ghead)" ]]; then \echo 'ghead does not exist. Please install it.'; \exit -1; \fi

它可以更简单,只是:

#!/usr/bin/env bashset -x
# if local program 'foo' returns 1 (doesn't exist) then...if ! type -P foo; thenecho 'crap, no foo'elseecho 'sweet, we have foo!'fi

foo更改为vi以触发另一个条件。

迟到的回答,但这就是我最终所做的。

我只是检查我执行的命令是否返回错误代码。如果它返回0,则表示程序已安装。此外,您可以使用它来检查脚本的输出。以这个脚本为例。

foo.sh

#!/bin/bashecho "hello world"exit 1 # throw some error code

示例:

# outputs something bad... and exitsbash foo.sh $? -eq 0 || echo "something bad happened. not installed" ; exit 1
# does NOT outputs nothing nor exits because dotnet is installed on my machinedotnet --version $? -eq 0 || echo "something bad happened. not installed" ; exit 1

基本上,这所做的就是检查命令运行的退出代码。这个问题上最被接受的答案将返回true,即使命令退出代码不是0。

#0而已,但对zsh脚本非常有用(例如,在编写完成脚本时):

zsh/parameter模块允许访问内部commands哈希表等。来自man zshmodules

THE ZSH/PARAMETER MODULEThe zsh/parameter module gives access to some of the internal hash  ta‐bles used by the shell by defining some special parameters.

[...]
commandsThis  array gives access to the command hash table. The keys arethe names of external commands, the values are the pathnames  ofthe  files  that would be executed when the command would be in‐voked. Setting a key in this array defines a new entry  in  thistable  in the same way as with the hash builtin. Unsetting a keyas in `unset "commands[foo]"' removes the entry  for  the  givenkey from the command hash table.

虽然它是一个可加载的模块,但它似乎是默认加载的,只要zsh不与--emulate一起使用。

例子:

martin@martin ~ % echo $commands[zsh]/usr/bin/zsh

要快速检查某个命令是否可用,只需检查哈希中是否存在键:

if (( ${+commands[zsh]} ))thenecho "zsh is available"fi

请注意,哈希将包含$PATH文件夹中的任何文件,无论它们是否可执行。为了绝对确定,您必须为此花费stat调用:

if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]thenecho "zsh is available"fi
#!/bin/basha=${apt-cache show program}if [[ $a == 0 ]]thenecho "the program doesn't exist"elseecho "the program exists"fi

#程序不是文字,您可以将其更改为要检查的程序名称

简单的回答,对于一个简单的问题:

列出终端上的所有命令和程序:

compgen -ac

检查您感兴趣的一个命令或程序:

compgen -ac | grep searchstr

可能的选择:

-a means Names of alias-b means Names of shell builtins-c means Names of all commands-d means Names of directory-e means Names of exported shell variables-f means Names of file and functions-g means Names of groups-j means Names of job-k means Names of Shell reserved words-s means Names of service-u means Names of userAlias names-v means Names of shell variables

有关更多详细信息,请查看手册页: