Uso de códigos "reservados" para el estado de salida de los scripts de shell

15

Recientemente me encontré con esta lista de Códigos de salida con significados especiales de la Guía avanzada de secuencias de comandos Bash. Se refieren a estos códigos como reservados y recomiendan que:

De acuerdo con la tabla anterior, los códigos de salida 1-2, 126-165 y 255 tienen significados especiales y, por lo tanto, deben evitarse para los parámetros de salida especificados por el usuario.

Hace un tiempo, escribí un script que usaba los siguientes códigos de estado de salida:

  • 0 - éxito
  • 1 - nombre de host incorrecto
  • 2 - argumentos no válidos especificados
  • 3 - privilegios de usuario insuficientes

Cuando escribí el script no tenía conocimiento de ningún código de salida especial, así que simplemente comencé en 1 para la primera condición de error e incrementé el estado de salida para cada tipo de error sucesivo.

Escribí el script con la intención de que en una etapa posterior pudiera ser invocado por otros scripts (que podrían verificar los códigos de salida distintos de cero). En realidad no he hecho eso todavía; Hasta ahora solo he ejecutado el script desde mi shell interactivo (Bash) y me preguntaba qué / si algún problema podría ser causado al usar mis códigos de salida personalizados. ¿Cuán relevante / importante es la recomendación de la Guía avanzada de secuencias de comandos Bash?

No pude encontrar ningún consejo que corroborara en la documentación de Bash; su sección sobre el estado de salida simplemente enumera los códigos de salida utilizados por Bash, pero no indica que ninguno de estos esté reservado ni advierta contra su uso para sus propios scripts / programas.

Anthony G - justicia para Monica
fuente
66
Yo, y otros, consideramos que el ABSG es generalmente de baja calidad. En mi opinión, el autor de la página que ha vinculado está haciendo una afirmación no respaldada de que los códigos de salida enumerados están reservados en base, aparentemente, al hecho de que el propio shell los usa para significados específicos. Ha habido intentos de crear estándares para scripts, ninguno de los cuales ha tenido éxito. Lo importante es documentar los códigos de error que elija para que los consumidores de sus scripts (por ejemplo, otros scripts) sepan qué hacer en función de ellos.
Pausado hasta nuevo aviso.
@DennisWilliamson Si publicas tu comentario como respuesta, estaré encantado de votarlo; Ya he votado todas las otras respuestas ya que encontré que cada una de ellas es útil. Si bien su respuesta es similar en contenido a la de David King (y en menor medida a la de Zwol), usted declara explícitamente que no hay evidencia de la afirmación en la cita de ABSG.
Anthony G - justicia para Monica
1
Gracias por la oferta, pero creo que mi comentario debería permanecer como tal.
Pausado hasta nuevo aviso.
Desde entonces descubrí que la especificación POSIX incluye consejos similares, así que agregué esa información a mi propia respuesta (que contiene los resultados de mi investigación desde que hice esta pregunta).
Anthony G - justicia para Monica

Respuestas:

10

Ha habido varios intentos de estandarizar los significados de los códigos de salida del proceso. Además del que mencionas, sé de:

  • los BSD tienen sysexits.hque define significados para valores desde 64 en adelante.

  • Los grepdocumentos de GNU que indican que el código de salida 0 significa que se encontró al menos una coincidencia, 1 significa que no se encontraron coincidencias y 2 significa que se produjo un error de E / S; esta convención obviamente también es útil para otros programas para los cuales la distinción entre "nada salió mal pero no encontré nada" y "ocurrió un error de E / S" es significativa.

  • Muchas implementaciones de la función de biblioteca C systemusan el código de salida 127 para indicar que el programa no existe o no pudo iniciarse.

  • En Windows, los NTSTATUScódigos (que están convenientemente dispersos por todo el espacio de números de 32 bits) pueden usarse como códigos de salida, particularmente los que indican que un proceso se terminó debido a un mal comportamiento catastrófico (por ejemplo STATUS_STACK_OVERFLOW).

