Utilizzo di codici "riservati" per lo stato di uscita degli script di shell


15

Recentemente mi sono imbattuto in questo elenco di codici di uscita con significati speciali dalla Guida avanzata di script Bash. Si riferiscono a questi codici come riservati e raccomandano di:

Secondo la tabella sopra, i codici di uscita 1-2, 126-165 e 255 hanno significati speciali e dovrebbero quindi essere evitati per i parametri di uscita specificati dall'utente.

Qualche tempo fa, ho scritto uno script che utilizzava i seguenti codici di stato di uscita:

  • 0 - successo
  • 1 - nome host errato
  • 2 - argomenti non validi specificati
  • 3 - privilegi utente insufficienti

Quando ho scritto lo script non ero a conoscenza di alcun codice di uscita speciale, quindi ho semplicemente iniziato da 1 per la prima condizione di errore e ho incrementato lo stato di uscita per ciascun tipo di errore successivo.

Ho scritto lo script con l'intenzione che in un secondo momento potrebbe essere chiamato da altri script (che potrebbero verificare la presenza di codici di uscita diversi da zero). Non l'ho ancora fatto; finora ho eseguito lo script solo dalla mia shell interattiva (Bash) e mi chiedevo quali / se eventuali problemi potessero essere causati usando i miei codici di uscita personalizzati. Quanto è rilevante / importante la raccomandazione della Guida avanzata di script Bash?

Non sono riuscito a trovare consigli corroboranti nella documentazione di Bash; la sua sezione sullo stato di uscita elenca semplicemente i codici di uscita usati da Bash ma non afferma che nessuno di questi è riservato o mette in guardia dal loro utilizzo per i propri script / programmi.


6
Io e altri riteniamo che l'ABSG sia generalmente di bassa qualità. A mio avviso, l'autore della pagina che hai collegato sta facendo un'asserzione non supportata secondo cui i codici di uscita elencati sono riservati in base, a quanto pare, al fatto che la shell stessa li utilizza per significati specifici. Ci sono stati tentativi di creare standard per gli script, nessuno dei quali ha avuto alcun successo. L'importante è documentare i codici di errore che scegli in modo che i consumatori dei tuoi script (ad esempio altri script) sappiano cosa fare in base a essi.
In pausa fino a ulteriore avviso.

@DennisWilliamson Se pubblichi il tuo commento come risposta, sarei felice di votarlo; Ho già votato tutte le altre risposte poiché ho trovato ciascuna di esse utile. Mentre la tua risposta è simile nei contenuti a quella di David King (e in misura minore di Zwol), dichiari esplicitamente che non ci sono prove per l'affermazione nella citazione dell'ABSG.
Anthony G - giustizia per Monica,

1
Grazie per l'offerta, ma credo che il mio commento dovrebbe rimanere tale.
In pausa fino a ulteriore avviso.

Da allora ho scoperto che le specifiche POSIX includono consigli simili, quindi ho aggiunto tali informazioni alla mia risposta (contenente i risultati della mia ricerca da quando ho posto questa domanda).
Anthony G - giustizia per Monica,

Risposte:


10

Ci sono stati diversi tentativi di standardizzare i significati dei codici di uscita del processo. Oltre a quello che menzioni, conosco:

  • i BSD hanno sysexits.hche definisce i significati per i valori da 64 in poi.

  • I grepdocumenti GNU che indicano il codice di uscita 0 indicano che è stata trovata almeno una corrispondenza, 1 indica che non è stata trovata alcuna corrispondenza e 2 indica che si è verificato un errore I / O; questa convenzione è ovviamente utile anche per altri programmi per i quali la distinzione tra "nulla è andato storto ma non ho trovato nulla" e "si è verificato un errore I / O" è significativa.

  • Molte implementazioni della funzione di libreria C systemutilizzano il codice di uscita 127 per indicare che il programma non esiste o non è stato avviato.

  • Su Windows, i NTSTATUScodici (che sono scomodamente sparsi in tutto lo spazio dei numeri a 32 bit) possono essere usati come codici di uscita, in particolare quelli che indicano che un processo è stato chiuso a causa di un comportamento catastrofico (ad es STATUS_STACK_OVERFLOW.).

