Використання "зарезервованих" кодів для виходу зі скриптів оболонки


15

Нещодавно я натрапив на цей список кодів виходу із спеціальними значеннями з Посібника з розширеного сценарію Bash. Вони посилаються на ці коди як на зарезервовані та рекомендують:

Згідно з наведеною вище таблицею, коди виходу 1-2, 126-165 та 255 мають особливі значення, тому слід уникати вказаних користувачем параметрів виходу.

Деякий час тому я написав сценарій, в якому були використані наступні коди стану виходу:

  • 0 - успіх
  • 1 - неправильне ім'я хоста
  • 2 - вказані недійсні аргументи
  • 3 - недостатня кількість привілеїв користувача

Коли я писав сценарій, мені не було відомо про будь-які спеціальні коди виходу, тому я просто запускався з 1 за першої умови помилки і збільшував статус виходу для кожного наступного типу помилки.

Я написав сценарій з наміром, щоб на більш пізньому етапі його можна було викликати іншими сценаріями (які могли перевірити наявність ненульових кодів виходу). Я ще цього ще не робив; поки що я запускаю сценарій лише з інтерактивної оболонки (Bash), і мені було цікаво, що / якщо якісь проблеми можуть бути викликані використанням моїх спеціальних кодів виходу. Наскільки релевантною / важливою є рекомендація з Посібника з розширеного сценарію?

Я не зміг знайти жодної підтверджуючої поради в документації на Bash; У його розділі про стан виходу просто перераховані коди виходу, використовувані Bash, але не зазначено, що будь-який із них зарезервований або не застерігає їх від використання для власних скриптів / програм.


6
Я та інші вважаю ABSG загалом низької якості. На мою думку, автор сторінки, яку ви пов’язали, робить непідтримуване твердження, що перераховані коди виходу зарезервовані, мабуть, на тому, що сама оболонка використовує їх для конкретних значень. Були спроби створити стандарти для сценаріїв, жоден з яких не мав успіху. Важливо - документувати вибрані вами коди помилок, щоб споживачі ваших сценаріїв (наприклад, інших сценаріїв) знали, що робити на їх основі.
Призупинено до подальшого повідомлення.

@DennisWilliamson Якщо ви опублікуєте свій коментар як відповідь, я з радістю підтримаю його; Я вже проголосував за всі інші відповіді, оскільки визнав кожну з них корисною. Незважаючи на те, що ваша відповідь за змістом схожа на інформацію Девіда Кінга (і в меншій мірі на Звола), ви чітко заявляєте, що в цитаті ABSG немає доказів твердження.
Ентоні Г - справедливість для Моніки

1
Дякую за пропозицію, але я вважаю, що мій коментар повинен залишатися таким.
Призупинено до подальшого повідомлення.

З тих пір я виявив, що специфікація POSIX включає подібні поради, тому я додав цю інформацію до власної відповіді (містить результати мого дослідження з моменту задавання цього питання).
Ентоні Г - справедливість для Моніки

Відповіді:


10

Було кілька спроб стандартизації значень кодів виходу з процесу. Окрім тієї, яку ви згадуєте, я знаю:

  • BSD мають, sysexits.hякий визначає значення для значень від 64 і вище.

  • grepДокументи GNU, що код виходу 0 означає щонайменше одну відповідність, 1 означає, що збіги не знайдено, а 2 означає помилку вводу / виводу; ця конвенція, очевидно, також корисна для інших програм, для яких розрізнення "нічого не пішло не так, але я нічого не знайшов", і "помилка вводу / виводу сталася" має значення.

  • У багатьох реалізаціях функції бібліотеки С systemвикористовується код виходу 127, щоб вказати, що програма не існує або не запускається.

  • У Windows NTSTATUSкоди (які незручно розкидані по 32-бітовому простору чисел) можуть використовуватися як вихідні коди, особливо ті, що вказують на те, що процес був припинений через катастрофічну поведінку (наприклад STATUS_STACK_OVERFLOW).

