Script de shell do Linux: execute um programa apenas se ele existir, ignore-o se ele não existir

15

Estou programando um script de shell do Linux que imprimirá faixas de status durante sua execução somente se a ferramenta adequada, por exemplo figlet, estiver instalada (isto é: acessível no caminho do sistema ).

Exemplo:

#!/usr/bin/env bash
echo "foo"
figlet "Starting"
echo "moo"
figlet "Working"
echo "foo moo"
figlet "Finished"

Gostaria que meu script funcionasse sem erros, mesmo quando nãofiglet estiver instalado .

O que poderia ser um método prático ?

Sopalajo de Arrierez
fonte
@ududus: apenas ignorar o comando 'figlet' (e seus parâmetros) ficaria OK. Continuando a execução, é claro.
Sopalajo de Arrierez 30/01/19
2
O título a esta pergunta me pegou em todos os tipos de problemas metafísicos
g_uint
2
Deseja ignorar todos os erros? Basta usar figlet ... || true.
Giacomo Alzetta
Se você não se importa com os códigos de saída, é necessário usar um atalho figlet || true, mas, no seu caso, provavelmente uma função shell que não é possível imprimir com o Echos plain text Se nenhum banner puder ser impresso, é mais provável que você queira.
Eckes

Respostas:

30

Minha interpretação usaria uma função de invólucro com o mesmo nome da ferramenta; nessa função, execute a ferramenta real, se existir:

figlet() {
  command -v figlet >/dev/null && command figlet "$@"
}

Então você pode ter figlet arg1 arg2...inalterado em seu script.

O @Olorin criou um método mais simples: defina uma função de wrapper apenas se for necessário (se a ferramenta não existir):

if ! command -v figlet > /dev/null; then figlet() { :; }; fi

Se você deseja que os argumentos figletsejam impressos, mesmo que o figlet não esteja instalado, ajuste a sugestão de Olorin da seguinte maneira:

if ! command -v figlet > /dev/null; then figlet() { printf '%s\n' "$*"; }; fi
Jeff Schaller
fonte
1
De onde commandvem? Não o tenho em minhas instalações (Red Hat 6.8 Enterprise e Cygwin64).
eewanco
1
@eewanco, consulte unix.stackexchange.com/a/85250/117549 ; Para encurtar a história, ele foi construído para o bash, que provavelmente é o seu shell.
Jeff Schaller
4
commandé um shell POSIX Bourne embutido. Ele normalmente executa o comando fornecido, mas a -vflag faz com que ele se comporte mais como typeoutro shell embutido.
Wyrm
@eewanco type -a commandirá mostrar-lhe. whichmostrará apenas executáveis ​​nas suas $PATHpalavras-chave, não incorporadas, palavras-chave, funções ou aliases.
precisa saber é
@ l0b0: no RedHat familiar com bash e o perfil padrão (s) which não encontrar um nome alternativo aplicável, porque aliases which-se a executar /usr/bin/which(!) e canalizá-lo uma lista de aliases do shell de olhar para (Claro \whichsuprime o alias e usa apenas o programa, que não mostra aliases.)
dave_thompson_085 31/01/19
14

Você pode testar para ver se figletexiste

if type figlet >/dev/null 2>&1
then
    echo Figlet is installed
fi
roaima
fonte
6

