Usando códigos "reservados" para o status de saída de scripts de shell

15

Recentemente, deparei-me com esta lista de códigos de saída com significados especiais do Advanced Bash-Scripting Guide. Eles se referem a esses códigos como reservados e recomendam que:

De acordo com a tabela acima, os códigos de saída 1-2, 126-165 e 255 têm significados especiais e, portanto, devem ser evitados para parâmetros de saída especificados pelo usuário.

Há um tempo atrás, escrevi um script que usava os seguintes códigos de status de saída:

  • 0 - sucesso
  • 1 - nome de host incorreto
  • 2 - argumentos inválidos especificados
  • 3 - privilégios de usuário insuficientes

Quando escrevi o script, não tinha conhecimento de nenhum código de saída especial, então comecei em 1 para a primeira condição de erro e incrementei o status de saída para cada tipo de erro sucessivo.

Escrevi o script com a intenção de que, posteriormente, ele pudesse ser chamado por outros scripts (que poderiam verificar códigos de saída diferentes de zero). Ainda não fiz isso; até agora, eu apenas executei o script do meu shell interativo (Bash) e fiquei imaginando o que / ou algum problema poderia ser causado usando meus códigos de saída personalizados. Quão relevante / importante é a recomendação do Advanced Bash-Scripting Guide?

Não encontrei nenhum conselho que comprove a documentação do Bash; sua seção no Status da saída simplesmente lista os códigos de saída usados ​​pelo Bash, mas não afirma que nenhum deles está reservado ou avisa sobre o uso deles para seus próprios scripts / programas.

Anthony G - justiça para Monica
fonte
6
Eu e outros consideramos o ABSG geralmente de baixa qualidade. Na minha opinião, o autor da página que você vinculou está fazendo uma afirmação sem suporte de que os códigos de saída listados são reservados com base, aparentemente, no fato de que o próprio shell os usa para significados específicos. Houve tentativas de criar padrões para scripts, nenhum dos quais teve sucesso. O importante é documentar os códigos de erro escolhidos para que os consumidores de seus scripts (por exemplo, outros scripts) saibam o que fazer com base neles.
Pausado até novo aviso.
@DennisWilliamson Se você postar seu comentário como resposta, eu ficaria feliz em votar; Eu já votei em todas as outras respostas, pois achei que cada uma delas era útil. Embora sua resposta tenha conteúdo semelhante ao de David King (e, em menor grau, de Zwol), você declara explicitamente que não há evidências para a afirmação na citação do ABSG.
Anthony G - justice para Monica
1
Obrigado pela oferta, mas acredito que meu comentário deve permanecer como tal.
Pausado até novo aviso.
Desde então, descobri que a especificação POSIX inclui conselhos semelhantes, portanto, adicionei essas informações à minha própria resposta (contendo os resultados de minha pesquisa desde que fiz essa pergunta).
Anthony G - justiça para Monica

Respostas:

10

Houve várias tentativas de padronizar os significados dos códigos de saída do processo. Além do que você mencionou, eu sei:

  • os BSDs possuem o sysexits.hque define significados para valores de 64 em diante.

  • Os grepdocumentos GNU que encerram o código 0 significam que pelo menos uma correspondência foi encontrada, 1 significa que nenhuma correspondência foi encontrada e 2 significa que ocorreu um erro de E / S; obviamente, essa convenção também é útil para outros programas para os quais a diferença entre "nada deu errado, mas eu não encontrei nada" e "ocorreu um erro de E / S" é significativa.

  • Muitas implementações da função de biblioteca C systemusam o código de saída 127 para indicar que o programa não existe ou falhou ao iniciar.

  • No Windows, os NTSTATUScódigos (que estão espalhados de maneira inconveniente por todo o espaço numérico de 32 bits) podem ser usados ​​como códigos de saída, particularmente aqueles que indicam que um processo foi encerrado devido a um mau comportamento catastrófico (por exemplo STATUS_STACK_OVERFLOW).

