Używanie kodów „zastrzeżonych” dla statusu wyjścia skryptów powłoki

15

Ostatnio natknąłem się na listę kodów wyjścia o specjalnych znaczeniach z Advanced Bash-Scripting Guide. Odnoszą się do tych kodów jako zastrzeżone i zalecają:

Zgodnie z powyższą tabelą kody wyjścia 1-2, 126-165 i 255 mają specjalne znaczenie i dlatego należy ich unikać w przypadku parametrów wyjścia określonych przez użytkownika.

Jakiś czas temu napisałem skrypt, który używał następujących kodów statusu wyjścia:

  • 0 - sukces
  • 1 - niepoprawna nazwa hosta
  • 2 - podano nieprawidłowe argumenty
  • 3 - niewystarczające uprawnienia użytkownika

Kiedy pisałem skrypt, nie znałem żadnych specjalnych kodów wyjścia, więc po prostu zacząłem od 1 dla pierwszego warunku błędu i zwiększałem status wyjścia dla każdego kolejnego typu błędu.

Napisałem skrypt z zamiarem, że na późniejszym etapie może on zostać wywołany przez inne skrypty (które mogą sprawdzić niezerowe kody wyjścia). Tak naprawdę jeszcze tego nie zrobiłem; jak dotąd uruchomiłem skrypt tylko z mojej interaktywnej powłoki (Bash) i zastanawiałem się, co / czy problemy mogą być spowodowane przez użycie moich niestandardowych kodów wyjścia. Jak istotna / ważna jest rekomendacja z Advanced Bash-Scripting Guide?

Nie mogłem znaleźć żadnej potwierdzającej porady w dokumentacji Bash; jej sekcja na temat statusu wyjścia po prostu zawiera listę kodów wyjścia używanych przez Bash, ale nie stwierdza, że ​​żaden z nich jest zarezerwowany, ani ostrzega przed użyciem ich do własnych skryptów / programów.

Anthony G - sprawiedliwość dla Moniki
źródło
6
Ja i inni uważamy ABSG za ogólnie niskiej jakości. Moim zdaniem autor strony, do której linkujesz, nie popiera twierdzenia, że ​​wymienione kody wyjścia są zastrzeżone, opierając się, najwyraźniej, na tym, że sama powłoka używa ich do określonych znaczeń. Podjęto próby stworzenia standardów dla skryptów, z których żaden nie odniósł sukcesu. Ważne jest, aby udokumentować wybrane kody błędów, aby konsumenci twoich skryptów (np. Innych skryptów) wiedzieli, co robić na ich podstawie.
Wstrzymano do odwołania.
@DennisWilliamson Jeśli opublikujesz swój komentarz jako odpowiedź, chętnie go poprę; Głosowałem już na wszystkie pozostałe odpowiedzi, ponieważ uznałem, że każda z nich jest przydatna. Chociaż twoja odpowiedź jest podobna w treści do odpowiedzi Davida Kinga (i, w mniejszym stopniu, Zwola), wyraźnie zaznaczasz, że nie ma dowodów na potwierdzenie w cytacie ABSG.
Anthony G - sprawiedliwość dla Moniki
1
Dzięki za ofertę, ale uważam, że mój komentarz powinien zostać.
Wstrzymano do odwołania.
Od tamtej pory odkryłem, że specyfikacja POSIX zawiera podobne porady, więc dodałem tę informację do mojej własnej odpowiedzi (zawierającej wyniki moich badań od czasu zadania tego pytania).
Anthony G - sprawiedliwość dla Moniki

Odpowiedzi:

10

Podjęto szereg prób standaryzacji znaczenia kodów zakończenia procesu. Oprócz tego, o którym wspominasz, wiem o:

  • BSD mają sysexits.hznaczenie dla wartości od 64 wzwyż.

  • grepDokumenty GNU, że kod wyjścia 0 oznacza, że ​​znaleziono co najmniej jedno dopasowanie, 1 oznacza, że ​​nie znaleziono pasujących, a 2 oznacza wystąpienie błędu We / Wy; ta konwencja jest oczywiście przydatna również w przypadku innych programów, dla których rozróżnienie między „nic nie poszło nie tak, ale nic nie znalazłem” i „wystąpił błąd we / wy” jest znaczący.

  • Wiele implementacji funkcji biblioteki C systemużywa kodu wyjścia 127, aby wskazać, że program nie istnieje lub nie uruchomił się.

  • W systemie Windows NTSTATUSkody (które są niewygodnie rozrzucone w 32-bitowej przestrzeni liczbowej) mogą być używane jako kody wyjścia, szczególnie te, które wskazują, że proces został zakończony z powodu katastrofalnego zachowania (np STATUS_STACK_OVERFLOW.).

