¿Código de salida predeterminado cuando finaliza el proceso?

54

Cuando se mata un proceso con una señal manejable como SIGINTo SIGTERMpero no maneja la señal, ¿cuál será el código de salida del proceso?

¿Qué pasa con las señales no manejables como SIGKILL?

Por lo que puedo decir, matar un proceso con SIGINTresultados probables en el código de salida 130, pero ¿variaría eso según la implementación del kernel o shell?

$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130

No estoy seguro de cómo probaría las otras señales ...

$ ./myScript &
$ killall myScript
$ echo $?
0  # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0  # same problem
Cory Klein
fuente
1
sus killall myScripttrabajos, por lo tanto, el retorno del killall (¡y no del script!) es 0. Podría colocar un kill -x $$[x como el número de señal, y $$ generalmente expandido por el shell al PID de ese script (funciona en sh, bash, ...)] dentro del script y luego pruebe cuál era su núcleo de salida.
Olivier Dulac
Superset: stackoverflow.com/questions/1101957/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
comente sobre la semipregunta: no ponga myScript en segundo plano. (omitir &) Envíe la señal desde otro proceso de shell (en otro terminal), luego puede usarla $?después de que myScript haya finalizado.
MattBianco

Respuestas:

61

Los procesos pueden llamar a la llamada del _exit()sistema (en Linux, ver también exit_group()) con un argumento entero para informar un código de salida a su padre. Aunque es un número entero, solo los 8 bits menos significativos están disponibles para el padre (la excepción es cuando se usa waitid()o maneja en SIGCHLD en el padre para recuperar ese código , aunque no en Linux).

El padre generalmente hará un wait()o waitpid()para obtener el estado de su hijo como un entero (aunque también waitid()se puede usar una semántica algo diferente).

En Linux y en la mayoría de los Unices, si el proceso finalizó normalmente, los bits 8 a 15 de ese número de estado contendrán el código de salida que se le pasó exit(). De lo contrario, los 7 bits menos significativos (0 a 6) contendrán el número de señal y el bit 7 se establecerá si se volcó un núcleo.

perl's, $?por ejemplo, contiene ese número establecido por waitpid():

$ perl -e 'system q(kill $$); printf "%04x\n", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04x\n", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04x\n", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status

Los shells tipo Bourne también hacen que el estado de salida del último comando de ejecución en su propia $?variable. Sin embargo, no contiene directamente el número devuelto por waitpid(), sino una transformación, y es diferente entre los shells.

Lo que es común entre todos los shells es que $?contiene los 8 bits más bajos del código de salida (el número pasado exit()) si el proceso terminó normalmente.

