tutorial - Como verificar se existe um programa a partir de um script Bash?




create function bash (20)

Responda

Compatível com POSIX:

command -v <the_command>

Para ambientes específicos de bash :

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

Explicação

Evite which . Não é apenas um processo externo que você está lançando para fazer muito pouco (o que significa que builtins como hash , type ou command são muito mais baratos), você também pode confiar nos builtins para fazer o que quiser, enquanto os efeitos de comandos externos podem facilmente variam de sistema para sistema.

Por que se importar?

  • Muitos sistemas operacionais possuem um which não define nem mesmo um status de saída , significando que o if which foo nem funcionará lá e sempre informará que foo existe, mesmo que não funcione (note que alguns shells POSIX parecem fazer isso para hash também).
  • Muitos sistemas operacionais fazem coisas customizadas e malignas, como alterar a saída ou até mesmo conectar-se ao gerenciador de pacotes.

Então, não use which . Em vez disso, use um destes:

$ 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; }

(Nota secundária: alguns irão sugerir 2>&- é o mesmo 2>/dev/null mas menor - isto é falso . 2>&- fecha o FD 2 que causa um erro no programa quando tenta escrever para stderr , que é muito diferente de gravar com sucesso e descartar a saída (e perigosa!))

Se o seu hash bang é /bin/sh então você deve se preocupar com o que o POSIX diz. type códigos de saída do type e do hash não são muito bem definidos pelo POSIX, e o hash é visto sair com sucesso quando o comando não existe (ainda não o vi com o type ). O status de saída do command é bem definido pelo POSIX, de modo que um é provavelmente o mais seguro de usar.

Se o seu script usa o bash , as regras POSIX não importam mais e o type e o hash se tornam perfeitamente seguros de usar. type agora tem um -P para pesquisar apenas o PATH e o hash tem o efeito colateral de que a localização do comando será dividida (para uma pesquisa mais rápida da próxima vez que você usá-lo), o que geralmente é uma coisa boa, pois você provavelmente para realmente usá-lo.

Como um exemplo simples, aqui está uma função que roda gdate se existir, caso contrário, date :

gnudate() {
    if hash gdate 2>/dev/null; then
        gdate "[email protected]"
    else
        date "[email protected]"
    fi
}

Como eu validaria que um programa existe, de uma maneira que retorne um erro e saia ou continue com o script?

Parece que deve ser fácil, mas está me enganando.


A hash-variante tem uma armadilha: na linha de comando, você pode, por exemplo, digitar

one_folder/process

ter processo executado. Para isso, a pasta pai de uma pasta deve estar em $ PATH . Mas quando você tenta hash este comando, sempre terá sucesso:

hash one_folder/process; echo $? # will always output '0'

Depende se você quer saber se existe em um dos diretórios na variável $PATH ou se você sabe a localização absoluta dele. Se você quiser saber se está na variável $PATH , use

if which programname >/dev/null; then
    echo exists
else
    echo does not exist
fi

caso contrário, use

if [ -x /path/to/programname ]; then
    echo exists
else
    echo does not exist
fi

O redirecionamento para /dev/null/ no primeiro exemplo suprime a saída do programa which .


Eu concordo com o lhunath para desencorajar o uso de which , e sua solução é perfeitamente válida para usuários de BASH . No entanto, para ser mais portátil, o command -v deve ser usado em vez disso:

$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed.  Aborting." >&2; exit 1; }

Comando de command é compatível com POSIX, veja aqui para sua especificação: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html

Nota: o type é compatível com POSIX, mas o type -P não é.


Eu nunca consegui obter as soluções acima para trabalhar na caixa que eu tenho acesso. Por um lado, o tipo foi instalado (fazendo o que mais faz). Portanto, a diretiva integrada é necessária. Este comando funciona para mim:

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

Eu segundo o uso de "comando -v". Por exemplo, assim:

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

Eu tive que verificar se o git foi instalado como parte da implementação do nosso servidor de CI. Meu último script bash foi o seguinte (servidor Ubuntu):

if ! builtin type -p git &>/dev/null; then
  sudo apt-get -y install git-core
fi

Espero que isso ajude alguém!


Eu uso isso porque é muito fácil:

if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then echo exists;else echo "not exists";fi

ou

if [ `LANG=C type example 2>/dev/null|wc -l` = 1 ];then
echo exists
else echo "not exists"
fi