Nie możesz liczyć na to, że dany program będzie przestrzegał określonej konwencji. Jedyną niezawodną regułą jest to, że kod wyjścia 0 to sukces, a wszystko inne to jakaś awaria. (Należy pamiętać, że C89 nieEXIT_SUCCESS ma gwarancji, że ma wartość zero; jednak musi zachowywać się identycznie, nawet jeśli wartości nie są takie same.)exit(0)exit(EXIT_SUCCESS)

zwol
źródło
Dzięki. Trudno było wybrać jedną odpowiedź spośród innych, ale akceptuję tę odpowiedź, ponieważ odpowiedziała ona na moje pytanie, jednocześnie zapewniając szeroki wachlarz różnych używanych kodów wyjścia (z odpowiednimi linkami): zasługuje na więcej niż 3 głosy poparcia obecnie ma.
Anthony G - sprawiedliwość dla Moniki
11

Żaden kod wyjścia nie ma specjalnego znaczenia, ale wartość w $?może mieć specjalne znaczenie.

Problemem jest sposób, w jaki Bourne Shell i ksh93 przetwarzają i przekazują kody wyjścia oraz sytuacje błędów do zmiennej powłoki $?. W przeciwieństwie do tego, co podajesz, tylko następujące wartości $?mają specjalne znaczenie:

  • 126 Nie można wykonać pliku binarnego, nawet jeśli istnieje
  • 127 Określony plik binarny nie istnieje
  • Status wyjścia 128 wynosił == 0, ale istnieje jakiś nieokreślony problem

Ponadto istnieje nieokreślona powłoka i specyficzny dla platformy zakres $?kodów> 128, który jest zarezerwowany dla programu, który został przerwany przez sygnał:

  • Bash Bourne Shell i ksh88 używają 128 + numeru sygnału
  • ksh93 używa 256 + numeru sygnału.

Inne wartości nie powodują problemów, ponieważ można je odróżnić od $?wartości specjalnych dla powłoki .

W szczególności wartości 1 i 2 nie są używane w szczególnych warunkach, ale są jedynie kodami wyjścia używanymi przez wbudowane polecenia, które mogłyby działać tak samo, gdy nie są wbudowanymi. Wygląda na to, że wskaźnik do dostarczonego przez ciebie skryptu bash nie jest dobrym podręcznikiem, ponieważ zawiera tylko kody używane przez bash bez komentowania, czy określony kod jest specjalną wartością, której należy unikać w przypadku własnych skryptów.

Nowsze wersje Bourne Shell używają waitid()zamiast waitpid()czekać na zakończenie programu, a waitid()(wprowadzony 1989 dla SVr4) używa lepszego interfejsu syscall (podobnego do tego, który UNOS używał już w 1980 roku).

Ponieważ nowsze wersje Bourne Shell kodują przyczynę wyjścia w osobnej zmiennej ${.sh.code}/ ${.sh.codename}niż kod wyjścia znajdujący się w ${.sh.status}/ ${.sh.termsig}, patrz http://schillix.sourceforge.net/man/man1/bosh.1.html , kod wyjścia nie jest przeciążony ze specjalnymi stanami, a w wyniku użycia `waitid (), powłoka Bourne'a obsługuje teraz zwracanie wszystkich 32 bitów kodu wyjścia - nie tylko niskich 8 bitów.

BTW: uważaj, aby nie exit(256)lub podobnie do programu C lub skryptu powłoki, ponieważ powoduje $?to interpretację jako 0 w klasycznej powłoce.

