"Armadilha ... EXT TERM SAIR" é realmente necessário?

63

Muitos exemplos para trapuso trap ... INT TERM EXITem tarefas de limpeza. Mas é realmente necessário listar todos os três tipos de sigs?

O manual diz:

Se um SIGNAL_SPEC for EXIT (0), ARG será executado na saída do shell.

que eu acredito que se aplica se o script terminou normalmente ou porque recebeu SIGINTou SIGTERM. Um experimento também confirma minha crença:

$ cat ./trap-exit
#!/bin/bash
trap 'echo TRAP' EXIT
sleep 3
$ ./trap-exit & sleep 1; kill -INT %1
[1] 759
TRAP
[1]+  Interrupt               ./trap-exit
$ ./trap-exit & sleep 1; kill -TERM %1
[1] 773
TRAP
[1]+  Terminated              ./trap-exit

Então, por que tantos exemplos listam todos INT TERM EXIT? Ou eu perdi alguma coisa e há algum caso em que uma solteira EXITperca?

musiphil
fonte
3
Lembre-se também de que, com uma especificação como INT TERM EXITo código de limpeza, é executado duas vezes quando SIGTERMou SIGINTé recebido.
maxschlepzig

Respostas:

19

A especificação POSIX não diz muito sobre as condições que resultam na execução da interceptação EXIT, apenas sobre como deve ser o ambiente quando é executado.

No shell de cinzas do Busybox, seu teste de captura e saída não faz eco de 'TRAP' antes de sair devido ao SIGINT ou SIGTERM. Eu suspeitaria que existem outras conchas na existência que podem não funcionar dessa maneira também.

# /tmp/test.sh & sleep 1; kill -INT %1
# 
[1]+  Interrupt                  /tmp/test.sh
# 
# 
# /tmp/test.sh & sleep 1; kill -TERM %1
# 
[1]+  Terminated                 /tmp/test.sh
# 
Shawn J. Goff
fonte
3
dashtambém não aprisiona apenas EXITquando recebe SIGINT/SIGTERM.
maxschlepzig
4
zshtambém - portanto, talvez bashseja o único shell em que EXITtambém corresponda a sinais.
maxschlepzig
@maxschlepzig zshnão intercepta EXITquando recebe INT, mas quando recebe TERM. EDIT: Eu só notei quantos anos isso foi ...
JOL
27

Sim, existe uma diferença.

Este script será encerrado quando você pressionar Enterou enviar SIGINTou SIGTERM:

trap '' EXIT
echo ' --- press ENTER to close --- '
read response

Este script será encerrado quando você pressionar Enter:

trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response

* Testado em sh , Bash e Zsh . (não funciona mais no sh quando você adiciona um comando para que a interceptação seja executada)


Há também o que @ Shawn disse: Ash e Dash não capturam sinais EXIT.

Portanto, para lidar com sinais de maneira robusta, é melhor evitar a interceptação EXITe usar algo como isto:

cleanup() {
    echo "Cleaning stuff up..."
    exit
}

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
Zaz
fonte
11
A solução com a limpeza faz a coisa certa - muito elegante! Tornou-se um idioma para meus scripts bash com mktempchamadas.
Bjoern Dahlgren
2
Isso é exitnecessário cleanup?
jarno 9/09/16
3
Isso não funcionará se você tiver erros no shellscript em seu código que fazem com que ele saia prematuramente.
IJW
2
@ijw: No Bash e no Ksh, você pode capturar ERRpara lidar com isso, mas não é portátil .
Zaz
5
Essa solução não é robusta quando outro shell o chama. Ele não lida com espera na saída cooperativa ; você desejará trap - INT TERM; kill -2 $$como a última linha de limpeza, informar ao shell pai que ele saiu prematuramente. Se um shell pai foobar.sh chama seu script (foo.sh) e depois chama bar.sh, você não quer que o bar.sh seja executado se INT / TERM for enviado ao seu foo.sh. trap cleanup EXITmanipulará essa propagação automaticamente, por isso é IMO a mais robusta. Isso também significa que você não precisaria ligar cleanupno final do script.
Nicholas Pipitone
12

Refinando a última resposta, porque há problemas:

# Our general exit handler
cleanup() {
    err=$?
    echo "Cleaning stuff up..."
    trap '' EXIT INT TERM
    exit $err 
}
sig_cleanup() {
    trap '' EXIT # some shells will call EXIT after the INT handler
    false # sets $?
    cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM

Pontos acima:

Os manipuladores INT e TERM não param para mim quando eu testo - eles lidam com o erro e o shell volta a sair (e isso não é muito surpreendente). Portanto, garanto que a limpeza saia depois e, no caso dos sinais, use sempre um código de erro (e, no outro caso de uma saída normal, preserve o código de erro).

Com o bash, parece que sair no manipulador INT também chama o manipulador EXIT, portanto, desmarcador o manipulador de saída e o chamo eu mesmo (que funcionará em qualquer shell, independentemente do comportamento).

Intercepto a saída porque os scripts do shell podem sair antes que eles atinjam o fundo - erros de sintaxe, set -e e um retorno diferente de zero, simplesmente chamando exit. Você não pode confiar em um shellscript chegando ao fundo.

SIGQUIT é Ctrl- \ se você nunca experimentou. Você recebe um bônus de coredump. Então acho que também vale a pena capturar, mesmo que seja um pouco obscuro.

A experiência passada diz que, se você (como eu) sempre pressiona Ctrl-C várias vezes, às vezes consegue capturá-lo no meio da parte da limpeza do seu script de shell, portanto isso funciona, mas nem sempre tão perfeitamente quanto você gostaria.

ijw
fonte
2
O chamador seria apenas obter 1 como o código de saída, não importa o que sinal causou a saída, enquanto withour trapo chamador iria receber 130 para SIGINT, 143 para SIGTERM, etc. Assim, gostaria de capturar e passar o código de saída correta como: sig_cleanup() { err=$?; trap '' EXIT; (exit $err); cleanup; }.
Musiphil 14/11/16
2
Você pode esclarecer o objetivo da trap '' EXIT INT TERMfunção de limpeza? Isso evita a interrupção acidental do usuário da limpeza mencionada no último parágrafo? O EXITredundante não é ?
6