Codice di uscita predefinito al termine del processo?


54

Quando un processo viene interrotto con un segnale gestibile come SIGINTo SIGTERMma non gestisce il segnale, quale sarà il codice di uscita del processo?

Che dire di segnali non gestibili come SIGKILL?

Da quello che posso dire, uccidere un processo con SIGINTrisultati probabili nel codice di uscita 130, ma varierebbe a seconda dell'implementazione del kernel o della shell?

$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130

Non sono sicuro di come testerei gli altri segnali ...

$ ./myScript &
$ killall myScript
$ echo $?
0  # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0  # same problem

1
i tuoi killall myScriptlavori, quindi il ritorno del killall (e non dello script!) è 0. Puoi posizionare un kill -x $$[x essendo il numero del segnale e $$ solitamente espanso dalla shell al PID di quello script (funziona in sh, bash, ...)] all'interno dello script e quindi prova qual era il suo nucleo di uscita.
Olivier Dulac il


commento sulla semi-domanda: non mettere myScript in background. (omettere &). Invia il segnale da un altro processo di shell (in un altro terminale), quindi puoi usarlo al $?termine di myScript.
MattBianco,

Risposte:


61

I processi possono chiamare la chiamata di _exit()sistema (su Linux, vedi anche exit_group()) con un argomento intero per segnalare un codice di uscita al loro genitore. Sebbene sia un numero intero, solo i 8 bit meno significativi sono disponibili per il genitore (ad eccezione di quando si utilizza waitid()o il gestore su SIGCHLD nel genitore per recuperare quel codice , sebbene non su Linux).

Il genitore farà tipicamente un wait()o waitpid()per ottenere lo stato del proprio figlio come intero (anche se waitid()con semantica un po 'diversa può essere usato anche).

Su Linux e la maggior parte degli Unices, se il processo è terminato normalmente, i bit da 8 a 15 di quel numero di stato conterranno il codice di uscita passato a exit(). In caso contrario, i 7 bit meno significativi (da 0 a 6) conterranno il numero del segnale e il bit 7 verrà impostato in caso di dump di un core.

perl's $?, ad esempio, contiene quel numero come set per waitpid():

$ perl -e 'system q(kill $$); printf "%04x\n", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04x\n", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04x\n", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status

Le shell tipo Bourne inoltre rendono lo stato di uscita dell'ultimo comando di esecuzione nella propria $?variabile. Tuttavia, non contiene direttamente il numero restituito da waitpid(), ma una trasformazione su di esso, ed è diverso tra le shell.

La cosa comune tra tutte le shell è che $?contiene gli 8 bit più bassi del codice di uscita (il numero passato a exit()) se il processo è terminato normalmente.

La differenza è quando il processo è terminato da un segnale. In tutti i casi, e ciò è richiesto da POSIX, il numero sarà maggiore di 128. POSIX non specifica quale possa essere il valore. In pratica, tuttavia, in tutte le shell tipo Bourne che conosco, i 7 bit più bassi $?conterranno il numero del segnale. Ma dov'è nil numero del segnale,

  • in cenere, zsh, pdksh, bash, la shell Bourne, $?è 128 + n. Ciò significa che in quei gusci, se si ottiene una $?di 129, non si sa se è perché il processo è terminato con exit(129)o se sia stato ucciso dal segnale 1( HUPsulla maggior parte dei sistemi). Ma la logica è che le shell, quando escono da sole, restituiscono di default lo stato di uscita dell'ultimo comando uscito. Assicurandosi che $?non sia mai maggiore di 255, ciò consente di avere uno stato di uscita coerente:

    $ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill \$\$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill \$\$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
    
  • ksh93, $?È 256 + n. Ciò significa che da un valore di $?te puoi distinguere tra un processo ucciso e non ucciso. Versioni più recenti di ksh, all'uscita, se $?maggiore di 255, si uccide con lo stesso segnale per poter riportare lo stesso stato di uscita al suo genitore. Anche se sembra una buona idea, ciò significa che kshgenererà un dump core aggiuntivo (potenzialmente sovrascrivendo l'altro) se il processo è stato interrotto da un segnale di generazione del core:

    $ ksh -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    ksh: 16828: Terminated
    10f # 256 + 15
    $ ksh -c 'sh -c "kill -ILL \$\$"; exit'; printf '%x\n' "$?"
    ksh: 16816: Illegal instruction(coredump)
    Illegal instruction(coredump)
    104 # 256 + 15, ksh did indeed kill itself so as to report the same
        # exit status as sh. Older versions of `ksh93` would have returned
        # 4 instead.
    

    Dove si potrebbe anche dire che c'è un bug è che si ksh93uccide anche se $?deriva return 257da una funzione eseguita:

    $ ksh -c 'f() { return "$1"; }; f 257; exit'
    zsh: hangup     ksh -c 'f() { return "$1"; }; f 257; exit'
    # ksh kills itself with a SIGHUP so as to report a 257 exit status
    # to its parent
    
  • yash. yashoffre un compromesso. Ritorna 256 + 128 + n. Ciò significa che possiamo anche distinguere tra un processo interrotto e uno che è terminato correttamente. E all'uscita, riferirà 128 + nsenza suicidarsi e con gli effetti collaterali che può avere.

    $ yash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    18f # 256 + 128 + 15
    $ yash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    8f  # that's from a exit(143), yash was not killed
    

