Código de saída padrão quando o processo é finalizado?


54

Quando um processo é interrompido com um sinal manipulável como SIGINTou SIGTERMmas não manipula o sinal, qual será o código de saída do processo?

E quanto a sinais que não podem ser manuseados SIGKILL?

Pelo que posso dizer, matar um processo com SIGINTresultados prováveis ​​no código de saída 130, mas isso varia de acordo com a implementação do kernel ou do shell?

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

Não sei como testaria os outros sinais ...

$ ./myScript &
$ killall myScript
$ echo $?
0  # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0  # same problem

11
suas killall myScriptobras, portanto, o retorno do killall (e não do script!) é 0. Você pode colocar um kill -x $$[x sendo o número do sinal e $$ geralmente expandido pelo shell para o PID do script (funciona em sh, bash, ...)] dentro do script e teste o que era seu núcleo de saída.
precisa


comentário sobre a semi-pergunta: não coloque o myScript em segundo plano. (omitir &). Envie o sinal de outro processo shell (em outro terminal) e use-o $?após o término do myScript.
Matt Bianco

Respostas:


61

Os processos podem chamar a chamada do _exit()sistema (no Linux, consulte também exit_group()) com um argumento inteiro para relatar um código de saída ao pai. Embora seja um número inteiro, apenas os 8 bits menos significativos estão disponíveis para o pai (a exceção é quando se usa waitid()ou manipula SIGCHLD no pai para recuperar esse código , embora não no Linux).

O pai normalmente faz um wait()ou waitpid()para obter o status de seu filho como um número inteiro (embora também waitid()possa ser usada uma semântica um pouco diferente).

No Linux e na maioria dos Unices, se o processo terminar normalmente, os bits 8 a 15 desse número de status conterão o código de saída conforme passado exit(). Caso contrário, os 7 bits menos significativos (0 a 6) conterão o número do sinal e o bit 7 será definido se um núcleo for despejado.

perl, $?por exemplo, contém esse número, conforme definido 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

Os shells tipo Bourne também fazem o status de saída do último comando de execução em sua própria $?variável. No entanto, ele não contém diretamente o número retornado por waitpid(), mas uma transformação e é diferente entre os shells.

O que é comum entre todos os shells é que $?contém os 8 bits mais baixos do código de saída (o número passado para exit()) se o processo terminar normalmente.

A diferença é quando o processo é finalizado por um sinal. Em todos os casos, e isso é exigido pelo POSIX, o número será maior que 128. O POSIX não especifica qual pode ser o valor. Na prática, porém, em todas as conchas semelhantes a Bourne que eu conheço, os 7 bits mais baixos $?conterão o número do sinal. Mas, onde nestá o número do sinal,

  • em ash, zsh, pdksh, bash, a concha Bourne, $?é 128 + n. O que isto significa é que nessas conchas, se você receber um $?dos 129, você não sabe se é porque o processo foi encerrado com exit(129)ou se ele foi morto pelo sinal 1( HUPna maioria dos sistemas). Mas a lógica é que os shells, quando saem por si mesmos, por padrão, retornam o status de saída do último comando encerrado. Ao garantir que $?nunca seja maior que 255, isso permite ter um status de saída 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, $?É 256 + n. Isso significa que, a partir de um valor, $?você pode diferenciar entre um processo morto e não morto. As versões mais recentes de ksh, na saída, se $?eram maiores que 255, se matam com o mesmo sinal para poder relatar o mesmo status de saída ao pai. Embora isso pareça uma boa ideia, isso significa que kshirá gerar um despejo de núcleo extra (potencialmente substituindo o outro) se o processo for interrompido por um sinal de geração 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.
    

    Onde você pode até dizer que há um bug é que se ksh93mata mesmo que $?provenha de return 257uma função:

    $ 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. yashoferece um compromisso. Retorna 256 + 128 + n. Isso significa que também podemos diferenciar entre um processo morto e um que terminou corretamente. E ao sair, será relatado 128 + nsem ter que se suicidar e os efeitos colaterais que pode ter.

    $ 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 obter o sinal do valor de $?, a maneira portátil é usar kill -l:

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

(para portabilidade, você nunca deve usar números de sinal, apenas nomes de sinal)

Nas frentes não Bourne:

  • csh/ tcshe o fishmesmo que o shell Bourne, exceto que o status está em $statusvez de $?(observe que zshtambém define $statuscompatibilidade com csh(além de $?)).
  • rc: o status de saída $statustambém está, mas quando morto por um sinal, essa variável contém o nome do sinal (como sigtermou sigill+corese um núcleo foi gerado) em vez de um número, que é mais uma prova do bom design desse shell .
  • es. o status de saída não é uma variável. Se você se importa com isso, execute o comando como:

    status = <={cmd}
    

    que retornará um número sigtermou sigsegv+coreigual a rc.