Ви не можете розраховувати, що будь-яка програма виконує будь-яку конкретну конвенцію. Єдиним надійним правилом є те, що вихідний код 0 - це успіх, а все інше - це якийсь збій. (Зверніть увагу , що C89 - х EXIT_SUCCESSце НЕ гарантовано мати нульове значення, однак, exit(0)потрібно , щоб вести себе так само exit(EXIT_SUCCESS). Навіть якщо значення не збігаються)


Спасибі. Вибрати одну відповідь над іншими було важко, але я приймаю цю, оскільки вона відповіла на моє запитання, а також надала широкий смак різних використовуваних кодів виходу (з відповідними посиланнями): вона заслуговує більше, ніж 3 оновлення в даний час є.
Ентоні G - справедливість для Моніки

11

Жоден вихідний код не має особливого значення, але значення в $?може мати особливе значення.

Проблема полягає в тому, як обробляли Bourne Shell і ksh93 та пересилали коди виходу та ситуації помилок до змінної оболонки $?. Всупереч тому, що ви перераховуєте, лише наступні значення для $?мають особливе значення:

  • 126 Не вдалося виконати двійковий файл, навіть якщо він існує
  • 127 Вказаного двійкового файла не існує
  • 128 статус виходу становив == 0, але існує певна не визначена проблема

Крім того, є не визначений оболонка та специфічний для оболонки діапазон $?кодів> 128, який зарезервований для програми, яка була перервана сигналом:

  • Bourne Shell bash та ksh88 використовують 128 + номер сигналу
  • ksh93 використовує 256 + номер сигналу.

Інші значення не створюють проблем, оскільки їх можна відрізняти від спеціальних оболонок $?.

Зокрема, значення 1 і 2 не використовуються для спеціальних умов, а є лише кодами виходу, використовуваними вбудованими командами, які можуть діяти однаково, коли вони не є вбудованими. Отож, схоже, що покажчик на базовий сценарій сценаріїв, який ви надали, не є гарним посібником, оскільки він просто перераховує коди, які використовує bash, не коментуючи, чи є специфічний код спеціальним значенням, якого слід уникати для власних сценаріїв.

Новіші версії Bourne Shell використовують waitid()замість того, waitpid()щоб чекати виходу програми, і waitid()(запроваджена в 1989 році для SVr4) використовується кращий інтерфейс системного виклику (подібний до того, який UNOS використовував у 1980 році).

Оскільки новіші версії Bourne Shell кодують причину виходу в окрему змінну ${.sh.code}/ ${.sh.codename}ніж код виходу, який знаходиться у ${.sh.status}/ ${.sh.termsig}, див. Http://schillix.sourceforge.net/man/man1/bosh.1.html , код виходу не перевантажений зі спеціальними станами, і в результаті використання `waitid (), оболонка Bourne тепер підтримує повернення всіх 32 біт вихідного коду - не лише низьких 8 біт.

BTW: будьте обережні, щоб не exit(256)схожий на C-програму або сценарій оболонки, оскільки це призводить до $?інтерпретації як 0 у класичній оболонці.


2
BTW: Я склав звіт про помилки щодо FreeBSD та ядра Linux для цієї waitid()помилки наприкінці травня. Люди FreeBSD виправили цю проблему протягом 20 годин, а Linux не зацікавлені у виправлення помилок. ... а люди Cygwin говорять, що їх помилка сумісна з помилкою Linux ;-)
schily

2
Така поведінка вимагається єдиною специфікацією Unix. Існує 32-бітове значення, але це значення містить 8-бітове бітове поле, що містить низькі 8 біт значення _exit. Зв’яжіть звіт про помилку FreeBSD, на який ви посилаєтесь, можливо я не розумію описану вами проблему.
Випадково832

2
ОП позначила це питання башем і згадала Баша в тексті запитання. Баш - шкаралупа походження Борна. Він не підтримує ${.sh.}змінні. Однак, правда, ви говорите "Bourne", а не "Bourne" (хоча ви включаєте ksh93).
Призупинено до подальшого повідомлення.

