Использование «зарезервированных» кодов для выхода из сценариев оболочки

15

Недавно я наткнулся на этот список кодов выхода со специальными значениями из расширенного руководства по написанию сценариев. Они ссылаются на эти коды как зарезервированные и рекомендуют следующее:

В соответствии с приведенной выше таблицей коды выхода 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 означает ошибку ввода-вывода; это соглашение, очевидно, также полезно для других программ, для которых различие между «ничего не пошло не так, но я ничего не нашел» и «произошла ошибка ввода-вывода» имеет смысл.

  • Многие реализации функции библиотеки C systemиспользуют код завершения 127, чтобы указать, что программа не существует или не удалось запустить.

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

Вы не можете рассчитывать на какую-либо конкретную программу, выполняющую какое-либо конкретное из этих соглашений. Единственное надежное правило заключается в том, что код выхода 0 - это успех, а все остальное - своего рода сбой. (Обратите внимание , что C89 - х EXIT_SUCCESSэто не гарантированно иметь нулевое значение, однако, exit(0)требуется , чтобы вести себя так же exit(EXIT_SUCCESS). Даже если значения не совпадают)

zwol
источник
Благодарю. Было трудно выбрать один ответ среди других, но я принимаю этот, так как он ответил на мой вопрос, в то же время предоставляя широкий спектр различных используемых кодов выхода (с соответствующими ссылками): он заслуживает большего, чем 3 отзыва в настоящее время имеет.
Энтони Дж - справедливость для Моники
11

Никакой код выхода не имеет специального значения, но значение $?может иметь особое значение.

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

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

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

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

Другие значения не создают проблем, поскольку их можно отличить от специальных $?значений оболочки .

В частности, значения 1 и 2 не используются для особых условий, а являются просто кодами выхода, используемыми встроенными командами, которые могут действовать одинаково, если они не являются встроенными. Похоже, что указатель на предоставленное вами руководство по написанию сценариев bash не является хорошим руководством, поскольку он просто перечисляет коды, используемые 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 битов.

КСТАТИ: будьте осторожны exit(256)с C-программой или сценарием оболочки или не похожи на них, так как это приводит $?к интерпретации как 0 в классической оболочке.

Шили
источник
2
Кстати, я сделал отчет об ошибке в FreeBSD и ядре Linux для этой waitid()ошибки примерно в конце мая. Люди из FreeBSD исправили проблему в течение 20 часов, люди из Linux не заинтересованы в исправлении их ошибки. ... и люди Cygwin говорят, что они совместимы с ошибками Linux ;-)
schily
2
Это поведение требуется спецификацией Single Unix. Да, есть 32-битное значение, но это значение содержит 8-битное битовое поле, содержащее младшие 8 битов значения from _exit. Пожалуйста, свяжите отчет об ошибке FreeBSD, на который вы ссылаетесь, возможно, я неправильно понимаю проблему, которую вы описываете.
Random832
2
ОП пометил вопрос с помощью bash и упомянул Bash в тексте вопроса. Bash - это оболочка, полученная из Борна. Он не поддерживает ${.sh.}переменные. Однако верно то, что вы говорите «Bourne», а не «Bourne-output» (хотя вы включаете ksh93).
Приостановлено до дальнейшего уведомления.
2
Этот ответ, кажется, очень специфичен для вашего конкретного варианта Unix, производного от SVR4. Проясните, что переносимо, а что нет, помните, что не существует такого понятия, как «оболочка Bourne», если только вы не имеете в виду тот, который был в V7.
Звол
4
Наоборот, я полагаю, что именно вы здесь преуменьшаете диапазон изменений, особенно исторических. Вы заставляете это звучать так, как будто вы /bin/shможете полагаться на постоянное поведение этих кросс-платформенных кодов выхода, что не соответствует действительности. (Мне все равно , /bin/shможно ли назвать какую-либо конкретную систему «настоящей оболочкой Борна». Гораздо важнее знать, что ничего этого нет в POSIX, и что большинство вещей, которые вы цитируете как «настоящие системы Unix», не в /bin/shлюбом случае, не POSIX-совместимый .)
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) для ленивых людей (я один).

PSkocik
источник
Я действительно задаюсь вопросом, есть ли что-либо подобное сопоставлению между кодом 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
Centimane
источник
2

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

Кажется, что автор Advanced Bash-Scripting Guide согласен с BSD, пытающимся стандартизировать коды выхода ( sysexits.h), и просто рекомендует, чтобы, когда пользователи пишут сценарии оболочки, они не указывали коды выхода, которые уже конфликтуют с предопределенными кодами выхода в процессе использования, т. е. они ограничивают свои пользовательские коды выхода 50 доступными кодами состояния в диапазоне 64-113.

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

Соответствующие спецификации POSIX

Я исследовал, что POSIX говорит о кодах выхода, и спецификация POSIX, похоже, совпадает с автором Advanced Bash-Scripting Guide. Я процитировал соответствующие спецификации POSIX (выделено мое):

Состояние выхода для команд

Каждая команда имеет статус выхода, который может влиять на поведение других команд оболочки. Состояние выхода команд, которые не являются утилитами, задокументировано в этом разделе. Статус выхода стандартных утилит задокументирован в соответствующих разделах.

Если команда не найдена, статус выхода должен быть 127. Если имя команды найдено, но это не исполняемая утилита, статус выхода должен быть 126. Приложения, которые вызывают утилиты без использования оболочки, должны использовать эти значения статуса выхода. сообщать о подобных ошибках.

Если команда не выполняется во время раскрытия или перенаправления слова, ее статус выхода должен быть больше нуля.

Внутренне, в целях принятия решения о том, завершается ли команда с ненулевым состоянием выхода, оболочка должна распознать все значение состояния, полученное для команды, эквивалентным макросу WEXITSTATUS функции wait () (как определено в томе System Interfaces POSIX.1-2008). При сообщении о состоянии выхода с помощью специального параметра «?» Оболочка должна сообщать о всех восьми битах доступного состояния выхода. Состояние выхода команды, которая завершилась из-за того, что она получила сигнал, должно быть сообщено как больше 128.

exitутилиты

Как объяснено в других разделах, определенные значения состояния выхода были зарезервированы для специальных целей и должны использоваться приложениями только для этих целей:

  • 126 - Файл для выполнения найден, но это не была исполняемая утилита.
  • 127 - Утилита для выполнения не найдена.
  • >128 - Команда была прервана сигналом.

Дальнейшая информация

Что бы это ни стоило, я смог проверить все, кроме одного, из списка кодов выхода со специальными значениями . Эта таблица кодов выхода полезна, поскольку она предоставляет более подробную информацию - и примеры того, как генерировать коды ошибок, документированные в справочнике Bash .

Попытка сгенерировать статус выхода 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)функция просто преобразовала число с плавающей точкой в ​​int (в данном случае 3) перед выходом:

#include <stdlib.h>
main()
{
    exit(3.14159);
}
Энтони Дж - справедливость для Моники
источник