Mantenha os códigos de saída ao capturar SIGINT e similares?

14

Se eu usar trapcomo descrito, por exemplo, em http://linuxcommand.org/wss0160.php#trap para capturar ctrl-c (ou similar) e limpeza antes de sair, estou alterando o código de saída retornado.

Agora, isso provavelmente não fará diferença no mundo real (por exemplo, porque os códigos de saída não são portáteis e, além disso, nem sempre são ambíguos, conforme discutido no Código de saída padrão quando o processo é finalizado? ), Mas ainda estou me perguntando se existe algum realmente não há como evitar isso e retornar o código de erro padrão para scripts interrompidos?

Exemplo (no bash, mas minha pergunta não deve ser considerada específica do bash):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

Resultado:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(Editado para remover para torná-lo mais compatível com POSIX.)

(Editado novamente para torná-lo um script bash, minha pergunta não é específica do shell.)

Editado para usar o "INT" portátil para interceptar a favor do "SIGINT" não portátil.

Editado para remover chaves inúteis e adicionar uma solução em potencial.

Atualizar:

Eu o resolvi agora, simplesmente saindo com alguns códigos de erro codificados e capturando EXIT. Isso pode ser problemático em alguns sistemas, porque o código de erro pode ser diferente ou a armadilha EXIT não é possível, mas no meu caso está tudo bem.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
phk
fonte
Seu script parece um pouco estranho: você pede readpara ler o coprocesso atual e trap cmd SIGINTnão funcionará, pois o padrão diz que você deve usar trap cmd INT.
schily
Ah, sim, no POSIX, é claro, sem o prefixo SIG.
Php12
Opa, mas o "read -p" também não seria suportado, então vou adaptá-lo para o bash.
Phk #
@ Schily: Eu não sei o que você quer dizer com "coprocess" embora.
Php12
Bem, a página de manual do Korn Shell diz que read -plê as entradas do coprocesso atual.
schily

Respostas:

4

Tudo que você precisa fazer é alterar o manipulador EXIT dentro do manipulador de limpeza. Aqui está um exemplo:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '
rochoso
fonte
você quis dizer em trap cleanup INTvez de trap cleanup EXIT?
Jeff Schaller
Eu acho que quis dizer EXIT. Quando a saída é finalmente chamada, no entanto, isso é feito, alteramos a armadilha para sair com o retorno 0. Não acredito que os manipuladores de sinal sejam recursivos.
rochoso
OK, no seu exemplo, não estou interceptando (SIG) INT, o que é realmente o que eu quero para fazer alguma limpeza, mesmo que o usuário esteja saindo do meu script interativo através do ctrl-c.
Php12
@phk Ok. Eu acho que, embora você tenha a idéia de como forçar a saída a retornar 0 ou algum outro valor, o que eu reuni foi o problema. Eu suponho que você poderá ajustar o código para colocar a configuração EXIT do trap dentro do seu manipulador de limpeza real, chamado por SIGINT.
rochoso
@rocky: Meu objetivo era ter um manipulador de armadilhas sem alterar o código de saída que eu normalmente teria sem o manipulador. Agora eu poderia simplesmente retornar o código de saída que você normalmente obteria, mas o problema era que eu não sabia exatamente o código de saída nesses casos (como visto no segmento ao qual vinculei e no post do meuh, é bastante complicado). Sua postagem ainda foi útil, não pensei em redefinir o manipulador de trapping dinamicamente.
Php12
9

Na verdade, interromper o interno do bash readparece ser um pouco diferente de interromper um comando executado pelo bash. Normalmente, quando você digita trap, $?está definido e você pode preservá-lo e sair com o mesmo valor:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

Se o seu script for interrompido ao executar um comando como sleep ou mesmo um interno wait, você verá

130 SIGINT
130 EXIT

e o código de saída é 130. No entanto, read -pparece que $?é 0 (na minha versão do bash 4.3.42 de qualquer maneira).


A manipulação de sinais durante readpode estar em andamento, de acordo com o arquivo de alterações no meu release ... (/ usr / share / doc / bash / CHANGES)

