Existe uma maneira de ter uma função no meu script bash executar automaticamente em qualquer erro de comando?

12

Estou escrevendo um script de shell que precisa executar vários comandos e todo comando depende de todos os comandos anteriores. Se algum comando falhar, o script inteiro falhará e eu chamo uma função de saída. Eu poderia verificar o código de saída de cada comando, mas estou pensando se existe um modo que eu possa ativar ou uma maneira de obter o bash para fazer isso automaticamente.

Por exemplo, tome estes comandos:

cd foo || myfunc
rm a || myfunc
cd bar || myfunc
rm b || myfunc


Existe uma maneira em que eu possa, de alguma forma, sinalizar para o shell antes de executar esses comandos que ele deve chamar de myfunc se algum deles falhar, para que eu possa escrever algo mais limpo como:

cd foo
rm a
cd bar
rm b
teste
fonte

Respostas:

13

Você pode usar o ERR do bash trap para fazer com que seu script seja encerrado se algum comando retornar um status maior que zero e executar sua função ao sair.

Algo como:

myfunc() {
  echo 'Error raised. Exiting!'
}

trap 'myfunc' ERR

# file does not exist, causing error
ls asd
echo 123

Observe que o trap bash ERR está implícito set -o errexitou set -enão é POSIX.

E a ERRarmadilha não é executado se o comando não faz parte da lista de comando imediatamente após untilou whilepalavra-chave, parte do teste seguindo as ifou elifreservadas palavras, parte de um comando executado em &&ou ||lista, ou se o estado de retorno do comando está sendo invertida usando !.

cuonglm
fonte
1

Uma variação (talvez) mais simples da resposta aceita:

  1. Use set -e para causar a falha de um comando para interromper a execução de uma lista.
  2. Simplesmente liste seus comandos.
  3. Use uma instrução if- then- elsepara executar seus comandos de tratamento de erros. Esta última peça é um pouco complicada. Assistir:
set -e
E se
    cmd 1                         # por exemplo, cd foo
     cmd 2                         # por exemplo, rm a
     cmd 3                         # por exemplo, barra de cd
     cmd 4                         # por exemplo, rm b
então
    set + e
    comandos a serem executados com sucesso (se houver)
outro
    set + e
    myfunc
    outros comandos a serem executados em caso de falha (se houver) 
fi

A parte complicada é que você colocar seus comandos para a ifparte da if- then- else, e não a thenparte ou a elseparte. Lembre-se de que a sintaxe da ifinstrução é

se  lista ; então  lista ; [  lista elif ; então  lista ; ] ... [outra  lista ; ] fi 
   ↑↑↑↑
O set -ecomando diz ao shell que, se ( ) falhar, ele não deve continuar e executar ( ), e assim por diante. Se isso acontecesse com um comando no nível mais externo de um script de shell, o shell sairia. No entanto, como a · · · é uma lista (composta) após um , a falha de qualquer um desses quatro comandos simplesmente causa falha na lista inteira - o que faz com que a cláusula seja executada. Se todos os quatro comandos tiverem êxito, a cláusula é executada.cmd1cd foocmd2rm acmd1cmd2cmd3cmd4ifelsethen

Em ambos os casos, a primeira coisa que você deve fazer é provavelmente desativar (desativar) a eopção set +e. Caso contrário, o script poderá sair da água se um comando myfuncfalhar.

set -eé especificado e descrito na especificação POSIX .

G-Man diz que 'restabelece Monica'
fonte
0

Pegue sua palavra " todo comando depende de todo comando anterior. Se algum comando falhar, todo o script falhará " literalmente, acho que você não precisa de nenhuma função especial para tratar os erros.

Tudo que você precisa é encadear seus comandos com &&operador e ||operador, o que faz exatamente o que você escreveu.

Por exemplo, essa cadeia será interrompida e imprimirá "algo deu errado" se algum dos comandos anteriores for interrompido (o bash lê da esquerda para a direita)

cd foo && rm a && cd bar && rm b || echo "something went wrong"

Exemplo real (eu criei dir foo, arquivo a, barra de dir e arquivo b apenas para uma demonstração real):

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm bb || echo "something is wrong"
rm: cannot remove 'bb': No such file or directory
something is wrong #mind the error in the last command

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm aa && cd bar && rm b || echo "something is wrong"
rm: cannot remove 'aa': No such file or directory
something is wrong #mind the error in second command in the row

E finalmente, se todos os comandos foram executados com sucesso (código de saída 0), o script continua:

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm b || echo "something is wrong"
gv@debian:/home/gv/Desktop/PythonTests/foo/bar$ 
# mind that the error message is not printed since all commands were successful.

O que é importante lembrar é que, com o uso do && next, o comando é executado se o comando anterior sair com o código 0, que para bash significa sucesso.

Se algum comando der errado na cadeia, o comando / script / o que se segue || será executado.

E apenas para o registro, se você precisar executar ações diferentes, dependendo do comando que quebrou, também poderá fazê-lo com script clássico, monitorando o valor $?que informa o código de saída do comando exatamente anterior (retorna zero se o comando foi executado com êxito ou outro número positivo se o comando falhar)

Exemplo:

for comm in {"cd foo","rm a","cd bbar","rm b"};do  #mind the error in third command
eval $comm
    if [[ $? -ne 0 ]];then 
        echo "something is wrong in command $comm"
        break
    else 
    echo "command $comm executed succesful"
    fi
done

Resultado:

command cd foo executed succesfull
command rm a executed succesfull
bash: cd: bbar: No such file or directory
something is wrong in command cd bbar

Dica: Você pode suprimir a mensagem "bash: cd: bbar: esse arquivo não existe ..." aplicando eval $comm 2>/dev/null

George Vasiliou
fonte