2
Ця відповідь виглядає дуже специфічно для вашого конкретного варіанту деяких Unix, похідних від SVR4. Будьте зрозуміліші щодо того, що є портативним, а що ні, маючи на увазі, що немає такого поняття, як "оболонка" Борна, якщо ви не маєте на увазі ту, яка була у V7.
zwol

4
Навпаки, я вважаю, що саме ви занижуєте діапазон варіацій, особливо історичний. Ви здаєтеся, що це /bin/shможе покладатися на те, що вони поводяться послідовно, коли ці спеціальні коди виходу є кросплатформенними, що не відповідає дійсності. (Мені байдуже, чи /bin/shможна сказати, що якась конкретна система є "справжньою оболонкою Борна". Набагато важливіше знати, що нічого з цього немає в POSIX, і що більшість речей, які ви цитуєте як "справжні системи Unix," не " t забезпечити POSIX-сумісний /bin/shвсе одно.)
zwol

6

Для сценаріїв оболонок я іноді ввожу еквівалент sysexist.hоболонки із зарезервованими оболонками вихідними кодами (з префіксом S_EX_), які я назвавexit.sh

Це в основному:

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

І може бути створено за допомогою:

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

Я мало використовую його, але я використовую функцію оболонки, яка обертає коди помилок до їх рядкових форматів. Я його назвав exit2str. Якщо припустити, що ви назвали вищезгаданий exit.shгенератор exit.sh.sh, код для exit2strможе бути сформований за допомогою ( 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
}"

Я використовую це в PS1своїй інтерактивній оболонці, щоб після кожної команди, яку я запускаю, я бачив її статус виходу та його рядкову форму (якщо вона має відому строкову форму):

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

Щоб отримати їх, вам потрібна переможна функція exit2str:

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

а потім скористайтеся ним у вашому, ~/.bashrcщоб зберегти та перекласти вихідний код у кожному командному рядку та відобразити його підказку (PS1 ):

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

Це досить зручно для спостереження за тим, як деякі програми слідують конвенціям про вихідний код, а деякі ні, для ознайомлення з умовами виходу з коду або просто для того, щоб можна було легше побачити, що відбувається. Використовуючи його деякий час, я можу сказати, що багато сценарії оболонки, орієнтовані на систему, дійсно відповідають умовам. EX_USAGEособливо поширений, хоча інших кодів не дуже. Я намагаюся час від часу дотримуватися конвенцій, хоча завжди є $S_EX_ANY(1) для ледачих людей (я такий).


Мені цікаво, чи є щось подібне до відображення між кодом errno та кодом виходу, якщо помилка, повідомлена з цим кодом errno, призводить до виходу з помилки. Мені може знадобитися придумати якісь розумні карти.
PSkocik

1
Оце Так! Я не очікував такої ретельної відповіді. Я обов'язково спробую це як хороший спосіб побачити, як поводяться різні команди. Спасибі.
Ентоні Г - справедливість для Моніки

4

Поки ви документуєте свої вихідні коди, щоб ви запам'ятали їх рік, відтепер, коли вам доведеться повертатися і налаштовувати сценарій, у вас все буде добре. Ідея "зарезервованих кодів виходу" насправді не застосовується більше, ніж сказати, що це звичайно використовувати 0як код успіху, а що-небудь інше як код відмови.


4

Найкраще, що я міг знайти, це: http://tldp.org/LDP/abs/html/exitcodes.html

Відповідно до цього:

1 це загальний опис помилок, і я завжди бачив, що він використовується для визначених користувачем помилок.

2 призначений для неправильного використання вбудованих оболонок, таких як помилка синтаксису

Для прямого відповіді на запитання ваш сценарій буде добре, використовуючи зарезервовані коди помилок, він буде функціонувати так, як очікувалося, припускаючи, що ви обробляєте помилку на основі коду помилки = 1/2/3.

Однак це, можливо, буде заплутаним, якщо ви зіткнетеся з кимсь, хто знає та використовує зарезервовані коди помилок, що здається досить рідкісним.

Ще один варіант, доступний вам, - це повторити помилку, якщо вона є, а потім вийти, припускаючи, що ваш сценарій дотримується конвенції Linux "жодна новина не є хорошою новиною", і нічого не пролунає успіх.

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

2

Виходячи з отриманих відповідей (важко було вибрати одну над іншими), не шкідливо вказувати певні типи помилок, використовуючи код виходу, який також використовує Bash. Bash (або будь-яка інша оболонка Unix) не зробить нічого особливого (наприклад, запущені обробники винятків), якщо сценарій користувача завершиться одним із цих кодів помилок.

Схоже, автор розширеного посібника з сценаріїв Bash погоджується з спробами BSD стандартизувати вихідні коди ( sysexits.h) і просто рекомендує , коли користувачі пишуть сценарії оболонок, вони не вказують коди виходу, які суперечать попередньо визначеним кодам виходу у використанні, тобто вони обмежують свої власні коди виходу до 50 доступних кодів статусу в діапазоні 64-113.

Я ціную цю ідею (і обґрунтування), але я вважав за краще, якщо автор буде більш чітким, що ігнорувати пораду не шкідливо - окрім випадків, коли споживач сценарію перевіряє на наявність помилок, таких як цитований приклад 127 (command not found ).

Відповідні специфікації POSIX

Я дослідив, що POSIX має сказати про вихідні коди, а специфікація POSIX, схоже, узгоджується з автором Посібника з розширеного сценарію Bash. Я цитував відповідні специфікації POSIX (міна акценту):

Вихід із стану команд

Кожна команда має статус виходу, який може впливати на поведінку інших команд оболонки. Стан виходу команд, які не є утилітами, задокументований у цьому розділі. Статус виходу стандартних утиліт задокументований у відповідних розділах.

Якщо команда не знайдена, стан виходу має бути 127. Якщо ім'я команди знайдено, але це не виконувана утиліта, статус виходу має бути 126. Програми, які викликають утиліти без використання оболонки, повинні використовувати ці значення стану виходу повідомляти про подібні помилки.

Якщо команда не працює під час розширення або перенаправлення слова, його вихідний статус повинен бути більшим за нуль.

Внутрішньо, для вирішення того, чи закінчується команда з ненульовим статусом виходу, оболонка повинна розпізнавати все значення статусу, отримане для команди, еквівалентним макросом WEXITSTATUS функції wait (), як визначено в обсязі системних інтерфейсів POSIX.1-2008). Повідомляючи про стан виходу зі спеціальним параметром "?", Оболонка повинна повідомляти про всі вісім бітів стану виходу. Про стан виходу команди, яка припинилась через отримання сигналу, повідомляється як 128.