Per ottenere il segnale dal valore di $?, il modo portatile è usare kill -l:

$ /bin/kill 0
Terminated
$ kill -l "$?"
TERM

(per la portabilità, non utilizzare mai numeri di segnale, solo nomi di segnali)

Sui fronti non Bourne:

  • csh/ tcshe lo fishstesso della shell Bourne tranne per il fatto che lo stato è in $statusinvece di $?(nota che zshimposta anche $statusper la compatibilità con csh(oltre a $?)).
  • rc: lo stato di uscita è $statusanche presente, ma quando viene ucciso da un segnale, quella variabile contiene il nome del segnale (come sigtermo sigill+corese è stato generato un core) anziché un numero, che è ancora un'altra prova del buon design di quella shell .
  • es. lo stato di uscita non è una variabile. Se ti interessa, esegui il comando come:

    status = <={cmd}
    

    che restituirà un numero sigtermo sigsegv+coresimili in rc.

Forse per completezza, dobbiamo menzionare zsh's $pipestatuse bash' s $PIPESTATUSarray che contengono lo stato di uscita dei componenti dell'ultima pipeline.

E anche per completezza, quando si tratta di funzioni shell e file di origine, le funzioni predefinite ritornano con lo stato di uscita dell'ultima esecuzione del comando, ma possono anche impostare esplicitamente uno stato di ritorno con l' returnintegrato. E vediamo alcune differenze qui:

  • bashe mksh(da R41, una regressione ^ Wchange apparentemente introdotta intenzionalmente ) troncerà il numero (positivo o negativo) a 8 bit. Ad esempio, return 1234verrà impostato $?su 210, return -- -1impostato $?su 255.
  • zshe pdksh(e derivati ​​diversi da mksh) consentire qualsiasi numero intero decimale a 32 bit con segno (da -2 31 a 2 31 -1) (e troncare il numero a 32 bit).
  • ashe yashconsentire qualsiasi numero intero positivo compreso tra 0 e 2 31 -1 e restituire un errore per qualsiasi numero.
  • ksh93per return 0di return 320set $?come è, ma per tutto il resto, Tronca a 8 bit. Attenzione come già detto che restituire un numero compreso tra 256 e 320 potrebbe causare kshl'uccisione di se stesso all'uscita.
  • rce esconsentire la restituzione di qualsiasi elenco uniforme.

Si noti inoltre che alcune shell usano anche valori speciali di $?/ $statusper segnalare alcune condizioni di errore che non sono lo stato di uscita di un processo, come 127o 126per comando non trovato o non eseguibile (o errore di sintassi in un file di origine) ...