Talvez para a completude, devemos mencionar zsh's $pipestatuse bash' s $PIPESTATUSmatrizes que contêm o status de saída dos componentes do último pipeline.

E também para completar, quando se trata de funções de shell e arquivos de origem, as funções padrão retornam com o status de saída do último comando executado, mas também podem definir um status de retorno explicitamente com o returnbuiltin. E vemos algumas diferenças aqui:

  • bashe mksh(desde R41, uma regressão ^ aparentemente alterada intencionalmente ) truncará o número (positivo ou negativo) para 8 bits. Assim, por exemplo, return 1234será definido $?como 210, return -- -1será definido $?como 255.
  • zshe pdksh(e derivadas que não sejam mksh) permitem qualquer número inteiro decimal de 32 bits assinado (-2 31 a 2 31 -1) (e truncar o número para 32 bits ).
  • ashe yashpermita qualquer número inteiro positivo de 0 a 2 31 -1 e retorne um erro para qualquer número desse valor.
  • ksh93para return 0a return 320set $?como é, mas para qualquer outra coisa, truncado para 8 bits. Cuidado, como já mencionado, que retornar um número entre 256 e 320 pode causar o kshsuicídio ao sair.
  • rce espermitir retornar qualquer coisa par.

Observe também que alguns shells também usam valores especiais de $?/ $statuspara relatar algumas condições de erro que não são o status de saída de um processo, como 127ou 126para comando não encontrado ou não executável (ou erro de sintaxe em um arquivo de origem) ...


11
an exit code to their parente to get the *status* of their child. você adicionou ênfase ao "status". É exit codee *status*o mesmo? Caso sim, qual é a origem de ter dois nomes? Caso não seja o mesmo, você poderia dar definição / referência de status?
N611x007

2
Existem 3 números aqui. O código de saída : o número passado para exit(). O status de saída : o número obtido pelo waitpid()qual inclui o código de saída, número do sinal e se houve um core despejado. E o número que alguns shells disponibilizam em uma de suas variáveis ​​especiais ( $?, $status) que é uma transformação do status de saída de forma que contenha o código de saída no caso de uma terminação normal, mas também carrega informações de sinal se o processo foi interrompido (esse também é geralmente chamado de status de saída ). Tudo isso está explicado na minha resposta.
Stéphane Chazelas

11
I ver obrigado! Eu definitivamente aprecio esta nota explícita da distinção aqui. Essas expressões relativas à saída são usadas de forma tão intercambiável em alguns lugares que vale a pena fazê-lo. A variante da variável shell ainda tem um nome (geral)? Então, sugiro esclarecer explicitamente antes de entrar em detalhes sobre as conchas. Sugiro inserir a explicação (do seu comentário) após o primeiro ou o segundo parágrafo.
N611x007

11
Você pode apontar para a citação do POSIX que diz que os primeiros 7 bits são o sinal? Tudo o que pude encontrar foi a > 128parte: "O status de saída de um comando que terminou porque recebeu um sinal deve ser relatado como superior a 128". pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Ciro Santilli escreveu:

11
@cuonglm, acho que não está disponível publicamente em nenhum outro lugar via HTTP, você ainda pode obtê-lo do gmane pelo NNTP. Procure o ID da mensagem efe764d811849b34eef24bfb14106f61@austingroupbugs.net(de 06/05/2015) ouXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas

23

Quando um processo sai, ele retorna um valor inteiro para o sistema operacional. Na maioria das variantes unix, esse valor é obtido no módulo 256: tudo, exceto os bits de ordem inferior, é ignorado. O status de um processo filho é retornado ao pai através de um número inteiro de 16 bits no qual

  • os bits 0–6 (os 7 bits de ordem inferior) são o número do sinal usado para interromper o processo ou 0 se o processo sair normalmente;
  • o bit 7 é definido se o processo foi morto por um sinal e núcleo despejado;
  • os bits 8–15 são o código de saída do processo, se o processo sair normalmente, ou 0, se o processo for morto por um sinal.

O status é retornado pela waitchamada do sistema ou por um de seus irmãos. O POSIX não especifica a codificação exata do status de saída e do número do sinal; apenas fornece

  • uma maneira de saber se o status de saída corresponde a um sinal ou a uma saída normal;
  • uma maneira de acessar o código de saída, se o processo sair normalmente;
  • uma maneira de acessar o número do sinal, se o processo foi interrompido por um sinal.

