"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?


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
# 

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

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.