No puede contar con que un programa determinado obedezca una de estas convenciones en particular. La única regla confiable es que el código de salida 0 es exitoso y cualquier otra cosa es algún tipo de falla. (Tenga en cuenta que de C89 EXIT_SUCCESSse no garantiza tener el valor cero, sin embargo, exit(0)se requiere que se comporten de forma idéntica al exit(EXIT_SUCCESS)., Incluso si los valores no son los mismos)

zwol
fuente
Gracias. Fue difícil elegir una respuesta sobre las otras, pero acepto esta, ya que respondió a mi pregunta y al mismo tiempo proporcionó una amplia muestra de los diferentes códigos de salida en uso (con enlaces relevantes): merece más que los 3 votos positivos. actualmente tiene.
Anthony G - justicia para Monica
11

Ningún código de salida tiene un significado especial, pero el valor en $?puede tener un significado especial.

El problema es la forma en que Bourne Shell y ksh93 manejaron y enviaron códigos de salida y situaciones de error a la variable de shell $?. Al contrario de lo que enumera, solo los siguientes valores $?tienen un significado especial:

  • 126 No se pudo ejecutar el binario aunque exista
  • 127 El binario especificado no existe
  • El estado de salida 128 fue == 0 pero existe algún problema no especificado

Además, hay un rango de $?códigos específicos de la plataforma y de la plataforma no especificados > 128 que está reservado para un programa que fue interrumpido por una señal:

  • Bourne Shell bash y ksh88 usan 128 + número de señal
  • ksh93 usa 256 + número de señal.

Otros valores no dan problemas, ya que pueden distinguirse de los $?valores especiales de shell .

En particular, los valores 1 y 2 no se usan para condiciones especiales, sino que son solo códigos de salida utilizados por comandos incorporados que podrían actuar de la misma manera cuando no están incorporados. Parece que el puntero a la guía de scripts de bash que proporcionó no es un buen manual, ya que solo enumera los códigos utilizados por bash sin comentar si un código específico es un valor especial que debe evitarse para los propios scripts.

Las versiones más nuevas de Bourne Shell usan en waitid()lugar de waitpid()esperar a que el programa salga y waitid()(introducido en 1989 para SVr4) usa una mejor interfaz syscall (similar a lo que UNOS usaba en 1980).

Como las versiones más recientes de Bourne Shell codifican el motivo de salida en una variable separada ${.sh.code}/ ${.sh.codename}que el código de salida que está en ${.sh.status}/ ${.sh.termsig}, consulte http://schillix.sourceforge.net/man/man1/bosh.1.html , el código de salida no se sobrecarga con estados especiales y, como resultado del uso de `waitid (), Bourne Shell ahora admite la devolución de los 32 bits del código de salida, no solo los 8 bits bajos.

Por cierto: tenga cuidado de no ser exit(256)similar a un programa C o script de shell, ya que esto $?se interpreta como 0 en un shell clásico.