alterações entre esta versão, bash-4.3-alpha e a versão anterior, bash-4.2-release.

  1. Novos recursos no Bash

    r. Quando no modo Posix, a leitura é interrompida por um sinal interceptado. Depois de executar o manipulador de interceptação, a leitura retorna o sinal 128 + e joga fora qualquer entrada parcialmente lida.

meuh
fonte
OK, isso é realmente estranho. É 130 no shell interativo no bash 4.3.42 (no cygwin), 0 no mesmo shell no modo script e POSIX ou não faz nenhuma diferença. Mas então sob colisão e busybox é sempre 1.
PHK
Eu tentei o modo POSIX com uma armadilha que não sai e ele reinicia o read, conforme observado no arquivo CHANGES (adicionado à minha resposta). Portanto, pode ser um trabalho em andamento.
Meuhttp:
O código de saída 130 é 100% não portátil. Enquanto o Bourne Shell usa 128 + signocomo código de saída para sinais, o ksh93 usa 256 + signo. POSIX diz: algo acima de 128 ....
Schily
@schily True, conforme observado no tópico ao qual vinculei a postagem original ( unix.stackexchange.com/questions/99112 ).
Phk #
6

Qualquer código de saída de sinal usual estará disponível na $?entrada do manipulador de trap:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

Se houver um trap EXIT separado, você poderá usar a mesma metodologia: mantenha imediatamente o status de saída passado da limpeza do manipulador de sinais (se houver), e retorne o status de saída salvo.

Tom Hale
fonte
Isso se aplica à maioria das conchas? Muito agradável. localnão é POSIX embora.
Php 30/09/19
1
Editado. Eu não testei em outro senão {ba,z}sh. AFAIK, é o melhor que pode ser feito, independentemente.
Tom Hale
Isso não funciona no traço ( $?é 1 qualquer que seja o sinal recebido).
MoonSweep
2

Basta retornar um código de erro que não é suficiente para simular uma saída pelo SIGINT. Estou surpreso que ninguém tenha mencionado isso até agora. Leitura adicional: https://www.cons.org/cracauer/sigint.html

A maneira correta é:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

Isso funciona com o Bash, Dash e zsh. Para maior portabilidade, você precisa usar especificações de sinal numérico (por outro lado, o zsh espera um parâmetro de string para o killcomando ...)

Observe também o tratamento especial do EXITsinal. Isso ocorre porque alguns shells (ou seja, o Bash) também executam traps EXITpara qualquer sinal (após possíveis traps definidos nesse sinal). A redefinição da EXITarmadilha impede isso.

Executando código apenas na saída "normal"

A verificação [ $sig = EXIT ]permite executar o código apenas na saída normal (sem sinal). No entanto, todos os sinais precisam ter armadilhas que, por fim, redefinem a armadilha EXIT; normal_exit_only_cleanuptambém será chamado pelos sinais que não o fazem. Também será executado por uma saída set -e. Isso pode ser corrigido capturando ERR(que não é suportado pelo Dash) e adicionando uma verificação [ $sig = ERR ]antes do kill.

Versão simplificada do Bash-only

Por outro lado, esse comportamento significa que no Bash você pode simplesmente fazer

trap cleanup EXIT

para executar apenas algum código de limpeza e manter o status de saída.

editado

  • O comportamento elaborado de Bash de "EXIT prende tudo"

  • Remova o sinal KILL, que não pode ser preso

  • Remover prefixos SIG dos nomes dos sinais

  • Não tente kill -s EXIT

  • Considerar set -e/ ERR

philipp2100
fonte
Não existe um requisito geral de que um programa que intercepte sinais termine de maneira a preservar o fato de ter recebido um sinal. Por exemplo, um programa pode declarar que o envio SIGINTé a maneira de desligá-lo e pode decidir sair com 0 se conseguir terminar sem erro ou outro código de erro se não desligar corretamente. Caso em questão: top. Execute top; echo $?e pressione Ctrl-C. O status despejado na tela será 0.
Louis
1
Obrigado por apontar isso. No entanto, o pôster perguntou especificamente sobre como manter o código de saída.
philipp2100