Mantenere i codici di uscita quando si intrappolano SIGINT e simili?


14

Se uso trapcome descritto ad esempio su http://linuxcommand.org/wss0160.php#trap per catturare ctrl-c (o simile) e ripulire prima di uscire, sto cambiando il codice di uscita restituito.

Ora questo probabilmente non farà differenza nel mondo reale (ad esempio perché i codici di uscita non sono portatili e per di più non sempre inequivocabili come discusso in Codice di uscita predefinito quando il processo è terminato? ) Ma mi chiedo ancora se ci sia davvero nessun modo per impedirlo e restituire invece il codice di errore predefinito per gli script interrotti?

Esempio (in bash, ma la mia domanda non dovrebbe essere considerata specifica per 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. ' _

Produzione:

$ ./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

(Modificato per rimuovere per renderlo più conforme POSIX.)

(Modificato di nuovo per renderlo invece uno script bash, la mia domanda non è specifica della shell però.)

Modificato per utilizzare il "INT" portatile per trap a favore del "SIGINT" non portatile.

Modificato per rimuovere inutili parentesi graffe e aggiungere una potenziale soluzione.

Aggiornare:

L'ho risolto ora semplicemente uscendo con alcuni codici di errore hardcoded e intrappolando EXIT. Questo potrebbe essere problematico su alcuni sistemi perché il codice di errore potrebbe essere diverso o la trappola EXIT non è possibile, ma nel mio caso è abbastanza OK.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

Il tuo script sembra un po 'strano: dici readdi leggere dal coprocesso corrente e trap cmd SIGINTnon funzionerà come lo standard dice che dovresti usare trap cmd INT.
schily,

Ah sì, sotto POSIX è ovviamente senza il prefisso SIG.
phk,

Oops, ma poi anche "read -p" non sarebbe supportato, quindi lo adatterò per bash.
phk,

@schily: Non so cosa intendi con "coprocesso" però.
phk,

Bene, la pagina man di Korn Shell dice che read -plegge l'input dal coprocesso corrente.
schily,

Risposte:


4

Tutto quello che devi fare è cambiare il gestore EXIT all'interno del gestore della pulizia. Ecco un esempio:

#!/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. '

intendevi trap cleanup INTinvece di trap cleanup EXIT?
Jeff Schaller

Penso di voler dire EXIT. Quando alla fine viene chiamato exit, comunque sia fatto, cambiamo la trap per uscire con return 0. Non credo che i gestori di segnali siano ricorsivi.
roccioso,

OK, nel tuo esempio non intrappolo affatto (SIG) INT, che è in realtà quello che voglio per fare un po 'di pulizia anche se l'utente sta uscendo dal mio script interattivo tramite ctrl-c.
phk,

@phk Ok. Penso che tu abbia l'idea di come forzare l'uscita per restituire 0 o qualche altro valore che quello che ho raccolto era il problema. Presumo che sarai in grado di regolare il codice per inserire l'impostazione EXIT della trap all'interno del tuo vero gestore di pulizia che viene chiamato da SIGINT.
roccioso,

@rocky: il mio obiettivo era quello di avere un gestore trap mentre non cambiavo il codice di uscita che avrei normalmente senza il gestore. Ora potrei semplicemente restituire il codice di uscita che otterresti normalmente, ma il problema era che non conosco l'esatto codice di uscita in questi casi (come visto nel thread a cui mi sono collegato e il post da meuh è abbastanza complicato). Il tuo post è stato ancora utile, non ho pensato di ridefinire dinamicamente il gestore di trap.
phk,

9

In realtà, l'interruzione interna di bash readsembra essere leggermente diversa dall'interruzione di un comando eseguito da bash. Normalmente, quando si entra trap, $?è impostato e è possibile conservarlo e uscire con lo stesso valore:

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

Se lo script viene interrotto durante l'esecuzione di un comando simile sleep o anche di un incorporato wait, vedrai

130 SIGINT
130 EXIT

e il codice di uscita è 130. Comunque, per read -p, sembra $?essere 0 (sulla mia versione di bash 4.3.42 comunque).


La gestione dei segnali durante readpotrebbe essere in corso di elaborazione , in base al file delle modifiche nella mia versione ... (/ usr / share / doc / bash / CHANGES)

cambia tra questa versione, bash-4.3-alpha e la versione precedente, bash-4.2-release.

  1. Nuove funzionalità in Bash

    r. In modalità Posix, `read 'è interrompibile da un segnale intrappolato. Dopo aver eseguito il gestore trap, read restituisce il segnale 128+ e getta via qualsiasi input parzialmente letto.


OK, è davvero strano. È 130 sulla shell interattiva su bash 4.3.42 (sotto cygwin), 0 sotto la stessa shell quando in uno script e in modalità POSIX o no non fa alcuna differenza. Ma poi sotto dash e busybox è sempre l'1.
phk

Ho provato la modalità POSIX con una trap che non esce e riavvia read, come indicato nel file CHANGES (aggiunto alla mia risposta). Quindi, potrebbe essere un lavoro in corso.
Meuh

Il codice di uscita 130 non è portatile al 100%. Mentre Bourne Shell usa 128 + signocome codice di uscita per i segnali, ksh93 usa 256 + signo. POSIX dice: qualcosa sopra 128 ...
schily

@schily True, come indicato nella discussione a cui mi sono collegato nel post originale ( unix.stackexchange.com/questions/99112 ).
phk,

6

Qualsiasi normale codice di uscita del segnale sarà disponibile $?all'entrata del gestore 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 esiste una trappola EXIT separata, è possibile utilizzare la stessa metodologia: mantenere immediatamente lo stato di uscita passato dal gestore del segnale (se presente) ripulire, quindi restituire lo stato di uscita salvato.


Questo vale per la maggior parte delle conchiglie? Molto bella. localnon è POSIX però.
phk,

1
Modificato. Non ho testato in altro {ba,z}sh. AFAIK, è il massimo che si può fare, indipendentemente.
Tom Hale,

Questo non funziona nel trattino ( $?è 1 qualunque sia il segnale ricevuto).
MoonSweep

2

Restituire un codice di errore non è sufficiente per simulare un'uscita di SIGINT. Sono sorpreso che nessuno lo abbia menzionato finora. Ulteriori letture: https://www.cons.org/cracauer/sigint.html

Il modo corretto è:

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

Funziona con Bash, Dash e zsh. Per ulteriore portabilità, è necessario utilizzare le specifiche del segnale numerico (d'altra parte, zsh prevede un parametro stringa per il killcomando ...)

Si noti inoltre il trattamento speciale del EXITsegnale. Ciò è dovuto ad alcune shell (ovvero Bash) che eseguono trap anche EXITper qualsiasi segnale (dopo possibilmente definite trap su quel segnale). Il reset della EXITtrappola impedisce questo.

Esecuzione del codice solo all'uscita "normale"

Il controllo [ $sig = EXIT ]consente di eseguire il codice solo all'uscita normale (non segnalata). Tuttavia, tutti i segnali devono avere trappole che alla fine resettano la trappola EXIT; normal_exit_only_cleanupverrà anche chiamato per i segnali che non lo fanno. Sarà anche eseguito da un'uscita attraverso set -e. Questo può essere risolto intercettando ERR(che non è supportato da Dash) e aggiungendo un segno di spunta [ $sig = ERR ]prima di kill.

Versione solo Bash semplificata

D'altra parte, questo comportamento significa che in Bash puoi semplicemente farlo

trap cleanup EXIT

per eseguire solo un po 'di codice di pulizia e mantenere lo stato di uscita.

modificato

  • Elaborare il comportamento di Bash di "EXIT intrappola tutto"

  • Rimuovi il segnale KILL, che non può essere intrappolato

  • Rimuovere i prefissi SIG dai nomi dei segnali

  • Non tentare di farlo kill -s EXIT

  • Prendi set -ein considerazione / ERR


Non vi è alcun requisito generale che un programma che intercetta i segnali venga chiuso in modo da preservare il fatto che abbia ricevuto un segnale. Ad esempio, un programma può dichiarare che l'invio SIGINTè il modo per chiuderlo e può decidere di uscire con 0 se è riuscito a terminare senza errori o un altro codice di errore se non si chiude in modo pulito. Caso in questione: top. Esegui top; echo $?e quindi premi Ctrl-C. Lo stato scaricato sullo schermo sarà 0.
Louis

1
Grazie per averlo sottolineato. Tuttavia, il poster ha chiesto specificamente di mantenere il codice di uscita.
philipp2100,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.