schily
źródło
2
BTW: Złożyłem raport o błędzie przeciwko FreeBSD i jądru Linuksa dla tego waitid()błędu pod koniec maja. Ludzie FreeBSD naprawili problem w ciągu 20 godzin, ludzie Linuksa nie są zainteresowani naprawieniem swojego błędu. ... a ludzie z Cygwin mówią, że są kompatybilni z błędami w Linuksie ;-)
schily
2
To zachowanie jest wymagane przez specyfikację Single Unix. Tak, jest 32-bitowa wartość, ale ta wartość zawiera 8-bitowe pole bitowe zawierające niskie 8 bitów wartości z _exit. Podaj link do raportu o błędzie FreeBSD, o którym mówisz, być może nie rozumiem opisanego problemu.
Random832,
2
OP oznaczył pytanie bash i wspomniał o Bash w tekście pytania. Bash jest powłoką pochodzącą z Bourne'a. Nie obsługuje ${.sh.}zmiennych. Prawdą jest jednak, że mówisz „Bourne”, a nie „wywodzące się z Bourne'a” (chociaż zawierasz ksh93).
Wstrzymano do odwołania.
2
Ta odpowiedź wydaje się być bardzo specyficzna dla konkretnego wariantu niektórych Uniksów pochodzących z SVR4. Wyjaśnij, co jest przenośne, a co nie, pamiętając, że nie ma czegoś takiego jak „powłoka Bourne'a”, chyba że masz na myśli tę, która była w V7.
zwolnienie
4
Wręcz przeciwnie, uważam, że to ty nie doceniasz tutaj różnorodności, zwłaszcza historycznej. Sprawiasz, że brzmi to tak, jakbyś /bin/shmógł polegać na konsekwentnym zachowaniu tych specjalnych kodów wyjścia na różnych platformach, co nie jest prawdą. (Nie dbam o to, czy jakikolwiek konkretny system /bin/shmożna uznać za „prawdziwą powłokę Bourne'a”. O wiele ważniejsze jest, aby wiedzieć, że nic z tego nie jest w POSIX-ie i że większość rzeczy, które wymieniasz jako „prawdziwe systemy uniksowe”, nie „ i tak zapewnia zgodność z POSIX /bin/sh.)
zwolnij
6

W przypadku skryptów powłoki czasami umieszczam w źródle odpowiednik powłoki sysexist.hz kodami wyjścia zarezerwowanymi dla powłoki (z prefiksem S_EX_), które nazwałemexit.sh

Jest to w zasadzie:

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   
#...

I można wygenerować za pomocą:

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

Nie używam go jednak dużo, ale używam funkcji powłoki, która odwraca kody błędów na ich formaty ciągów. Nazwałem to exit2str. Zakładając, że nazwałeś powyższy exit.shgenerator exit.sh.sh, kod dla exit2strmożna wygenerować za pomocą ( 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
}"

Używam tego w PS1mojej interaktywnej powłoce, dzięki czemu po każdym uruchomieniu polecenia widzę jego status wyjścia i ciąg znaków (jeśli ma znany ciąg znaków):

[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)$

Aby je uzyskać, potrzebujesz klucza źródłowego dla funkcji exit2str:

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

a następnie użyj go w swoim, ~/.bashrcaby zapisać i przetłumaczyć kod wyjścia w każdym wierszu polecenia i wyświetlić go jako znak zachęty ( PS1):

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

Jest to całkiem przydatne do obserwowania, jak niektóre programy przestrzegają konwencji kodu wyjścia, a inne nie, do poznawania konwencji kodu wyjścia lub po prostu dla łatwiejszego zobaczenia, co się dzieje. Używając go od jakiegoś czasu, mogę powiedzieć, że wiele systemowych skryptów powłoki przestrzega konwencji. EX_USAGEjest szczególnie dość powszechny, choć inne kody niewiele. Od czasu do czasu staram się przestrzegać konwencji, chociaż zawsze $S_EX_ANYleniwi ludzie (jestem jednym z nich).

PSkocik
źródło
Zastanawiam się, czy istnieje coś takiego jak odwzorowanie między kodem errno a kodem wyjścia, którego można użyć, jeśli błąd zgłoszony przy pomocy tego kodu błędu spowoduje wyjście błędu. Być może będę musiał wymyślić jakieś rozsądne mapowanie.
PSkocik,
1
Łał! Nie spodziewałem się tak skomplikowanej odpowiedzi. Zdecydowanie wypróbuję to jako dobry sposób, aby zobaczyć, jak zachowują się różne polecenia. Dzięki.
Anthony G - sprawiedliwość dla Moniki
4

Dopóki dokumentujesz swoje kody wyjścia, abyś pamiętał je za rok, kiedy będziesz musiał wrócić i dostosować skrypt, nic ci nie będzie. Pomysł „zarezerwowanych kodów wyjścia” nie ma już zastosowania poza stwierdzeniem, że zwykle stosuje się go 0jako kod sukcesu, a cokolwiek innego jako kod błędu.

David King
źródło
4

Najlepsze referencje, jakie mogłem znaleźć, to: http://tldp.org/LDP/abs/html/exitcodes.html

Według tego:

1 jest ogólną regułą dotyczącą błędów i zawsze widziałem, że jest używana w przypadku błędów zdefiniowanych przez użytkownika.

2 służy do niewłaściwego użycia wbudowanych powłok, takich jak błąd składniowy

Aby bezpośrednio odpowiedzieć na twoje pytanie, skrypt będzie działał poprawnie przy użyciu zastrzeżonych kodów błędów, będzie działał zgodnie z oczekiwaniami, zakładając, że obsłużysz błąd na podstawie kodu błędu = 1/2/3.

Jednak może być mylące, jeśli spotkasz kogoś, kto zna i używa zastrzeżonych kodów błędów, co wydaje się dość rzadkie.

Inną dostępną opcją jest powtórzenie błędu, jeśli taki istnieje, a następnie wyjście, przy założeniu, że skrypt jest zgodny z konwencją Linuksa, zgodnie z którą „brak wiadomości to dobra wiadomość”, a echo nie ma żadnego sukcesu.

if [ $? -ne 0 ];then
    echo "Error type"
    exit 1
fi
Centyman
źródło
2

W oparciu o odpowiedzi, które otrzymałem (trudno było wybrać jedną z pozostałych), wskazanie pewnych rodzajów błędów przy użyciu kodu wyjścia, którego używa Bash , nie jest szkodliwe . Bash (lub jakakolwiek inna powłoka uniksowa) nie zrobi nic specjalnego (takiego jak uruchamianie programów obsługi wyjątków), jeśli skrypt użytkownika zakończy działanie z jednym z tych kodów błędów.

Wygląda na to, że autor Advanced Bash-Scripting Guide zgadza się z próbami standaryzacji kodów wyjścia ( sysexits.h) przez BSD i po prostu zaleca, aby użytkownicy piszący skrypty powłoki nie podawali już kodów wyjścia, które są w konflikcie ze wstępnie zdefiniowanymi kodami wyjścia w użyciu, tj. ograniczają swoje niestandardowe kody wyjścia do 50 dostępnych kodów stanu z zakresu 64-113.

Doceniam pomysł (i uzasadnienie), ale wolałbym, gdyby autor był bardziej wyraźny, że ignorowanie porady nie jest szkodliwe - oprócz przypadków, w których konsument skryptu sprawdza błędy, takie jak cytowany przykład 127 ( command not found).

Odpowiednie specyfikacje POSIX

Zbadałem, co POSIX ma do powiedzenia na temat kodów wyjścia, a specyfikacja POSIX wydaje się zgadzać z autorem Advanced Bash-Scripting Guide. Przytoczyłem odpowiednie specyfikacje POSIX (moje wyróżnienie):

Status wyjścia dla poleceń

Każde polecenie ma status wyjścia, który może wpływać na zachowanie innych poleceń powłoki. Status wyjścia komend, które nie są narzędziami, jest udokumentowany w tej sekcji. Status wyjścia standardowych narzędzi jest udokumentowany w odpowiednich sekcjach.

Jeśli polecenie nie zostanie znalezione, stanem wyjścia będzie 127. Jeśli nazwa polecenia zostanie znaleziona, ale nie jest to narzędzie wykonywalne, stanem wyjścia będzie 126. Aplikacje, które wywołują narzędzia bez użycia powłoki, powinny używać tych wartości statusu wyjścia zgłaszać podobne błędy.

Jeżeli polecenie nie powiedzie się podczas rozwijania lub przekierowywania słów, jego status wyjścia powinien być większy niż zero.

Wewnętrznie, w celu podjęcia decyzji, czy polecenie kończy działanie z niezerowym statusem wyjścia, powłoka rozpoznaje całą wartość statusu pobraną dla polecenia przez odpowiednik makra funkcji WEXITSTATUS funkcji wait () zdefiniowanego w objętości interfejsów systemowych POSIX.1-2008). Zgłaszając status wyjścia za pomocą specjalnego parametru „?”, Powłoka zgłasza pełne osiem dostępnych bitów statusu wyjścia. Status wyjścia polecenia, które zakończyło się, ponieważ odebrał sygnał, zgłasza się jako większy niż 128.

