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 。 它不仅仅是一个外部过程,你只需要做很少的事情(意思是像hashtypecommand这样的内建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的说法。 typehash的退出代码不是由POSIX很好地定义的,并且在命令不存在的情况下hash可以成功退出(尚未见到这种type )。 command的退出状态由POSIX很好地定义,所以一个可能是最安全的使用。

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

作为一个简单的例子,这里有一个运行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内置命令 ,那么commandtypehash不适合测试,因为它们都返回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
}




shell