Donde difiere es cuando el proceso termina con una señal. En todos los casos, y eso es requerido por POSIX, el número será mayor que 128. POSIX no especifica cuál puede ser el valor. Sin embargo, en la práctica, en todos los shells tipo Bourne que conozco, los 7 bits más bajos $?contendrán el número de señal. Pero, ¿dónde nestá el número de señal?

  • en ash, zsh, pdksh, bash, el shell Bourne, $?es 128 + n. Lo que eso significa es que en esos proyectiles, si obtienes uno $?de 129, no sabes si es porque el proceso salió exit(129)o si la señal lo mató 1( HUPen la mayoría de los sistemas). Pero la razón es que los shells, cuando salen ellos mismos, por defecto devuelven el estado de salida del último comando salido. Al asegurarse de $?que nunca sea mayor que 255, eso permite tener un estado de salida consistente:

    $ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill \$\$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill \$\$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
    
  • ksh93, $?Es 256 + n. Eso significa que a partir de un valor de $?usted puede diferenciar entre un proceso muerto y no muerto. Las versiones más nuevas de ksh, al salir, si $?eran superiores a 255, se suicidan con la misma señal para poder informar el mismo estado de salida a su padre. Si bien eso suena como una buena idea, eso significa que kshgenerará un volcado de núcleo adicional (posiblemente sobrescribiendo el otro) si el proceso fue eliminado por una señal generadora de núcleo:

    $ ksh -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    ksh: 16828: Terminated
    10f # 256 + 15
    $ ksh -c 'sh -c "kill -ILL \$\$"; exit'; printf '%x\n' "$?"
    ksh: 16816: Illegal instruction(coredump)
    Illegal instruction(coredump)
    104 # 256 + 15, ksh did indeed kill itself so as to report the same
        # exit status as sh. Older versions of `ksh93` would have returned
        # 4 instead.
    

    Donde incluso podría decir que hay un error es que se ksh93suicida incluso si $?proviene de una función return 257realizada por:

    $ ksh -c 'f() { return "$1"; }; f 257; exit'
    zsh: hangup     ksh -c 'f() { return "$1"; }; f 257; exit'
    # ksh kills itself with a SIGHUP so as to report a 257 exit status
    # to its parent
    
  • yash. yashofrece un compromiso Vuelve 256 + 128 + n. Eso significa que también podemos diferenciar entre un proceso cancelado y uno que finalizó correctamente. Y al salir, informará 128 + nsin tener que suicidarse y los efectos secundarios que puede tener.

    $ yash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    18f # 256 + 128 + 15
    $ yash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    8f  # that's from a exit(143), yash was not killed
    

Para obtener la señal del valor de $?, la forma portátil es usar kill -l:

$ /bin/kill 0
Terminated
$ kill -l "$?"
TERM

(para portabilidad, nunca debe usar números de señal, solo nombres de señal)

En los frentes no Bourne:

  • csh/ tcshe fishigual que el shell Bourne, excepto que el estado está en $statuslugar de $?(tenga en cuenta que zshtambién establece la $statuscompatibilidad con csh(además de $?)).
  • rc: El estado de salida está en $statustambién, pero cuando matado por una señal, dicha variable contiene el nombre de la señal (como sigtermo sigill+coresi se generó un núcleo) en lugar de un número, que es otra prueba del diseño bien de que shell .
  • es. El estado de salida no es una variable. Si te importa, ejecutas el comando como:

    status = <={cmd}
    

    que devolverá un número sigtermo me sigsegv+coregusta en rc.

Tal vez para completar, deberíamos mencionar zshlas matrices 's $pipestatusy bash' $PIPESTATUSque contienen el estado de salida de los componentes de la última tubería.

Y también para completar, cuando se trata de funciones de shell y archivos de origen, las funciones por defecto regresan con el estado de salida del último comando ejecutado, pero también pueden establecer un estado de retorno explícitamente con el returnincorporado. Y vemos algunas diferencias aquí:

  • bashy mksh(desde R41, una regresión ^ Wchange aparentemente introducida intencionalmente ) truncará el número (positivo o negativo) a 8 bits. Entonces, por ejemplo, return 1234se establecerá $?en 210, return -- -1se establecerá $?en 255.
  • zshy pdksh(y derivados distintos de mksh) permiten cualquier entero decimal con signo de 32 bits (-2 31 a 2 31 -1) (y truncan el número a 32 bits).
  • ashy yashpermitir cualquier número entero positivo de 0 a 2 31 -1 y devolver un error para cualquier número de eso.
  • ksh93para return 0al return 320conjunto $?como es, pero para cualquier otra cosa, truncado a 8 bits. Tenga en cuenta, como ya se mencionó, que devolver un número entre 256 y 320 podría causar la kshmuerte al salir.
  • rcy espermitir devolver cualquier cosa, incluso listas.

También tenga en cuenta que algunos shells también usan valores especiales de $?/ $statuspara informar algunas condiciones de error que no son el estado de salida de un proceso, como 127o 126para el comando no encontrado o no ejecutable (o error de sintaxis en un archivo de origen) ...