exitużyteczność

Jak wyjaśniono w innych sekcjach, niektóre wartości statusu wyjścia zostały zarezerwowane do specjalnych zastosowań i powinny być używane przez aplikacje tylko do tych celów:

  • 126 - Znaleziono plik do wykonania, ale nie był to program wykonywalny.
  • 127 - Nie znaleziono narzędzia do wykonania.
  • >128 - Polecenie zostało przerwane sygnałem.

Dalsza informacja

Z tego, co warto, udało mi się zweryfikować wszystkie kody wyjścia poza specjalnymi znaczeniami oprócz jednego . Ta tabela kodów wyjścia jest przydatna, ponieważ zawiera więcej szczegółów - i przykłady generowania kodów błędów udokumentowanych w odnośniku Bash .

Próba wygenerowania statusu wyjścia 128

Korzystając z wersji Bash 3.2.25 i 4.2.46, próbowałem wyrzucić 128 Invalid argument to exitbłąd, ale za każdym razem otrzymywałem 255 (Status wyjścia poza zakresem). Na przykład, jeśli exit 3.14159jest wykonywany jako część skryptu powłoki lub w interaktywnej powłoce podrzędnej, powłoka kończy działanie z kodem 255:

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

Dla jeszcze większej zabawy próbowałem również uruchomić prosty program w C, ale w tym przypadku wydaje się, że exit(3)funkcja po prostu przekształciła liczbę zmiennoprzecinkową na int (w tym przypadku 3) przed wyjściem:

#include <stdlib.h>
main()
{
    exit(3.14159);
}
Anthony G - sprawiedliwość dla Moniki
źródło