1
an exit code to their parente to get the *status* of their child. hai aggiunto enfasi sullo "stato". È exit codee *status*la stessa cosa? Caso sì, qual è l'origine di avere due nomi? Caso diverso, potresti dare definizione / riferimento allo stato?
n611x007,

2
Ci sono 3 numeri qui. Il codice di uscita : il numero passato a exit(). Lo stato di uscita : il numero ottenuto con il waitpid()quale include il codice di uscita, il numero del segnale e se è stato scaricato un core. E il numero che alcune shell rendono disponibili in una delle loro variabili speciali ( $?, $status) che è una trasformazione dello stato di uscita in modo tale da contenere il codice di uscita nel caso in cui ci fosse una normale terminazione, ma porta anche informazioni sul segnale se il processo è stato interrotto (quello è anche generalmente chiamato stato di uscita ). Questo è tutto spiegato nella mia risposta.
Stéphane Chazelas,

1
Vedo grazie! Apprezzo sicuramente questa nota esplicita della distinzione qui. Queste espressioni sull'uscita sono usate in modo così intercambiabile in alcuni punti che vale la pena farlo. La variante della variabile shell ha anche un nome (generale)? Quindi suggerirei di chiarirlo esplicitamente prima di entrare nei dettagli sulle shell. Suggerirei di inserire la spiegazione (dal tuo commento) dopo il tuo primo o secondo paragrafo.
n611x007,

1
Puoi indicare la citazione POSIX che dice che i primi 7 bit sono il segnale? Tutto quello che ho trovato è stata la > 128parte: "Lo stato di uscita di un comando che è terminato perché ha ricevuto un segnale deve essere riportato come maggiore di 128". pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

1
@cuonglm, non penso che sia disponibile pubblicamente altrove su HTTP, puoi ancora ottenerlo da Gmane su NNTP. Cerca l'id messaggio efe764d811849b34eef24bfb14106f61@austingroupbugs.net(dal 06/05/2015) oXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas il

23

Quando un processo viene chiuso, restituisce un valore intero al sistema operativo. Sulla maggior parte delle varianti unix, questo valore è assunto modulo 256: tutto tranne i bit di ordine inferiore viene ignorato. Lo stato di un processo figlio viene restituito al suo genitore tramite un numero intero a 16 bit in cui

  • i bit 0–6 (i 7 bit di ordine inferiore) sono il numero del segnale utilizzato per terminare il processo, oppure 0 se il processo è uscito normalmente;
  • il bit 7 viene impostato se il processo è stato interrotto da un segnale e ha scaricato il core;
  • i bit 8–15 sono il codice di uscita del processo se il processo è uscito normalmente o 0 se il processo è stato interrotto da un segnale.

Lo stato viene restituito dalla waitchiamata di sistema o da uno dei suoi fratelli. POSIX non specifica la codifica esatta dello stato di uscita e il numero del segnale; fornisce solo

  • un modo per stabilire se lo stato di uscita corrisponde a un segnale o a un'uscita normale;
  • un modo per accedere al codice di uscita, se il processo è terminato normalmente;
  • un modo per accedere al numero del segnale, se il processo è stato interrotto da un segnale.

A rigor di termini, non esiste un codice di uscita quando un processo viene interrotto da un segnale: ciò che esiste invece è uno stato di uscita .

In uno script di shell, lo stato di uscita di un comando viene riportato tramite la variabile speciale $?. Questa variabile codifica lo stato di uscita in modo ambiguo:

  • Se il processo è uscito normalmente, allora $?è il suo stato di uscita.
  • Se il processo è stato interrotto da un segnale, allora $?è 128 più il numero del segnale sulla maggior parte dei sistemi. $?In questo caso POSIX richiede solo un numero maggiore di 128; ksh93 aggiunge 256 invece di 128. Non ho mai visto una variante unix che abbia fatto altro che aggiungere una costante al numero del segnale.

Quindi in uno script di shell non si può dire in modo conclusivo se un comando è stato ucciso da un segnale o chiuso con un codice di stato maggiore di 128, tranne con ksh93. È molto raro che i programmi escano con codici di stato superiori a 128, in parte perché i programmatori lo evitano a causa $?dell'ambiguità.