Stéphane Chazelas
fuente
1
an exit code to their parenty to get the *status* of their child. has agregado énfasis en el "estado". Es exit codey *status*lo mismo? Caso sí, ¿cuál es el origen de tener dos nombres? Caso no igual, ¿podría dar definición / referencia de estado?
n611x007
2
Hay 3 números aquí. El código de salida : el número pasado a exit(). El estado de salida : el número obtenido por el waitpid()cual incluye el código de salida, el número de señal y si hubo un núcleo volcado. Y el número que algunos shells ponen a disposición en una de sus variables especiales ( $?, $status) que es una transformación del estado de salida de tal manera que contiene el código de salida en caso de que haya una terminación normal, pero también lleva información de señal si el proceso fue cancelado (a ese también se le llama generalmente estado de salida ). Todo eso se explica en mi respuesta.
Stéphane Chazelas
1
¡Ya entiendo, gracias! Definitivamente aprecio esta nota explícita de la distinción aquí. Estas expresiones con respecto a la salida se usan de manera intercambiable en algunos lugares que vale la pena hacerlo. ¿La variante de shell tiene incluso un nombre (general)? Por lo tanto, sugeriría que lo aclare explícitamente antes de entrar en detalles sobre los depósitos. Sugeriría insertar la explicación (de su comentario) después de su primer o segundo párrafo.
n611x007
1
¿Puede señalar la cita POSIX que dice que los primeros 7 bits son la señal? Todo lo que pude encontrar fue la > 128parte: "El estado de salida de un comando que terminó porque recibió una señal se informará como mayor que 128". pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
1
@cuonglm, no creo que esté disponible públicamente en ningún otro lugar a través de HTTP, todavía puede obtenerlo de gmane a través de NNTP. Busque la identificación del mensaje [email protected](del 06/05/2015) oXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas
23

Cuando sale un proceso, devuelve un valor entero al sistema operativo. En la mayoría de las variantes de Unix, este valor se toma en el módulo 256: se ignora todo menos los bits de orden inferior. El estado de un proceso hijo se devuelve a su padre a través de un entero de 16 bits en el que

  • los bits 0–6 (los 7 bits de orden inferior) son el número de señal que se utilizó para matar el proceso, o 0 si el proceso salió normalmente;
  • el bit 7 se establece si el proceso fue eliminado por una señal y un núcleo volcado;
  • los bits 8-15 son el código de salida del proceso si el proceso salió normalmente, o 0 si el proceso fue eliminado por una señal.

El estado lo devuelve la waitllamada del sistema o uno de sus hermanos. POSIX no especifica la codificación exacta del estado de salida y el número de señal; solo proporciona

  • una forma de saber si el estado de salida corresponde a una señal o a una salida normal;
  • una forma de acceder al código de salida, si el proceso salió normalmente;
  • una forma de acceder al número de señal, si el proceso fue cancelado por una señal.

Estrictamente hablando, no hay código de salida cuando un proceso es eliminado por una señal: lo que sí hay es un estado de salida .

En un script de shell, el estado de salida de un comando se informa a través de la variable especial $?. Esta variable codifica el estado de salida de una manera ambigua:

  • Si el proceso salió normalmente, entonces $?es su estado de salida.
  • Si el proceso fue eliminado por una señal, entonces $?es 128 más el número de señal en la mayoría de los sistemas. POSIX solo exige que $?sea ​​mayor que 128 en este caso; ksh93 agrega 256 en lugar de 128. Nunca he visto una variante de Unix que hiciera otra cosa que agregar una constante al número de señal.

Por lo tanto, en un script de shell no se puede determinar de manera concluyente si un comando fue eliminado por una señal o si salió con un código de estado superior a 128, excepto con ksh93. Es muy raro que los programas salgan con códigos de estado superiores a 128, en parte porque los programadores lo evitan debido a la $?ambigüedad.