A rigor, não há código de saída quando um processo é interrompido por um sinal: o que existe é um status de saída .

Em um script de shell, o status de saída de um comando é relatado por meio da variável especial $?. Essa variável codifica o status de saída de uma maneira ambígua:

  • Se o processo sair normalmente, então $?é seu status de saída.
  • Se o processo foi interrompido por um sinal, então $?é 128 mais o número do sinal na maioria dos sistemas. O POSIX apenas exige que $?seja maior que 128 neste caso; O ksh93 adiciona 256 em vez de 128. Nunca vi uma variante unix que fiz algo diferente de adicionar uma constante ao número do sinal.

Portanto, em um script shell, você não pode dizer conclusivamente se um comando foi morto por um sinal ou saiu com um código de status maior que 128, exceto com ksh93. É muito raro os programas saírem com códigos de status maiores que 128, em parte porque os programadores evitam isso devido à $?ambiguidade.

SIGINT é o sinal 2 na maioria das variantes unix, portanto, $?é 128 + 2 = 130 para um processo que foi eliminado pelo SIGINT. Você verá 129 para SIGHUP, 137 para SIGKILL etc.


Muito melhor redigido e mais direto ao ponto do que o meu, mesmo que diga essencialmente as mesmas coisas. Você pode esclarecer que $?é apenas para conchas do tipo Bourne. Veja também yashpara um comportamento diferente (mas ainda POSIX). Também de acordo com o POSIX + XSI (Unix), a kill -2 "$pid"enviará um SIGINT ao processo, mas o número real do sinal pode não ser 2, então $? não será necessariamente 128 + 2 (ou 256 + 2 ou 384 + 2), mas kill -l "$?"retornará INT, e é por isso que aconselho a portabilidade a não se referir aos números em si.
Stéphane Chazelas

8

Isso depende da sua concha. Na bash(1)página do manual, seção SHELL GRAMMAR , subseção Comandos Simples :

O valor de retorno de um comando simples é [...] 128+ n se o comando for finalizado pelo sinal n .

Como SIGINTem seu sistema o sinal é o número 2, o valor de retorno é 130 quando é executado no Bash.


11
Como no mundo você encontra isso, ou sabe até onde procurar? Eu me curvo diante do seu gênio.
Cory Klein

11
@CoryKlein: Experiência, principalmente. Ah, e você provavelmente também desejará a signal(7)página de manual.
Ignacio Vazquez-Abrams

coisas legais; você sabe se eu incluí arquivos em C com essas constantes por acaso? +1
Rui F Ribeiro

@CoryKlein Por que você não selecionou isso como a resposta correta?
Rui F Ribeiro

3

Parece ser o lugar certo para mencionar que o SVr4 introduziu waitid () em 1989, mas nenhum programa importante parece usá-lo até agora. waitid () permite recuperar os 32 bits completos do código exit ().

Há cerca de dois meses, reescrevi a parte de controle de espera / trabalho do Bourne Shell para usar waitid () em vez de waitpid (). Isso foi feito para remover a limitação que mascara o código de saída com 0xFF.

A interface waitid () é muito mais limpa que as implementações wait () anteriores, exceto a chamada cwait () do UNOS de 1980.

Você pode estar interessado em ler a página de manual em:

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

e verifique a seção "Substituição de parâmetros" atualmente na página 8.

As novas variáveis ​​.sh. * Foram introduzidas para a interface waitid (). Essa interface não possui mais significados ambíguos para os números conhecidos por $? e tornar a interface muito mais fácil.

Observe que você precisa de um waitid () compatível com POSIX para poder usar esse recurso; portanto, atualmente, o Mac OS X e Linux não oferecem isso, mas o waitid () é emulado na chamada waitpid (), e assim por diante. Na plataforma não-POSIX, você ainda obterá apenas 8 bits do código de saída.

Em resumo: .sh.status é o código de saída numérico, .sh.code é o motivo de saída numérica.

Para uma melhor portabilidade, existe: .sh.codename para a versão textual do motivo da saída, por exemplo, "DUMPED" e .sh.termsig, o nome original do sinal que encerrou o processo.

Para melhor uso, existem dois valores .sh.codename não relacionados à saída: "NOEXEC" e "NOTFOUND" que são usados ​​quando um programa não pode ser iniciado.

O FreeBSD corrigiu seu bug waitid () kerlnel dentro de 20 horas após o meu relatório, o Linux ainda não começou com a correção. Espero que 26 anos após a introdução desse recurso que está no POSIX agora, todos os sistemas operacionais o suportem em breve.


Uma resposta relacionada é unix.stackexchange.com/a/453432/5132 .
JdeBP
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.