Você não pode contar com nenhum programa que obedeça a uma dessas convenções em particular. A única regra confiável é que o código de saída 0 seja bem-sucedido e qualquer outra coisa seja algum tipo de falha. (Observe que nãoEXIT_SUCCESS é garantido que o C89 tenha o valor zero; no entanto, é necessário que ele se comporte de maneira idêntica, mesmo que os valores não sejam os mesmos.)exit(0)exit(EXIT_SUCCESS)

zwol
fonte
Obrigado. Foi difícil escolher uma resposta em relação às outras, mas estou aceitando essa, pois ela respondeu minha pergunta e, ao mesmo tempo, oferece uma ampla variedade dos diferentes códigos de saída em uso (com links relevantes): ela merece mais do que os 3 votos positivos atualmente tem.
Anthony G - justice para Monica
11

Nenhum código de saída tem um significado especial, mas o valor em $?pode ter um significado especial.

A maneira como o Bourne Shell e o ksh93 manipularam e encaminharam códigos de saída e situações de erro para a variável do shell $?é o problema. Ao contrário do que você lista, apenas os seguintes valores para $?têm um significado especial:

  • 126 Não foi possível executar o binário mesmo que ele exista
  • 127 O binário especificado não existe
  • O status da saída 128 foi == 0, mas existe algum problema não especificado

Além disso, existe um shell não especificado e um intervalo de $?códigos específico da plataforma > 128 reservado para um programa que foi interrompido por um sinal:

  • O Bourne Shell bash e o ksh88 usam o número de sinal 128 +
  • O ksh93 usa 256 + número de sinal.

Outros valores não apresentam problemas, pois podem ser diferenciados dos $?valores especiais do shell .

Em particular, os valores 1 e 2 não são usados ​​para condições especiais, mas são apenas códigos de saída usados ​​por comandos internos que podem agir da mesma maneira quando não há elementos internos. Parece que o ponteiro para o guia de scripts do bash que você forneceu não é um bom manual, pois apenas lista os códigos usados ​​pelo bash sem comentar se um código específico é um valor especial que deve ser evitado para os próprios scripts.

Versões mais recentes do Bourne Shell usam em waitid()vez de waitpid()aguardar a saída do programa e waitid()(introduzido em 1989 no SVr4) usam uma interface syscall melhor (semelhante ao que o UNOS já usava em 1980).

Como as versões mais recentes do Bourne Shell codificam o motivo da saída em uma variável separada ${.sh.code}/ ${.sh.codename}do código de saída que está em ${.sh.status}/ ${.sh.termsig}, consulte http://schillix.sourceforge.net/man/man1/bosh.1.html , o código de saída não está sobrecarregado com estados especiais e, como resultado do uso de `waitid (), o Bourne Shell agora suporta o retorno de todos os 32 bits do código de saída - não apenas dos baixos 8 bits.

BTW: tenha cuidado para não ser exit(256)semelhante a um programa C ou script de shell, pois isso resulta em $?ser interpretado como 0 em um shell clássico.