SIGINT es la señal 2 en la mayoría de las variantes de Unix, por lo tanto, $?es 128 + 2 = 130 para un proceso que fue eliminado por SIGINT. Verás 129 para SIGHUP, 137 para SIGKILL, etc.

Gilles 'SO- deja de ser malvado'
fuente
Mucho mejor redactado y más al grano que el mío, incluso si en esencia dice lo mismo. Es posible que desee aclarar que $?es solo para conchas tipo Bourne. Consulte también yashun comportamiento diferente (pero aún POSIX). También según POSIX + XSI (Unix), a kill -2 "$pid"enviará un SIGINT al proceso, pero el número de señal real puede no ser 2, entonces $? no necesariamente será 128 + 2 (o 256 + 2 o 384 + 2), aunque kill -l "$?"regresará INT, por lo que recomendaría que la portabilidad no se refiera a los números en sí.
Stéphane Chazelas
8

Eso depende de tu caparazón. Desde la bash(1)página de manual, sección SHELL GRAMMAR , subsección Comandos simples :

El valor de retorno de un comando simple es [...] 128+ n si el comando termina con la señal n .

Como SIGINTen su sistema está la señal número 2, el valor de retorno es 130 cuando se ejecuta bajo Bash.

Ignacio Vazquez-Abrams
fuente
1
¿Cómo en el mundo encuentras esto, o incluso sabes dónde buscar? Me inclino ante tu genio.
Cory Klein
1
@CoryKlein: Experiencia, principalmente. Ah, y es probable que también quieras la signal(7)página de manual.
Ignacio Vazquez-Abrams
cosas interesantes; ¿Sabes si por casualidad he incluido archivos en C con esas constantes? +1
Rui F Ribeiro
@CoryKlein ¿Por qué no ha seleccionado esto como la respuesta correcta?
Rui F Ribeiro
3

Parece ser el lugar adecuado para mencionar que SVr4 introdujo waitid () en 1989, pero ningún programa importante parece usarlo hasta ahora. waitid () permite recuperar los 32 bits completos del código de salida ().

Hace aproximadamente 2 meses, reescribí la parte de espera / control de trabajo de Bourne Shell para usar waitid () en lugar de waitpid (). Esto se hizo para eliminar la limitación que enmascara el código de salida con 0xFF.

La interfaz waitid () es mucho más limpia que las implementaciones anteriores wait (), excepto la llamada cwait () de UNOS desde 1980.

Puede interesarle leer la página de manual en:

http://schillix.sourceforge.net/man/man1/bosh.1.html

y revise la sección "Sustitución de parámetros" que se encuentra actualmente en la página 8.

Las nuevas variables .sh. * Se han introducido para la interfaz waitid (). Esta interfaz ya no tiene significados ambiguos para los números conocidos por $? y hacer la interfaz mucho más fácil.

Tenga en cuenta que debe tener un waitid () compatible con POSIX para poder usar esta función, por lo que Mac OS X y Linux actualmente no ofrecen esto, pero el waitid () se emula en la llamada waitpid (), así que en un Sin la plataforma POSIX, solo obtendrá 8 bits del código de salida.

En resumen: .sh.status es el código de salida numérico, .sh.code es el motivo de salida numérico.

Para una mejor portabilidad, existe: .sh.codename para la versión textual del motivo de salida, por ejemplo, "DUMPED" y .sh.termsig, el nombre único de la señal que finalizó el proceso.

Para un mejor uso, hay dos valores .sh.codename no relacionados con la salida: "NOEXEC" y "NOTFOUND" que se utilizan cuando no se puede iniciar un programa.

FreeBSD corrigió su error del kernel waitid () dentro de las 20 horas posteriores a mi informe, Linux aún no comenzó con su corrección. Espero que 26 años después de presentar esta característica que se encuentra en POSIX ahora, todos los sistemas operativos lo admitirán pronto.

astuto
fuente
Una respuesta relacionada es unix.stackexchange.com/a/453432/5132 .
JdeBP