SIGINT è il segnale 2 sulla maggior parte delle varianti unix, quindi $?è 128 + 2 = 130 per un processo che è stato ucciso da SIGINT. Vedrai 129 per SIGHUP, 137 per SIGKILL, ecc.


Molto più formulata e più precisa della mia, anche se in sostanza dice le stesse cose. Potresti voler chiarire che $?è solo per shell tipo Bourne. Vedi anche yashper un comportamento diverso (ma ancora POSIX). Sempre secondo POSIX + XSI (Unix), a kill -2 "$pid"invierà un SIGINT al processo, ma il numero del segnale effettivo potrebbe non essere 2, quindi $? non sarà necessariamente 128 + 2 (o 256 + 2 o 384 + 2), anche se kill -l "$?"tornerà INT, motivo per cui consiglierei la portabilità di non fare riferimento ai numeri stessi.
Stéphane Chazelas,

8

Dipende dalla tua shell. Dalla bash(1)pagina man, sezione SHELL GRAMMAR , sottosezione Comandi semplici :

Il valore di ritorno di un comando semplice è [...] 128+ n se il comando è terminato dal segnale n .

Poiché SIGINTsul tuo sistema è presente il segnale numero 2, il valore di ritorno è 130 quando viene eseguito in Bash.


1
Come al mondo lo trovi o sai persino dove cercare? Mi inchino davanti al tuo genio.
Cory Klein,

1
@CoryKlein: esperienza, soprattutto. Oh, e probabilmente vorrai anche la signal(7)pagina man.
Ignacio Vazquez-Abrams,

roba forte; sai se ho incluso i file in C con quelle costanti per caso? +1
Rui F Ribeiro,

@CoryKlein Perché non hai selezionato questa come risposta corretta?
Rui F Ribeiro,

3

Sembra essere il posto giusto per menzionare che SVr4 ha introdotto waitid () nel 1989, ma nessun programma importante sembra usarlo finora. waitid () consente di recuperare tutti i 32 bit dal codice exit ().

Circa 2 mesi fa, ho riscritto la parte wait / job control di Bourne Shell per utilizzare waitid () invece di waitpid (). Ciò è stato fatto per rimuovere la limitazione che maschera il codice di uscita con 0xFF.

L'interfaccia waitid () è molto più pulita rispetto alle precedenti implementazioni wait () ad eccezione della chiamata cwait () di UNOS del 1980.

Potresti essere interessato a leggere la pagina man su:

http://schillix.sourceforge.net/man/man1/bosh.1.html

e controlla la sezione "Sostituzione parametri" attualmente a pagina 8.

Le nuove variabili .sh. * Sono state introdotte per l'interfaccia waitid (). Questa interfaccia non ha più significati ambigui per i numeri noti per $? e rendere l'interfaccia molto più semplice.

Nota che devi avere un waitid () conforme a POSIX per poter usare questa funzione, quindi Mac OS X e Linux attualmente non lo offrono, ma waitid () viene emulato sulla chiamata waitpid (), quindi su un piattaforma non POSIX otterrai comunque solo 8 bit dal codice di uscita.

In breve: .sh.status è il codice numerico di uscita, .sh.code è il motivo numerico di uscita.

Per una migliore portabilità, esiste: .sh.codename per la versione testuale del motivo di uscita, ad esempio "DUMPED" e .sh.termsig, il nome singolare del segnale che ha terminato il processo.

Per un migliore utilizzo, esistono due valori .sh.codename non correlati all'uscita: "NOEXEC" e "NOTFOUND" che vengono utilizzati quando un programma non può essere avviato affatto.

FreeBSD ha corretto il bug di waitid () kerlnel entro 20 ore dalla mia segnalazione, Linux non ha ancora iniziato con la sua correzione. Spero che 26 anni dopo l'introduzione di questa funzione che è ora in POSIX, tutti i sistemi operativi la supporteranno presto.


Una risposta correlata è unix.stackexchange.com/a/453432/5132 .
JdeBP,
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.