Non puoi contare su alcun programma dato obbedendo a nessuna di queste convenzioni. L'unica regola affidabile è che il codice di uscita 0 è successo e qualsiasi altra cosa è una sorta di errore. (Si noti che nonEXIT_SUCCESS è garantito che C89 abbia il valore zero; tuttavia, è necessario comportarsi in modo identico anche se i valori non sono uguali.)exit(0)exit(EXIT_SUCCESS)


Grazie. È stata difficile scegliere una risposta rispetto alle altre, ma sto accettando questa poiché ha risposto alla mia domanda fornendo anche un ampio panorama dei diversi codici di uscita in uso (con collegamenti pertinenti): merita più dei 3 voti attualmente ha.
Anthony G - giustizia per Monica,

11

Nessun codice di uscita ha un significato speciale, ma il valore in $?può avere un significato speciale.

Il modo in cui Bourne Shell e ksh93 gestiscono e inoltrano codici di uscita e situazioni di errore alla variabile shell $?è il problema. Contrariamente a quanto elencato, solo i seguenti valori $?hanno un significato speciale:

  • 126 Impossibile eseguire il file binario anche se esiste
  • 127 Il file binario specificato non esiste
  • Lo stato di uscita 128 era == 0 ma esiste qualche problema non specificato

Inoltre, esiste una shell non specificata e un intervallo di $?codici specifico per piattaforma > 128 che è riservato per un programma che è stato interrotto da un segnale:

  • Bourne Shell bash e ksh88 usano 128 + numero di segnale
  • ksh93 utilizza 256 + numero di segnale.

Altri valori non danno problemi in quanto possono essere distinti dai $?valori speciali della shell .

In particolare, i valori 1 e 2 non sono usati per condizioni speciali ma sono solo codici di uscita usati dai comandi integrati che potrebbero agire allo stesso modo quando non sono integrati. Quindi sembra che il puntatore alla guida agli script di bash che hai fornito non sia un buon manuale in quanto elenca solo i codici usati da bash senza commentare se un codice specifico è un valore speciale che dovrebbe essere evitato per i propri script.

Le versioni più recenti di Bourne Shell utilizzano waitid()invece di waitpid()attendere la waitid()chiusura del programma e (introdotto nel 1989 per SVr4) utilizza una migliore interfaccia syscall (simile a quella già utilizzata da UNOS nel 1980).

Poiché le versioni più recenti di Bourne Shell codificano il motivo di uscita in una variabile separata ${.sh.code}/ ${.sh.codename}rispetto al codice di uscita in ${.sh.status}/ ${.sh.termsig}, vedere http://schillix.sourceforge.net/man/man1/bosh.1.html , il codice di uscita non è sovraccarico con stati speciali e, come risultato dell'uso di `waitid (), la Bourne Shell ora supporta la restituzione di tutti i 32 bit del codice di uscita, non solo degli 8 bit bassi.

A proposito: fai attenzione a non exit(256)o simile da un programma C o da uno script di shell, in quanto ciò si traduce in $?interpretazione di 0 in una shell classica.


2
A proposito: ho fatto una segnalazione di bug contro FreeBSD e il kernel Linux per questo waitid()bug verso la fine di maggio. Le persone di FreeBSD hanno risolto il problema entro 20 ore, le persone di Linux non sono interessate a correggere il loro bug. ... e la gente di Cygwin afferma di essere compatibile con Linux bug; bug
schily

2
Questo comportamento è richiesto dalla specifica Unix singola. C'è un valore a 32 bit, sì, ma quel valore contiene un bitfield a 8 bit contenente gli 8 bit bassi del valore da _exit. Ti preghiamo di collegare la segnalazione di bug di FreeBSD a cui ti riferisci, forse sto fraintendendo il problema che descrivi.
Casuale 832

