Usando códigos “reservados” para o status de saída dos scripts de shell

14

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

According to the above table, exit codes 1-2, 126-165, and 255 have special meanings, and should therefore be avoided for user-specified exit parameters.

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

  • 0 - sucesso
  • 1 - nome do 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, por isso comecei em 1 para a primeira condição de erro e aumentei o status de saída para cada tipo de erro sucessivo.

Eu escrevi o script com a intenção de que em um estágio posterior ele pudesse ser chamado por outros scripts (que poderiam verificar os códigos de saída diferentes de zero). Eu ainda não fiz isso; Até agora eu só executei o script do meu shell interativo (Bash) e fiquei me perguntando o que / se 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?

Eu não encontrei nenhum conselho de confirmação na documentação do Bash; sua seção em Exit Status simplesmente lista os códigos de saída usados por Bash, mas não declara que qualquer um deles é reservado ou avisa para usá-los em seus próprios scripts / programas.

    
por Anthony Geoghegan 10.11.2015 / 14:22

6 respostas

9

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

  • os BSDs têm sysexits.h que define significados para valores de 64 em diante.

  • GNU grep documentos cujo código de saída 0 significa 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; esta convenção é obviamente também útil para outros programas para os quais a distinção entre "nada deu errado mas não encontrei nada" e "ocorreu um erro de I / O" é significativa.

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

  • No Windows, os códigos NTSTATUS (que estão espalhados de forma inconveniente por todo o lado o espaço numérico de 32 bits) pode ser usado 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 obedecendo a nenhuma dessas convenções. A única regra confiável é que o código de saída 0 é bem-sucedido e qualquer outra coisa é um tipo de falha. (Observe que o EXIT_SUCCESS do C89 não é garantido para ter o valor zero; no entanto, exit(0) é necessário para se comportar de forma idêntica a exit(EXIT_SUCCESS) , mesmo que os valores não sejam iguais.)

    
por 10.11.2015 / 20:35
10

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 de 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 exista
  • 127 O binário especificado não existe
  • 128 status de saída foi == 0, mas existe algum problema não especificado

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

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

Outros valores não apresentam problemas, pois podem ser distinguidos 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 forma quando não são internos. Então parece que o ponteiro para o guia de script bash que você forneceu não é um bom manual, pois apenas lista códigos usados pelo bash sem comentar se um código específico é um valor especial que deve ser evitado para scripts próprios .

As versões mais recentes do Bourne Shell usam waitid() em vez de waitpid() para aguardar a saída do programa e waitid() (introduzido 1989 para SVr4) usa uma interface syscall melhor (semelhante ao que o UNOS usou em 1980) .

Como versões mais recentes do Bourne Shell codificam a razão de saída em uma variável separada ${.sh.code} / ${.sh.codename} do que o código de saída que está em ${.sh.status} / ${.sh.termsig} , consulte link , o código de saída não está sobrecarregado com estados especiais e, como resultado Usando o 'waitid (), o Bourne Shell agora suporta retornar todos os 32 bits do código de saída - não apenas os 8 bits baixos.

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

    
por 10.11.2015 / 14:35
6

Para o shell de script, às vezes eu corrijo o equivalente em shell de sysexist.h com os códigos de saída reservados pela shell (prefixados com S_EX_ ), que eu nomeei como exit.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*)/=/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; }'

Eu não uso muito, porém, mas o que eu uso é uma função de shell que inverte os códigos de erro para seus formatos de string. Eu chamei de exit2str . Supondo que você tenha nomeado o exit.sh generator exit.sh.sh acima, o código para exit2str pode ser gerado com ( exit2str.sh.sh ):

#!/bin/sh
echo '
exit2str(){
  case "$1" in'
./exit.sh.sh | sed -nEe's|^(S_)?EX_(([^_=]+_?)+)=([0-9]+).*|) echo "";;|p'
echo "
  esac
}"

Eu uso isso no PS1 do meu shell interativo para que, após cada comando que eu executar, 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 ~/.bashrc para 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)\$'
    # ...                                                                                   

É muito útil 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 ver o que está acontecendo mais prontamente. Tendo sido usado por algum tempo, posso dizer que muitos shell scripts orientados a sistemas seguem as convenções. EX_USAGE é particularmente bastante comum, apesar de outros códigos, não muito. Eu tento seguir as convenções de tempos em tempos, embora sempre haja $S_EX_ANY (1) para pessoas preguiçosas (eu sou uma).

    
por 11.11.2015 / 16:03
4

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

    
por 10.11.2015 / 14:27
4

A melhor referência que pude encontrar foi: link

De acordo com isto:

1 é um código genérico para erros, e eu sempre o vi usado para erros definidos pelo usuário.

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

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

No entanto, possivelmente seria confuso se você encontrar alguém que conheça e use os códigos de erro reservados, o que parece bastante raro.

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

if [ $? -ne 0 ];then
    echo "Error type"
    exit 1
fi
    
por 10.11.2015 / 14:34
1

Com base nas respostas que recebi (foi difícil escolher uma sobre as outras), não é prejudicial indicar determinados tipos de erros usando um código de saída que o Bash também usa . Bash (ou qualquer outro shell Unix) não fará nada de especial (como a execução de 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 escrevem scripts shell, eles não • especificar códigos de saída que entrem em conflito com códigos de saída predefinidos já em uso, isto é, eles restringem seus códigos de saída personalizados aos 50 códigos de status disponíveis no intervalo 64-113.

Eu aprecio a idéia (e a lógica), mas preferia que o autor fosse mais explícito que não é prejudicial ignorar o conselho - além de casos em que o consumidor de um script está verificando erros como os citados exemplo de 127 ( command not found ).

Especificações POSIX relevantes

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

Sair do status dos comandos

Each command has an exit status that can influence the behavior of other shell commands. The exit status of commands that are not utilities is documented in this section. The exit status of the standard utilities is documented in their respective sections.

If a command is not found, the exit status shall be 127. If the command name is found, but it is not an executable utility, the exit status shall be 126. Applications that invoke utilities without using the shell should use these exit status values to report similar errors.

If a command fails during word expansion or redirection, its exit status shall be greater than zero.

Internally, for purposes of deciding whether a command exits with a non-zero exit status, the shell shall recognize the entire status value retrieved for the command by the equivalent of the wait() function WEXITSTATUS macro (as defined in the System Interfaces volume of POSIX.1-2008). When reporting the exit status with the special parameter '?', the shell shall report the full eight bits of exit status available. The exit status of a command that terminated because it received a signal shall be reported as greater than 128.

O utilitário exit

As explained in other sections, certain exit status values have been reserved for special uses and should be used by applications only for those purposes:

  • 126 – A file to be executed was found, but it was not an executable utility.
  • 127 – A utility to be executed was not found.
  • >128 – A command was interrupted by a signal.

Mais informações

Por que vale a pena, eu consegui verificar todos, exceto um, da lista de Códigos de saída com especial Significados . Esta tabela de códigos de saída é útil, pois fornece mais detalhes - e exemplos de como gerar os códigos de erro documentados no Referência de bash .

Tentativa de gerar status de saída de 128

Usando as versões 3.2.25 e 4.2.46 do Bash, tentei lançar um erro 128 Invalid argument to exit , mas cada vez que recebi um 255 (status de saída fora do intervalo). Por exemplo, se exit 3.14159 for executado como parte de um script shell ou em um shell filho interativo, o shell sairá com um código 255 :

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

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

#include <stdlib.h>
main()
{
    exit(3.14159);
}
    
por 11.11.2015 / 13:31