Uma maneira comum de fazer isso é com o test -xaka [ -x. Aqui está um exemplo retirado de /etc/init.d/ntpum sistema Linux:

if [ -x /usr/bin/lockfile-create ]; then
    lockfile-create $LOCKFILE
    lockfile-touch $LOCKFILE &
    LOCKTOUCHPID="$!"
fi

Essa variante depende de conhecer o caminho completo do executável. Em /bin/lesspipeque eu encontrei um exemplo que funciona em torno de que, combinando -xeo whichcomando:

if [ -x "`which bunzip`" ]; then bunzip -c "$1"
else echo "No bunzip available"; fi ;;

Dessa forma, isso funcionará sem saber com antecedência onde está PATHo bunzipexecutável.

Kasperd
fonte
4
Não usewhich . E mesmo que whichfuncione, o uso test -xde sua saída é bobo: se você obtiver um caminho which, ele existe.
Gilles 'SO- deixa de ser mau'
1
@ Gilles: Tecnicamente falando, test -xfaz mais do que apenas verificar se existe um arquivo ( test -eé para isso). Ele também verifica se o arquivo tem as permissões de execução definidas.
Comfreak #
@comfreak O mesmo acontece which.
Gilles 'SO- deixa de ser mau'
5

No início do seu script, verifique se figletexiste e, se não existir, defina uma função shell que não faz nada:

type figlet >/dev/null 2>&1 || figlet() { :; }

typeverifica se figletexiste como um shell embutido, função, alias ou palavra-chave, >/dev/null 2>&1descarta stdin e stdout para que você não obtenha nenhuma saída e, se ela não existir,figlet() { :; } definefiglet como uma função que não faz nada.

Dessa forma, você não precisa editar todas as linhas do seu script que usam figlet ou verificar se elas existem toda vezfiglet é chamado.

Você pode adicionar uma mensagem de diagnóstico, se desejar:

type figlet >/dev/null 2>&1 || { echo 'figlet not installed.' ; figlet() { :; } ; }

Como bônus, como você não mencionou qual shell está usando, acredito que ele seja compatível com POSIX, portanto, ele deve funcionar na maioria dos shell.

Chris
fonte
Eu gosto da simplicidade desta resposta. Você também pode substituir type figlet >/dev/null 2>&1com hash figlet 2>/dev/nullse você estiver usando bash. (O OP disse "se estiver no meu caminho".) #
211 Joe Joe
5

Outra alternativa - um padrão que eu já vi nos scripts de configuração automática do projeto:

if [ -x /usr/bin/figlet ]
then
    FIGLET=/usr/bin/figlet
else
    FIGLET=:
fi

$FIGLET "Hello, world!"

No seu caso específico, você poderia até fazer,

if [ -x /usr/bin/figlet ]
then
   SAY=/usr/bin/figlet
elif [ -x /usr/local/bin/figlet ]
then
   SAY=/usr/local/bin/figlet
elif [ -x /usr/bin/banner ]
then
   SAY=/usr/bin/banner
else
   SAY=/usr/bin/echo
fi

$SAY "Hello, world!"

Se você não conhece o caminho específico, pode tentar vários elif(veja acima) para tentar locais conhecidos ou apenas usar o PATHpara sempre resolver o comando:

if command -v figlet >/dev/null
then
    SAY=figlet
elif command -v banner >/dev/null
then
    SAY=banner
else
    SAY=echo
fi

Em geral, ao escrever scripts, prefiro chamar apenas comandos em locais específicos especificados por mim. Não gosto da incerteza / risco do que o usuário final pode ter colocado na sua PATH, talvez por conta própria ~/bin.

Se, por exemplo, eu estivesse escrevendo um script complicado para outras pessoas que possam remover arquivos com base na saída de um comando específico que estou chamando, eu não desejaria pegar acidentalmente algo no ~/binque possa ou não ser o comando Eu esperava.

rrauenza
fonte
3
E se figletestivesse dentro /usr/local/binou /home/bob/stuff/programs/executable/figlet?
Gilles 'SO- stop be evil'
3
type -p figlet > /dev/null && figlet "foo"

O typecomando bash localiza um comando, função, alias, palavra-chave ou interno (consulte help type) e imprime o local ou a definição. Ele também retorna um código de retorno representando o resultado da pesquisa; true (0) se encontrado. Portanto, o que estamos fazendo aqui é tentar encontrar figletno caminho ( -psignifica apenas procurar arquivos, não built-ins ou funções e também suprime mensagens de erro), descartando a saída (é o que > /dev/nullfaz) e se ela retorna verdadeira (&& ) , ele será executado figlet.

Isso é mais simples se figletestiver em um local fixo:

[ -x /usr/bin/figlet ] && /usr/bin/figlet "foo"

Aqui estamos usando o testcomando (aka [) para ver se /usr/bin/figleté executável ( -x) e se sim ( &&) executá-lo. Esta solução eu acho que é mais portátil do que usartype que é um basismo que eu acredito.

Você pode criar uma função que faça isso para você:

function x() {
    if type -p "$1" >/dev/null; then
        cmd="$1"
        shift
        "$cmd" "$@"
    fi
}

(As cotações são necessárias devido a espaços em potencial)

Então você faria:

x figlet "foo"
eewanco
fonte
0

o /, eu diria algo como

#!/usr/bin/env bash
# if figlet is installed :
if [ "$(which figlet 2>/dev/null)" ]; then
       # do what you wanted to do
       echo "foo"
       figlet "Starting"
       echo "moo"
       figlet "Working"
       echo "foo moo"
       figlet "Finished"
# if not
else
       # exit program with an error
       echo "please install figlet"
       exit 1
fi
Jus de Patate_
fonte
0

Você pode executar um teste, suprimindo qualquer saída e testar o código de êxito / falha. Escolha argumentos para o figlet para tornar este teste barato. -? ou --help ou --version são possibilidades óbvias.

if figlet --help >/dev/null 2>&1 ; then
    # figlet is available
    echo "foo"
    figlet "starting"
    #etc
else
    rc=$?
    echo "figlet is not installed or not working correctly (return code ${rc})"
fi

Adicionado em resposta ao comentário abaixo: se você realmente deseja testar se o figlet existe, e não que seja utilizável, você faria

figlet --help >/dev/null 2>&1 
rc=$?
if [ $rc -eq 127 ] ; then # 127 is "command not found" on linux bash 4.4.23(1)
    echo "command figlet not found"
else
    figlet "whatever" # use figlet
fi
nigel222
fonte
O problema aqui é que o figlet pode falhar por razões além de não estar instalado, portanto, apenas testar o código de saída não é suficiente.
Chris
Se você realmente deseja testar apenas sua instalação, deseja testar se $?é igual a 127 (no meu sistema linux). 127 é "comando não encontrado". Mas meu pensamento é que, para comandos racionais, se command --helpfalhar, a instalação será acionada o suficiente para que ela não esteja lá!
Ngel222
1
126 é "comando encontrado, mas não executável", então você também desejaria testar isso. Infelizmente, você não pode depender de --helpestar disponível. Os utilitários Posix não o possuem, por um lado, e as diretrizes posix realmente recomendam isso . at --helpfalha com at: invalid option -- '-', por exemplo, e um status de saída 130no meu sistema.
Chris
Além disso, é claro, se o figlet puder sair com o status 127, isso também pode ser um problema.
Chris
Ponto justo sobre 126. A maioria dos utilitários sãos tem algum tipo de opções de comando leves e inofensivas, como -? --help --version... ou você pode descobrir o que figlet -0 --illegalexiste e tratá-lo como seu indicador de sucesso (contanto que não seja 127, o que eu classificaria como sabotagem).
Ngel222