2
L'OP ha taggato la domanda con bash e menzionato Bash nel testo della domanda. Bash è una shell derivata da Bourne. Non supporta le ${.sh.}variabili. È vero, tuttavia, che dici "Bourne" e non "derivato da Bourne" (anche se includi ksh93).
In pausa fino a ulteriore avviso.

2
Questa risposta sembra essere molto specifica per la tua particolare variante di alcuni Unix derivati ​​da SVR4. Si prega di essere più chiari su ciò che è portatile e cosa non lo è, tenendo presente che non esiste una cosa come "la" shell Bourne, a meno che non intendiate quella che era in V7.
zwol,

4
Al contrario, credo che tu stia sottovalutando la gamma di variazioni qui, in particolare la variazione storica. Sembra che si /bin/shpossa fare affidamento sul comportamento coerente con questi codici di uscita speciali multipiattaforma, il che non è vero. (Non mi interessa se si /bin/shpuò dire che un determinato sistema sia una "vera shell Bourne". Molto più importante sapere che nulla di tutto ciò è in POSIX, e che la maggior parte delle cose che citi come "sistemi Unix reali" non ' fornire comunque un POSIX conforme /bin/sh.)
zwol

6

Per gli script di shell, a volte ho nel sorgente l'equivalente della sysexist.hshell con i codici di uscita riservati alla shell (con prefisso S_EX_), che ho chiamatoexit.sh

È fondamentalmente:

EX_OK=0 # successful termination 
EX__BASE=64     # base value for error messages 
EX_USAGE=64     # command line usage error 
EX_DATAERR=65   # data format error 
EX_NOINPUT=66   # cannot open input 
EX_NOUSER=67    # addressee unknown 
EX_NOHOST=68    # host name unknown 
EX_UNAVAILABLE=69       # service unavailable 
EX_SOFTWARE=70  # internal software error 
EX_OSERR=71     # system error (e.g., can't fork) 
EX_OSFILE=72    # critical OS file missing 
EX_CANTCREAT=73 # can't create (user) output file 
EX_IOERR=74     # input/output error 
EX_TEMPFAIL=75  # temp failure; user is invited to retry 
EX_PROTOCOL=76  # remote error in protocol 
EX_NOPERM=77    # permission denied 
EX_CONFIG=78    # configuration error 
EX__MAX=78      # maximum listed value 

#System errors
S_EX_ANY=1      #Catchall for general errors
S_EX_SH=2       #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute         Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit       exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)                                                                                        
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)                               
#255*   Exit status out of range        exit -1 exit takes only integer args in the range 0 - 255              
S_EX_HUP=129                                                                                                   
S_EX_INT=130   
#...

E può essere generato con:

#!/bin/sh
src=/usr/include/sysexits.h
echo "# Generated from \"$src\"" 
echo "# Please inspect the source file for more detailed descriptions"
echo
< "$src" sed -rn 's/^#define  *(\w+)\s*(\d*)/\1=\2/p'| sed 's:/\*:#:; s:\*/::'
cat<<'EOF'

#System errors
S_EX_ANY=1  #Catchall for general errors
S_EX_SH=2   #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute     Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit   exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)
#255*   Exit status out of range    exit -1 exit takes only integer args in the range 0 - 255
EOF
$(which kill) -l |tr ' ' '\n'| awk '{ printf "S_EX_%s=%s\n", $0, 128+NR; }'

Non lo uso molto, però, ma quello che uso è una funzione shell che inverte i codici di errore nei loro formati di stringa. L'ho chiamato exit2str. Supponendo che tu abbia chiamato il exit.shgeneratore sopra exit.sh.sh, il codice per exit2strpuò essere generato con ( exit2str.sh.sh):

#!/bin/sh
echo '
exit2str(){
  case "$1" in'
./exit.sh.sh | sed -nEe's|^(S_)?EX_(([^_=]+_?)+)=([0-9]+).*|\4) echo "\1\2";;|p'
echo "
  esac
}"