esperto
fonte
2
BTW: Fiz um relatório de bug no FreeBSD e no kernel Linux para esse waitid()bug no final de maio. O pessoal do FreeBSD resolveu o problema em 20 horas, o pessoal do Linux não está interessado em corrigir o erro. ... e as pessoas Cygwin diz que eles são bug pelo bug Linux compatível ;-)
Schily
2
Esse comportamento é exigido pela especificação Unix única. Há um valor de 32 bits, sim, mas esse valor contém um campo de bits de 8 bits contendo os baixos 8 bits do valor de _exit. Por favor, vincule o relatório de bug do FreeBSD ao qual você está se referindo, talvez eu esteja entendendo mal o problema que você descreve.
Random832
2
O OP marcou a pergunta com bash e mencionou Bash no texto da pergunta. Bash é um shell derivado de Bourne. Não suporta ${.sh.}variáveis. É verdade, no entanto, que você diz "Bourne" e não "derivado de Bourne" (embora você inclua ksh93).
Pausado até novo aviso.
2
Esta resposta parece ser muito específica para sua variante específica de algum Unix derivado do SVR4. Por favor, seja mais claro sobre o que é portátil e o que não é, tendo em mente que não existe "shell" Bourne, a menos que você queira dizer o que estava na V7.
Zwol 10/11/2015
4
Pelo contrário, acredito que é você quem está subestimando a faixa de variação aqui, especialmente a variação histórica. Você faz parecer que/bin/sh pode confiar para se comportar de forma consistente com esses códigos de saída especiais em várias plataformas, o que não é verdade. (Eu não me importo se um sistema em particular /bin/shpossa ser considerado um "shell Bourne real". Muito mais importante saber que nada disso está no POSIX e que a maioria das coisas que você cita como "sistemas Unix reais" não t fornecer um compatível com POSIX /bin/shde qualquer maneira).
Zwol
6

Para scripts de shell, às vezes eu in-source o equivalente a shell sysexist.hcom códigos de saída reservados à shell (prefixados com S_EX_), que chamei deexit.sh

É basicamente:

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

E pode ser gerado com:

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

Porém, eu não uso muito, mas o que eu uso é uma função shell que inverte códigos de erro em seus formatos de string. Eu o nomeei exit2str. Supondo que você tenha nomeado o exit.shgerador acima exit.sh.sh, o código para exit2strpode ser gerado com ( 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
}"

Eu uso isso no PS1meu shell interativo para que, após cada comando executado, eu possa ver seu status de saída e seu formato de string (se ele tiver um formato de string conhecido):