astuto
fuente
2
Por cierto: hice un informe de error contra FreeBSD y el kernel de Linux para este waitid()error a fines de mayo. La gente de FreeBSD solucionó el problema en 20 horas, la gente de Linux no está interesada en corregir su error. ... y la gente Cygwin dice que son bug por error Linux compatible ;-)
schily
2
Este comportamiento es requerido por la especificación Single Unix. Hay un valor de 32 bits, sí, pero ese valor contiene un campo de bits de 8 bits que contiene los 8 bits bajos del valor _exit. Por favor, vincule el informe de errores de FreeBSD al que se refiere, tal vez estoy malinterpretando el problema que describe.
Random832
2
El OP etiquetó la pregunta con bash y mencionó Bash en el texto de la pregunta. Bash es un shell derivado de Bourne. No admite ${.sh.}variables. Sin embargo, es cierto que usted dice "Bourne" y no "derivado de Bourne" (aunque sí incluye ksh93).
Pausado hasta nuevo aviso.
2
Esta respuesta parece ser muy específica para su variante particular de algunos Unix derivados de SVR4. Sea más claro acerca de lo que es portátil y lo que no lo es, teniendo en cuenta que no existe tal cosa como "el" shell Bourne, a menos que se refiera al que estaba en V7.
zwol
44
Por el contrario, creo que es usted quien está subestimando el rango de variación aquí, especialmente la variación histórica. Hace que parezca que /bin/shse puede confiar en que se comportará de manera consistente con estos códigos de salida especiales multiplataforma, lo cual no es cierto. (No me importa si /bin/shse puede decir que algún sistema en particular sea ​​un "shell Bourne real". Es mucho más importante saber que nada de esto está en POSIX, y que la mayoría de las cosas que usted cita como "sistemas Unix reales" no t proporcione un POSIX compatible de /bin/shtodos modos.)
zwol
6

Para las secuencias de comandos de shell, a veces incluyo en la fuente el equivalente de sysexist.hshell con códigos de salida reservados con shell (con el prefijo S_EX_), que he denominadoexit.sh

Básicamente es:

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

Y se puede generar 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; }'

Sin embargo, no lo uso mucho, pero lo que sí uso es una función de shell que invierte los códigos de error en sus formatos de cadena. He nombrado él exit2str. Suponiendo que haya nombrado el exit.shgenerador anterior exit.sh.sh, el código para exit2strse puede generar 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 en PS1mi shell interactivo para que después de cada comando que ejecuto, pueda ver su estado de salida y su forma de cadena (si tiene una forma de cadena conocida):

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

Para obtener estos, necesita un insourcable para la función exit2str:

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

y luego úselo en su ~/.bashrcpara guardar y traducir el código de salida en cada símbolo del sistema y mostrarlo en su indicador ( PS1):

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

Es bastante útil para observar cómo algunos programas siguen las convenciones del código de salida y otros no, para aprender acerca de las convenciones del código de salida, o simplemente para poder ver qué sucede más fácilmente. Después de haberlo usado durante algún tiempo, puedo decir que muchos scripts de shell orientados al sistema siguen las convenciones. EX_USAGEEs particularmente bastante común, aunque otros códigos, no mucho. Trato de seguir las convenciones de vez en cuando, aunque siempre hay $S_EX_ANY(1) para gente perezosa (yo soy uno).

PSkocik
fuente
Me pregunto si hay algo así como una asignación entre un código de error y un código de salida para usar si el error informado con ese código de error da como resultado una salida de error. Es posible que deba idear un mapeo razonable.
PSkocik
1
¡Guauu! No esperaba una respuesta tan elaborada. Definitivamente lo probaré como una buena forma de ver cómo se comportan los diferentes comandos. Gracias.
Anthony G - justicia para Monica
4

Siempre que documente sus códigos de salida para que los recuerde dentro de un año, cuando tenga que regresar y modificar el script, estará bien. La idea de "códigos de salida reservados" realmente no se aplica más que decir que es costumbre usarla 0como código de éxito y cualquier otra cosa como código de falla.

David King
fuente
4

La mejor referencia que pude encontrar fue esta: http://tldp.org/LDP/abs/html/exitcodes.html

De acuerdo a esto:

1 es una trampa general para los errores, y siempre he visto que se usa para errores definidos por el usuario.

2 es por mal uso de los complementos integrados de shell, como un error de sintaxis

Para responder su pregunta directamente, su script estará bien usando los códigos de error reservados, funcionará como se espera, suponiendo que maneje el error en función del código de error = 1/2/3.

Sin embargo, posiblemente sería confuso si se encuentra con alguien que conozca y use los códigos de error reservados, lo que parece bastante raro.