Lo uso nella PS1mia shell interattiva in modo che dopo ogni comando che eseguo, posso vedere il suo stato di uscita e la sua forma di stringa (se ha una forma di stringa nota):

[15:58] pjump@laptop:~ 
(0=OK)$ 
[15:59] pjump@laptop:~ 
(0=OK)$ fdsaf
fdsaf: command not found
[15:59] pjump@laptop:~ 
(127=S_NOENT)$ sleep
sleep: missing operand
Try 'sleep --help' for more information.
[15:59] pjump@laptop:~ 
(1=S_ANY)$ sleep 100
^C
[15:59] pjump@laptop:~ 
(130=S_INT)$ sleep 100
^Z
[1]+  Stopped                 sleep 100
[15:59] pjump@laptop:~ 
(148=S_TSTP)$

Per ottenerli, è necessario un insourcable per la funzione exit2str:

$ ./exit2str.sh.sh > exit2str.sh #Place this somewhere in your PATH

e poi usalo nel tuo ~/.bashrcper salvare e tradurre il codice di uscita su ogni prompt dei comandi e visualizzarlo come tuo ( PS1):

    # ...
    . exit2str.sh
PROMPT_COMMAND='lastStatus=$(st="$?"; echo -n "$st"; str=$(exit2str "$st") && echo "=$str"); # ...'
    PS1="$PS1"'\n($lastStatus)\$'
    # ...                                                                                   

È abbastanza utile per osservare come alcuni programmi seguono le convenzioni del codice di uscita e altri no, per conoscere le convenzioni del codice di uscita o semplicemente per essere in grado di vedere cosa sta succedendo più facilmente. Avendolo usato da un po 'di tempo, posso dire che molti script di shell orientati al sistema seguono le convenzioni. EX_USAGEè particolarmente abbastanza comune, anche se altri codici, non molto. Cerco di seguire le convenzioni di volta in volta, sebbene ci sia sempre $S_EX_ANY(1) per i pigri (io sono uno).


Mi chiedo se ci sia qualcosa di simile a una mappatura tra un codice errno e un codice di uscita da utilizzare se l'errore segnalato con quel codice errno provoca un'uscita di errore. Potrei aver bisogno di trovare una mappatura ragionevole.
PSkocik,

1
Wow! Non mi aspettavo una risposta così elaborata. Lo proverò sicuramente come un buon modo per vedere come si comportano i diversi comandi. Grazie.
Anthony G - giustizia per Monica,

4

Fintanto che documenterai i tuoi codici di uscita in modo da ricordarli tra un anno, quando dovrai tornare e modificare la sceneggiatura, starai bene. L'idea di "codici di uscita riservati" non si applica più che per dire che è consuetudine utilizzare 0come codice di successo e qualsiasi altra cosa come codice di errore.


4

Il miglior riferimento che ho trovato è stato questo: http://tldp.org/LDP/abs/html/exitcodes.html

Secondo questo:

1 è una catchall generale per errori, e l'ho sempre visto usato per errori definiti dall'utente.

2 è per uso improprio dei componenti incorporati della shell, come un errore di sintassi

Per rispondere direttamente alla tua domanda, lo script andrà bene utilizzando i codici di errore riservati, funzionerà come previsto supponendo che tu gestisca l'errore in base al codice di errore = 1/2/3.

Tuttavia, potrebbe essere fonte di confusione se incontri qualcuno che conosce e utilizza i codici di errore riservati, il che sembra abbastanza raro.

Un'altra opzione a tua disposizione è quella di fare eco all'errore se ce n'è uno e poi uscire, supponendo che il tuo script segua la convenzione Linux di "nessuna notizia è una buona notizia" e l'eco non è nulla di successo.

if [ $? -ne 0 ];then
    echo "Error type"
    exit 1
fi

2