[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 obtê-los, você precisa de um insourcable para a função exit2str:

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

e use-o no seu ~/.bashrcpara salvar e traduzir o código de saída em cada prompt de comando e exibir seu prompt ( PS1):

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

É bastante útil para observar como alguns programas seguem as convenções de código de saída e outros não, para aprender sobre convenções de código de saída ou apenas para poder ver o que está acontecendo mais rapidamente. Depois de usá-lo há algum tempo, posso dizer que muitos scripts shell orientados ao sistema seguem as convenções. EX_USAGEé particularmente bastante comum, embora outros códigos, não muito. Eu tento seguir as convenções de tempos em tempos, embora sempre exista $S_EX_ANY(1) pessoas preguiçosas (eu sou uma).

PSkocik
fonte
Eu me pergunto se existe algo como um mapeamento entre um código de erro e um código de saída para usar se o erro relatado com esse código de erro resultar em uma saída de erro. Talvez eu precise criar um mapeamento razoável.
PSKocik
1
Uau! Eu não esperava uma resposta tão elaborada. Definitivamente vou tentar isso como uma boa maneira de ver como os diferentes comandos se comportam. Obrigado.
Anthony G - justice para Monica
4

Contanto que você documente seus códigos de saída para lembrá-los daqui a um ano, quando precisar voltar e ajustar o script, tudo ficará bem. A idéia de "códigos de saída reservados" não se aplica mais do que dizer que é habitual usar 0como código de sucesso e qualquer outra coisa como código de falha.

David King
fonte
4

A melhor referência que pude encontrar foi esta: http://tldp.org/LDP/abs/html/exitcodes.html

De acordo com isso:

1 é uma regra geral para erros, e sempre o vi usado para erros definidos pelo usuário.

2 é para uso indevido de shell built ins, como um erro de sintaxe

Para responder sua pergunta diretamente, seu script funcionará bem usando os códigos de erro reservados, ele funcionará como esperado, supondo que você lide com o erro com base no código de erro = 1/2/3.

No entanto, seria confuso se você encontrar alguém que conhece e usa os códigos de erro reservados, o que parece bastante raro.

Outra opção disponível é repetir o erro se houver um e depois sair, assumindo que o script segue a convenção do Linux de "nenhuma notícia é boa notícia" e o eco não é nada de sucesso.

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

Com base nas respostas que recebi (foi difícil escolher uma sobre as outras), não é prejudicial indicar certos tipos de erros usando um código de saída que o Bash também usa. O Bash (ou qualquer outro shell Unix) não fará nada de especial (como executar manipuladores de exceção) se um script de usuário sair com um desses códigos de erro.

Parece que o autor do Advanced Bash-Scripting Guide concorda com as tentativas do BSD de padronizar os códigos de saída ( sysexits.h) e está simplesmente recomendando que, quando os usuários escrevam scripts shell, eles não especifiquem códigos de saída que já entrem em conflito com códigos de saída predefinidos. em uso, ou seja, eles restringem seus códigos de saída personalizados aos 50 códigos de status disponíveis no intervalo de 64 a 113.

Aprecio a idéia (e a justificativa), mas eu preferiria que o autor fosse mais explícito que não é prejudicial ignorar o conselho - exceto nos casos em que o consumidor de um script está verificando erros, como o exemplo citado de 127 ( command not found)

Especificações POSIX relevantes

Pesquisei o que o POSIX tem a dizer sobre códigos de saída e a especificação do POSIX parece concordar com o autor do Advanced Bash-Scripting Guide. Citei as especificações POSIX relevantes (ênfase minha):

Status de saída para comandos

Cada comando tem um status de saída que pode influenciar o comportamento de outros comandos do shell. O status de saída dos comandos que não são utilitários está documentado nesta seção. O status de saída dos utilitários padrão está documentado em suas respectivas seções.

Se um comando não for encontrado, o status de saída será 127. Se o nome do comando for encontrado, mas não for um utilitário executável, o status de saída será 126. Os aplicativos que chamam utilitários sem usar o shell devem usar esses valores de status de saída para relatar erros semelhantes.

Se um comando falhar durante a expansão ou redirecionamento de palavras, seu status de saída será maior que zero.

Internamente, para fins de decidir se um comando sai com um status de saída diferente de zero, o shell deve reconhecer todo o valor do status recuperado para o comando pelo equivalente à macro WEXITSTATUS da função wait () (conforme definido no volume System Interfaces do POSIX.1-2008). Ao relatar o status de saída com o parâmetro especial '?', O shell deve relatar os oito bits completos do status de saída disponíveis. O status de saída de um comando que foi finalizado porque recebeu um sinal deve ser relatado como superior a 128.

O exitutilitário

Conforme explicado em outras seções, determinados valores de status de saída foram reservados para usos especiais e devem ser usados ​​por aplicativos apenas para esses fins:

  • 126 - Um arquivo a ser executado foi encontrado, mas não era um utilitário executável.
  • 127 - Um utilitário a ser executado não foi encontrado.
  • >128 - Um comando foi interrompido por um sinal.

Outras informações

Pelo que vale, pude verificar tudo, exceto um da lista de códigos de saída com significados especiais . Esta tabela de códigos de saída é útil, pois fornece mais detalhes - e exemplos de como gerar os códigos de erro documentados na referência do bash .

Tentativa de gerar o status de saída 128

Usando as versões 3.2.25 e 4.2.46 do Bash, tentei gerar um 128 Invalid argument to exiterro, mas sempre que recebia um 255 (status de saída fora do intervalo). Por exemplo, se exit 3.14159for executado como parte de um script de shell ou em um shell filho interativo, o shell será encerrado com um código de 255:

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

Para ainda mais diversão, eu também tentei executar um programa C simples, mas neste caso, parece que a exit(3)função simplesmente converteu o float em int (3 neste caso) antes de sair:

#include <stdlib.h>
main()
{
    exit(3.14159);
}
Anthony G - justiça para Monica
fonte