Otra opción disponible para usted es hacer eco del error si hay uno y luego salir, suponiendo que su script siga la convención de Linux de "ninguna noticia es una buena noticia" y echo no es nada exitoso.

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

Según las respuestas que recibí (fue difícil elegir una sobre las otras), no es dañino indicar ciertos tipos de errores al usar un código de salida que Bash también usa. Bash (o cualquier otro shell de Unix) no hará nada especial (como ejecutar controladores de excepciones) si una secuencia de comandos de usuario sale con uno de estos códigos de error.

Parece que el autor de la Guía avanzada de secuencias de comandos Bash está de acuerdo con los intentos de BSD de estandarizar los códigos de salida (sysexits.h ) y simplemente recomienda que cuando los usuarios escriben scripts de shell, no especifiquen códigos de salida que ya entren en conflicto con los códigos de salida predefinidos en uso, es decir, restringen sus códigos de salida personalizados a los 50 códigos de estado disponibles en el rango 64-113.

Aprecio la idea (y la justificación) pero hubiera preferido que el autor fuera más explícito que no es dañino ignorar el consejo, aparte de los casos en que el consumidor de un guión está buscando errores, como el ejemplo citado de 127 ( command not found)

Especificaciones POSIX relevantes

Investigué lo que POSIX tiene que decir sobre los códigos de salida y la especificación POSIX parece estar de acuerdo con el autor de la Guía Avanzada de Bash-Scripting. He citado las especificaciones POSIX relevantes (énfasis mío):

Estado de salida para comandos

Cada comando tiene un estado de salida que puede influir en el comportamiento de otros comandos de shell. El estado de salida de los comandos que no son utilidades se documenta en esta sección. El estado de salida de las utilidades estándar está documentado en sus respectivas secciones.

Si no se encuentra un comando, el estado de salida será 127. Si se encuentra el nombre del comando, pero no es una utilidad ejecutable, el estado de salida será 126. Las aplicaciones que invocan utilidades sin usar el shell deben usar estos valores de estado de salida para informar errores similares.

Si un comando falla durante la expansión o redirección de palabras, su estado de salida será mayor que cero.

Internamente, con el fin de decidir si un comando sale con un estado de salida distinto de cero, el shell reconocerá el valor de estado completo recuperado para el comando por el equivalente de la macro WEXITSTATUS de la función wait () (como se define en el volumen de Interfaces del sistema de POSIX.1-2008). Al informar el estado de salida con el parámetro especial '?', El shell informará los ocho bits completos del estado de salida disponibles. El estado de salida de un comando que finalizó porque recibió una señal se informará como mayor que 128.

La exitutilidad

Como se explicó en otras secciones, ciertos valores de estado de salida se han reservado para usos especiales y las aplicaciones deben usarlos solo para esos fines:

  • 126 - Se encontró un archivo a ejecutar, pero no era una utilidad ejecutable.
  • 127 - No se encontró una utilidad para ejecutar.
  • >128 - Un comando fue interrumpido por una señal.

Más información

Por lo que vale, pude verificar todos menos uno de la lista de Códigos de salida con significados especiales . Esta tabla de códigos de salida es útil ya que proporciona más detalles y ejemplos de cómo generar los códigos de error documentados en la referencia de Bash .

Intenta generar un estado de salida de 128

Usando las versiones Bash 3.2.25 y 4.2.46, traté de arrojar un 128 Invalid argument to exiterror, pero cada vez que recibí un 255 (estado de salida fuera de rango). Por ejemplo, si exit 3.14159se ejecuta como parte de un script de shell o en un shell secundario interactivo, el shell sale con un código de 255:

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

Para divertirme aún más, también intenté ejecutar un programa C simple, pero en este caso, parece que la exit(3)función simplemente convirtió el flotador en int (3 en este caso) antes de salir:

#include <stdlib.h>
main()
{
    exit(3.14159);
}
Anthony G - justicia para Monica
fuente