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

gre*_*egh 2032 bash

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

看起来应该很容易,但它一直在困扰我.

lhu*_*ath 2813

回答

POSIX兼容:

command -v <the_command>
Run Code Online (Sandbox Code Playgroud)

对于bash特定环境:

hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
Run Code Online (Sandbox Code Playgroud)

说明

避免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; }
Run Code Online (Sandbox Code Playgroud)

(次要注意:有些人会建议2>&-相同2>/dev/null但更短 - 这是不真实的. 2>&-关闭FD 2会导致程序在尝试写入stderr时出错,这与成功写入并丢弃输出有很大不同(又危险!))

如果你的哈希爆炸是/bin/sh那么你应该关心POSIX所说的. typehash的退出代码不是非常好由POSIX定义的,并且hash被认为是当所述命令不存在成功地退出(未与看到这种type尚未). commandPOSIX很好地定义了退出状态,因此最安全的可能是最安全的.

如果你的脚本使用bash虽然POSIX规则真的不重要了两者typehash成为完全安全的使用. type现在有一个-P只搜索PATH并且hash有副作用的命令的位置将被哈希(为了下次你使用它时更快的查找),这通常是一件好事,因为你可能检查它的存在,以便实际使用它.

举个简单的例子,这里有一个运行的函数gdate,否则date:

gnudate() {
    if hash gdate 2>/dev/null; then
        gdate "$@"
    else
        date "$@"
    fi
}
Run Code Online (Sandbox Code Playgroud)

  • 对于那些不熟悉bash中"高级"i/o重定向的人:1)`2>& - `*("关闭输出文件描述符2",这是stderr)*与`2>/dev/null具有相同的结果; 2)`>&2`是`1>&2`的快捷方式,您可以将其识别为"将stdout重定向到stderr".有关详细信息,请参阅[Advanced Bash Scripting Guide i/o重定向页面](http://tldp.org/LDP/abs/html/io-redirection.html). (126认同)
  • @mikewaters`2>& - `**不是**与'2>/dev/null`相同.前者关闭文件描述符,而后者只是将其重定向到`/ dev/null`.您可能看不到错误,因为程序试图通过stderr通知您stderr已关闭. (48认同)
  • @Geert:当'foo'不存在时,&>/dev/null部分隐藏消息'type'.echo上的>&2确保将错误消息发送到标准错误而不是标准输出; 因为这是惯例.它们都出现在您的终端上,但标准错误肯定是错误消息和意外警告的首选输出. (33认同)
  • @mikewaters ABS看起来相当先进,描述了广泛的bash和非bash CLI功能,但它在很多方面都非常疏忽,并没有遵循良好的做法.在评论中我没有足够的空间来写一篇文章; 但我可以粘贴一些随机的BAD代码示例:`read元素; do .. done <<< $(echo $ {ArrayVar [*]})`,`for $ in $(fgrep -l $ ORIGINAL*.txt)`,`ls -l"$ directory"| sed 1d`,{{for a seq $ BEGIN $ END`}},...许多人试图联系作者并提出改进建议,但这并不是维基,而且请求已被置若罔闻. (8认同)
  • -P标志在'sh'中不起作用,例如http://stackoverflow.com/questions/2608688/check-if-a-program-exists-in-bash (5认同)
  • 如果没有foo,你的`exit 1`会杀死xterm.非常感谢你! (4认同)
  • 在类似POSIX.1-2004的shell中,保证`type`,`command -v`或`hash`都不存在.请参阅http://stackoverflow.com/q/34572700/1175080和http://stackoverflow.com/q/34572886/1175080. (4认同)
  • 如果`command`最安全地使用POSIX约束,即使我使用bash,为什么还要使用`type`或`hash`? (3认同)
  • @ b.long花括号用于组合`echo`和`exit`命令,以便它们都作为`command`的退出代码运行.如果省略花括号,只有`echo`命令将有条件地运行,并且`exit`命令将始终运行,无论命令是否存在.请参阅http://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Grouping_Statements (3认同)
  • 值得注意的是,如果`foo`是shell别名,内置函数或函数,那么`foo`(如果存在的话)很可能会失败,而上面的POSIX替代品会产生成功.因此,例如``foo && foo`比`命令-v foo>/dev/null 2>&1 && foo`更接近普通`命令foo`. (3认同)
  • 我对所有这些方法进行了效率比较:https://www.topbug.ne​​t/blog/2016/10/11/speed-test-check-the-existence-of-a-command-in-bash-and- zsh的/ (3认同)
  • @michuelnik 我解释了 `hash` 如何导致命令名称被添加到哈希表中,并且 `type` 有选项可以做 `command` 不能做的事情。总而言之,这是一个偏好问题,`type` 和 `hash` 解决方案要短一些。另外,不要混淆`bash`和`sh`——当你的解释器是bash时,使用POSIX语法的收益为零。你根本没有便携性。你的 hashbang 是 `bash`,你将永远需要它。你也可以使用普通的 `bash` 语法。就像在使用 C++ 编译器进行编译时将自己限制在 C 语法上毫无意义一样。 (2认同)
  • @mikewaters我不认为链接到LDP的ABS是非常负责任的.bash专家普遍认为它对bash学生有不良影响.相反,我建议以下内容来了解​​bash中的I/O:http://mywiki.wooledge.org/BashSheet#Streams (2认同)
  • @lhunath 谢谢。我试图编辑我的评论没有运气 - 太旧了。catonmat 在 http://www.catonmat.net/download/bash-redirections-cheat-sheet.pdf 有一个很好的 pdf (2认同)
  • @CMCDragonkai在这种情况下,你通常根本不会进行测试而只是运行命令.您可以在命令前加上!(和空格)否定其退出状态,或者你可以用`&&'替换`||`.参见`man bash`或http://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Control_Operators_.28.26.26_and_.7C.7C.29 (2认同)
  • @lhunah这个页面的标题问:"如何检查程序是否存在于bash脚本中?",唯一真正的方法是测试文件是否存在,例如:[ - e/bin/ls ],所以:你需要文件的正确位置而不是抽象词(ls).更多的额外选择.此外,如果将ls程序更改为/ bin/ls-temp之类的命令,则"命令-v ls"仍然报告相同,而"which"确实报告"null"(空). (2认同)
  • 替代品不是黑客.替代方案100%良好且便携,"哪个"没有获得单一优势,并且存在许多缺点.`which`实际上并不告诉你在运行命令时你的shell会做什么.`哪个'是谎言.只有你的shell可以告诉你命令名在那个shell中解析的内容.只有内置的shell才能提供您需要的信息.不要再是传播错误信息的毒药. (2认同)
  • @mkobit Bash的文档在`man bash`中.可以使用`help command`获得简短的摘要 (2认同)
  • @EvanCarroll `test -x` 只能告诉你一个文件是否可执行。这要求您已经知道文件的确切路径。如果您完全了解程序文件的唯一路径,则可以使用`test`,但它在回答“我可以执行将运行程序的命令”这个问题时不是很有用,为此,您需要执行命令名称解析或 PATH 搜索,而测试不会这样做。 (2认同)

nyu*_*a7h 485

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

[ -x "$(command -v foo)" ]
Run Code Online (Sandbox Code Playgroud)

例:

if ! [ -x "$(command -v git)" ]; then
  echo 'Error: git is not installed.' >&2
  exit 1
fi
Run Code Online (Sandbox Code Playgroud)

需要执行可执行检查,因为如果找不到具有该名称的可执行文件,bash将返回非可执行文件$PATH.

另请注意,如果先前存在与可执行文件同名的非可执行文件,则$PATHdash会返回前者,即使后者将被执行.这是一个错误,违反了POSIX标准.[ 错误报告 ] [ 标准 ]

此外,如果您要查找的命令已被定义为别名,则此操作将失败.

  • @einpoklum是的,这是必要的.实际上,即使这种解决方案也可能在一个边缘情况下破裂.谢谢你引起我的注意.执行命令时,dash,bash和zsh都跳过`$ PATH`中的非可执行文件.但是,`command -v`的行为非常不一致.在dash中,它返回`$ PATH`中的第一个匹配文件,无论它是否可执行.在bash中,它返回`$ PATH`中的第一个可执行匹配,但如果没有,则它可以返回一个不可执行的文件.在zsh中,它永远不会返回非可执行文件. (11认同)
  • 即使对于非可执行文件,`command -v`也会产生路径吗?也就是说,-x真的有必要吗? (4认同)
  • @einpoklum`-x`测试文件是否可执行,这就是问题所在. (4认同)
  • 据我所知,"破折号"是这三种中唯一符合非POSIX标准的; `[-x"$(命令-v COMMANDNAME)"]`将在另外两个中起作用.看起来这个bug已经被报道但尚未得到任何回复:https://bugs.debian.org/cgi-bin/bugreport.cgi?video = 874264 (4认同)
  • 在某些操作系统上,空字符串被视为等同于 `.` ,因此如果用户对当前目录具有扫描权限,则 `[ -x '' ]` 可能返回 true ;这会导致误报。(幸运的是,这只会影响一些技术上不兼容 POSIX 的旧系统。)更具弹性的是 `thing_path=$( command -v thing ) &amp;&amp; [ -x "$thing_path" ]` (4认同)
  • @KenSharp:但这似乎是多余的,因为`command`本身会测试它是可执行的 - 不是吗? (3认同)
  • @ nyuszika7h重要的是要注意,如果您要检查的命令是*不是*可执行文件,而是shell函数或内置函数,此解决方案将会中断.它应该专门用于检查可执行文件. (3认同)

Gre*_*egV 201

我同意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; }
Run Code Online (Sandbox Code Playgroud)

Command command符合POSIX标准,请参阅此处了解其规范:http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

注意:type符合POSIX标准,但type -P不是.

  • @jyavenard:问题标记为_bash_,因此更简洁的bash特定重定向符号`&>/dev/null`.但是,我同意你的意见,真正重要的是可移植性,我已经相应地编辑了我的答案,现在使用标准的sh重定向`>/dev/null 2>&1`. (7认同)
  • 与上面相同 - `退出1;`如果从那里调用,则杀死xterm. (3认同)

Jos*_*ter 87

我在.bashrc中定义了一个函数,使这更容易.

command_exists () {
    type "$1" &> /dev/null ;
}
Run Code Online (Sandbox Code Playgroud)

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

if command_exists mvim ; then
    export VISUAL="mvim --nofork"
fi
Run Code Online (Sandbox Code Playgroud)

  • `&>`[重定向stdout和stderr](http://www.gnu.org/software/bash/manual/bash.html#Redirecting-Standard-Output-and-Standard-Error). (5认同)
  • 无法使用内置指令和保留字:例如,尝试使用`then`一词。如果您要求可执行文件存在于$ PATH中,请参见[this answer](/sf/answers/3765914981/)。 (2认同)

dre*_*lax 75

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

if which programname >/dev/null; then
    echo exists
else
    echo does not exist
fi
Run Code Online (Sandbox Code Playgroud)

否则使用

if [ -x /path/to/programname ]; then
    echo exists
else
    echo does not exist
fi
Run Code Online (Sandbox Code Playgroud)

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

  • 出于我的评论中概述的原因,你真的不应该使用"which". (22认同)

Rom*_*rio 35

扩展@ lhunath和@ GregV的答案,这里是那些想要轻松地将该检查放在if语句中的人的代码:

exists()
{
  command -v "$1" >/dev/null 2>&1
}
Run Code Online (Sandbox Code Playgroud)

以下是如何使用它:

if exists bash; then
  echo 'Bash exists!'
else
  echo 'Your system does not have Bash'
fi
Run Code Online (Sandbox Code Playgroud)

  • 学习和提高的意愿必须得到回报.+1这很干净简单.我唯一可以添加的是`命令`甚至对于别名也能成功,这可能有点违反直觉.检查交互式shell中是否存在会在将其移动到脚本时产生不同的结果. (12认同)
  • @Palec:你是对的,它非常笨拙.我把它清理了一下,我希望现在看起来更合适. (5认同)
  • 几乎尽可能笨拙.阅读[man bash`中的退出状态](https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html)并了解如何使用它.它将使您的代码更简单,更优雅.括号不是`if`语法的一部分,它们只是命令`test`的简写.`if`检查命令是否成功(退出状态为0). (2认同)
  • 我刚刚测试并使用 `shopt -u Expand_aliases` 忽略/隐藏别名(如另一个答案中提到的 `alias ls='ls -F'`),并且 `shopt -s Expand_aliases` 通过 `command -v` 解析它们。因此,也许应该在检查之前设置它并在检查之后取消设置,尽管如果您不显式捕获并返回命令调用的输出,它可能会影响函数返回值。 (2认同)

dmc*_*kee 20

尝试使用:

test -x filename
Run Code Online (Sandbox Code Playgroud)

要么

[ -x filename ]
Run Code Online (Sandbox Code Playgroud)

条件表达式下的bash手册页:

 -x file
          True if file exists and is executable.
Run Code Online (Sandbox Code Playgroud)

  • 这意味着您需要已经知道应用程序的完整路径. (25认同)
  • OP没有说明他是否要检查特定实例或任何可执行实例......我按照我的方式回答它. (9认同)

dch*_*les 15

要使用hash,如@lhunath表明,在bash脚本:

hash foo &> /dev/null
if [ $? -eq 1 ]; then
    echo >&2 "foo not found."
fi
Run Code Online (Sandbox Code Playgroud)

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

&> /dev/null重定向标准错误和标准输出,hash以便它不会出现在屏幕上并将echo >&2消息写入标准错误.

  • 为什么不只是`if hash foo&>/dev/null; 然后......`? (8认同)
  • 如果设置了“set -e”,则“if...”部分永远不会运行。 (2认同)

tod*_*dsm 12

它可以更简单,只需:

#!/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                                                                                 
Run Code Online (Sandbox Code Playgroud)

更改foovi以使其他条件触发。

注意:始终首先处理负面情况,例如 if !. 最好给程序提供一切失败的机会;让测试变得超级简单 ;-)


Ant*_*nyC 11

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

我发现以下内容更防故障:

test -x "$(which <command>)"
Run Code Online (Sandbox Code Playgroud)

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

  • @algal我认为你需要使用引号,所以 `test -x "$(which &lt;command&gt;)"` (2认同)
  • @acorello,“测试:参数太多”表明“whichabsent_cmd”返回多字错误消息。可能存在与消息同名的可执行文件。以这种方式使用“which”是一个坏主意。 (2认同)

kei*_*sar 10

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

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

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

  • `[[` 和命令替换在这里没有用。只需`命令 -v mvn || 如果没有找到“mvn”,exit`将“退出”;但修复后,这基本上只是重复接受的答案。 (2认同)

Mag*_*nus 9

我从来没有得到上述解决方案在我可以访问的盒子上工作.例如,已安装类型(执行更多操作).所以需要内置指令.这个命令对我有用:

if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
Run Code Online (Sandbox Code Playgroud)

  • 括号不是`if`语法的一部分,只需使用`if builtin type -p vim; 然后......` 反引号是非常古老和不赞成的语法,在所有现代系统上,甚至`sh`都支持`$()`. (3认同)

Cir*_*四事件 8

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

for cmd in latex pandoc; do
  printf '%-10s' "$cmd"
  if hash "$cmd" 2>/dev/null; then
    echo OK
  else
    echo missing
  fi
done
Run Code Online (Sandbox Code Playgroud)

样本输出:

latex     OK
pandoc    missing
Run Code Online (Sandbox Code Playgroud)

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

  • 这样做的非详细方法:1)摆脱宽度说明符;2) 在命令名称的 printf 后添加一个空格;3) 将你的 for 循环传送到 `column -t`(util-linux 的一部分)。 (2认同)

0xF*_*0xF 7

如果你检查程序是否存在,你可能会在以后运行它.为什么不尝试首先运行它?

if foo --version >/dev/null 2>&1; then
    echo Found
else
    echo Not found
fi
Run Code Online (Sandbox Code Playgroud)

这是一个更值得信赖的检查程序运行,而不仅仅是查看PATH目录和文件权限.

另外,您可以从程序中获得一些有用的结果,例如它的版本.

当然缺点是某些程序可能很重要,有些程序没有--version立即(并成功)退出的选项.


Ric*_*ing 7

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

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


blu*_*yed 6

hash foo 2>/dev/null:适用于zsh,bash,dash和ash.

type -p foo:它似乎与zsh,bash和ash(busybox)一起使用,但不是破折号(它解释-p为参数).

command -v foo:适用于zsh,bash,dash,但不适用于ash(busybox)(-ash: command: not found).

还要注意的是builtin不可用ashdash.


Mar*_*ich 6

zsh,但对于脚本编写非常有用zsh(例如,编写完成脚本时):

\n

zsh/parameter模块可以访问内部哈希表等commands。从man zshmodules

\n
THE ZSH/PARAMETER MODULE\n       The zsh/parameter module gives access to some of the internal hash  ta\xe2\x80\x90\n       bles used by the shell by defining some special parameters.\n\n\n[...]\n\n       commands\n              This  array gives access to the command hash table. The keys are\n              the names of external commands, the values are the pathnames  of\n              the  files  that would be executed when the command would be in\xe2\x80\x90\n              voked. Setting a key in this array defines a new entry  in  this\n              table  in the same way as with the hash builtin. Unsetting a key\n              as in `unset "commands[foo]"' removes the entry  for  the  given\n              key from the command hash table.\n\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

例子:

\n
martin@martin ~ % echo $commands[zsh]\n/usr/bin/zsh\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
if (( ${+commands[zsh]} ))\nthen\n  echo "zsh is available"\nfi\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,散列将包含$PATH文件夹中的任何文件,无论它们是否可执行。为了绝对确定,您必须拨打电话stat

\n
if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]\nthen\n  echo "zsh is available"\nfi\n
Run Code Online (Sandbox Code Playgroud)\n


Ada*_*vis 5

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
Run Code Online (Sandbox Code Playgroud)

好处是which它可以确定可执行文件在which运行的环境中是否可用 - 它可以避免一些问题......

  • 不要依赖其退出状态。许多操作系统甚至不设置除 0 以外的退出状态。 (15认同)

小智 5

如果可以,为什么不使用Bash builtins?

which programname
Run Code Online (Sandbox Code Playgroud)

...

type -P programname
Run Code Online (Sandbox Code Playgroud)

  • 咦?`which`不是Bash内置的. (16认同)

Nat*_*use 5

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

dpkg --status libdb-dev | grep -q not-installed

if [ $? -eq 0 ]; then
    apt-get install libdb-dev
fi
Run Code Online (Sandbox Code Playgroud)

从上面可以看出,查询结果为“0”意味着该软件包未安装。这是“grep”的函数 - “0”表示找到匹配项,“1”表示未找到匹配项。

  • 然而,反模式`cmd; 如果 [ $? -eq 0];then` 应该重构为 `if cmd; 那么` (12认同)

归档时间:

查看次数:

618488 次

最近记录:

5 年,11 月 前