Ele usa o shell embutido e programa o status de eco para stdout e nada para stderr por outro lado, se um comando não for encontrado, o status é echos apenas para stderr.


Há uma tonelada de opções aqui, mas fiquei surpreso com os one-liners rápidos, isso é o que eu usei no início dos meus scripts: [[ "$(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 -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; } [[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }

isso é baseado na resposta selecionada aqui e em outra fonte (e eu brincando um pouco).

Espero que isso seja útil para os outros.


O comando which pode ser útil. homem que

Retorna 0 se o executável for encontrado, 1 se não for encontrado ou não for executável:

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

O interessante é que ele descobre se o executável está disponível no ambiente em que é executado - salva alguns problemas ...

-Adão


Para os interessados, nenhuma das metodologias acima funciona se você deseja detectar uma biblioteca instalada. Eu imagino que você ficou com a verificação física do caminho (potencialmente para arquivos de cabeçalho e tal), ou algo assim (se você estiver em uma distribuição baseada no Debian):

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

if [ $? -eq 0 ]; then
    apt-get install libdb-dev
fi

Como você pode ver acima, uma resposta "0" da consulta significa que o pacote não está instalado. Esta é uma função de "grep" - um "0" significa que uma correspondência foi encontrada, um "1" significa que nenhuma correspondência foi encontrada.


Para usar o hash , como sugere o @lhunath , em um script bash:

hash foo &> /dev/null
if [ $? -eq 1 ]; then
    echo >&2 "foo not found."
fi

Este script executa o hash e, em seguida, verifica se o código de saída do comando mais recente, o valor armazenado em $? é igual a 1 . Se o hash não encontrar foo , o código de saída será 1 . Se foo estiver presente, o código de saída será 0 .

&> /dev/null redireciona o erro padrão e a saída padrão do hash para que ele não apareça na tela e echo >&2 grava a mensagem no erro padrão.


Roteiro

#!/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"'

Resultado

 [ABRT]: notacmd: command does not exist
hits    command
   0    /usr/bin/bash
Fin.

Se não houver nenhum comando de type externo disponível (conforme assumido here ), podemos usar env -i sh -c 'type cmd 1>/dev/null 2>&1' compatível com 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

Pelo menos no Mac OS X 10.6.8 usando Bash 4.2.24 (2) o command -v ls não corresponde a um /bin/ls-temp movido.


Se vocês não conseguirem fazer com que as coisas acima / abaixo funcionem e tirarem o cabelo das costas, tente executar o mesmo comando usando bash -c . Basta olhar para este delírio somnambular, isso é o que realmente acontece quando você executa $ (subcomando):

Primeiro. Pode dar-lhe uma saída completamente diferente.

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

Segundo. Não pode dar a você nenhuma saída.

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

Segue-se uma forma portátil de verificar se existe um comando em $PATH e é executável:

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

Exemplo:

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

A verificação executável é necessária porque o bash retorna um arquivo não executável se nenhum arquivo executável com esse nome for encontrado em $PATH .

Observe também que, se um arquivo não executável com o mesmo nome do executável existir anteriormente em $PATH , traço retorna o primeiro, mesmo que o último seja executado. Este é um bug e está violando o padrão POSIX. [ Bug report ] [ Standard ]

Além disso, isso falhará se o comando que você está procurando tiver sido definido como um alias.


Vai dizer de acordo com a localização se o programa existe ou não

if [ -x /usr/bin/yum ]; then
    echo This is Centos
fi

comando -v funciona bem se a opção POSIX_BUILTINS estiver definida para o <command> para testar, mas pode falhar se não. (isso funcionou para mim por anos, mas recentemente aconteceu em um onde não funcionou).

Eu acho o seguinte para ser mais à prova de falhas:

test -x $(which <command>)

Desde que testa para 3 coisas: caminho, execução e permissão.


hash foo 2>/dev/null : trabalha com zsh, bash, dash e ash.

type -p foo : parece funcionar com zsh, bash e ash (busybox), mas não traço (interpreta -p como um argumento).

command -v foo : funciona com zsh, bash, dash, mas não com ash (busybox) ( -ash: command: not found ).

Observe também que o builtin não está disponível com ash e dash .


Verifique várias dependências e informe o status para os usuários finais

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

Exemplo de saída:

latex     OK
pandoc    missing

Ajuste o 10 ao comprimento máximo do comando. Não automático porque não vejo uma maneira POSIX não verbosa: como alinhar as colunas de uma tabela separada por espaço no Bash?





bash