exitутиліти

Як пояснено в інших розділах, певні значення статусу виходу зарезервовані для спеціального використання і повинні використовуватися програмами лише для цих цілей:

  • 126 - Файл, який потрібно виконати, був знайдений, але це не була програма, що виконується.
  • 127 - Утиліта, яку потрібно виконати, не знайдено.
  • >128 - Команда була перервана сигналом.

Додаткова інформація

Наскільки це варте, я зміг перевірити всі, крім одного зі списку кодів виходу із спеціальними значеннями . Ця таблиця кодів виходів корисна, оскільки містить більше деталей - та приклади того, як генерувати коди помилок, задокументовані у посиланні Баша .

Спроба створити статус виходу 128

Використовуючи версії Bash 3.2.25 та 4.2.46, я намагався викинути 128 Invalid argument to exitпомилку, але кожен раз, коли я отримував 255 (статус виходу за межі діапазону). Наприклад, якщо exit 3.14159він виконується як частина сценарію оболонки або в інтерактивній дочірній оболонці, оболонка виходить з кодом 255:

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

Для ще більш розваги я також спробував запустити просту програму на C, але в цьому випадку, здається, що exit(3)функція просто перетворила float в int (3 у цьому випадку) перед виходом:

#include <stdlib.h>
main()
{
    exit(3.14159);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.