Sulla base delle risposte che ho ricevuto (è stato difficile sceglierne uno rispetto agli altri), non è dannoso indicare alcuni tipi di errori utilizzando un codice di uscita che utilizza anche Bash. Bash (o qualsiasi altra shell Unix) non farà nulla di speciale (come l'esecuzione di gestori di eccezioni) se uno script utente esce con uno di questi codici di errore.

Sembra che l'autore di Advanced Bash-Scripting Guide sia d'accordo con i tentativi di BSD di standardizzare i codici di uscita ( sysexits.h) e sta semplicemente raccomandando che quando gli utenti scrivono script di shell, non specificano già codici di uscita in conflitto con codici di uscita predefiniti in uso, cioè limitano i loro codici di uscita personalizzati ai 50 codici di stato disponibili nell'intervallo 64-113.

Apprezzo l'idea (e la logica) ma avrei preferito se l'autore fosse più esplicito che non è dannoso ignorare il consiglio - a parte i casi in cui il consumatore di una sceneggiatura sta verificando errori come l'esempio citato di 127 ( command not found).

Specifiche POSIX pertinenti

Ho studiato ciò che POSIX ha da dire sui codici di uscita e le specifiche POSIX sembrano concordare con l'autore della Guida avanzata di script Bash. Ho citato le specifiche POSIX pertinenti (sottolineatura mia):

Stato di uscita per i comandi

Ogni comando ha uno stato di uscita che può influenzare il comportamento di altri comandi della shell. Lo stato di uscita dei comandi che non sono utility è documentato in questa sezione. Lo stato di uscita delle utility standard è documentato nelle rispettive sezioni.

Se non viene trovato un comando, lo stato di uscita deve essere 127. Se viene trovato il nome del comando, ma non è un'utilità eseguibile, lo stato di uscita deve essere 126. Le applicazioni che invocano utilità senza utilizzare la shell devono utilizzare questi valori di stato di uscita per segnalare errori simili.

Se un comando fallisce durante l'espansione o il reindirizzamento delle parole, il suo stato di uscita deve essere maggiore di zero.

Internamente, al fine di decidere se un comando esce con uno stato di uscita diverso da zero, la shell deve riconoscere l'intero valore di stato recuperato per il comando dall'equivalente della macro WEXITSTATUS della funzione wait () (come definito nel volume Interfacce di sistema di POSIX.1-2008). Nel segnalare lo stato di uscita con il parametro speciale '?', La shell deve riportare tutti gli otto bit di stato di uscita disponibili. Lo stato di uscita di un comando che è terminato perché ha ricevuto un segnale deve essere riportato come maggiore di 128.

L' exitutilità

Come spiegato in altre sezioni, alcuni valori dello stato di uscita sono stati riservati per usi speciali e dovrebbero essere utilizzati dalle applicazioni solo per tali scopi:

  • 126 - È stato trovato un file da eseguire, ma non era un'utilità eseguibile.
  • 127 - Non è stata trovata un'utilità da eseguire.
  • >128 - Un comando è stato interrotto da un segnale.

Ulteriori informazioni

Per quello che vale, sono stato in grado di verificare tutti tranne uno dell'elenco dei codici di uscita con significati speciali . Questa tabella di codici di uscita è utile in quanto fornisce ulteriori dettagli ed esempi su come generare i codici di errore documentati nel riferimento Bash .

Tentativo di generare lo stato di uscita di 128

Usando le versioni 3.2.25 e 4.2.46 di Bash, ho provato a lanciare un 128 Invalid argument to exiterrore ma ogni volta che ho ricevuto un 255 (stato di uscita fuori intervallo). Ad esempio, se exit 3.14159viene eseguito come parte di uno script di shell o in una shell figlio interattiva, la shell esce con un codice di 255:

$ exit 3.14159
exit
bash: exit: 3.14159: numeric argument required

Per ancora più divertente, ho anche provato a eseguire un semplice programma C ma in questo caso, sembra che la exit(3)funzione abbia semplicemente convertito il float in un int (3 in questo caso) prima di uscire:

#include <stdlib.h>
main()
{
    exit(3.14159);
}
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.