Jornada Shell Script: Parte 4 Exit Code STDOut e STDErr
Tema Central: Interpretação do Exit Code, STDOut e STDError em um comando Linux para utilização em Shell Script
Versão 1
Emissão: 22 de julho de 2022
Validade: Indeterminada
Objetivo
Hoje, temos como objetivo neste artigo capacitar à quem for de interesse do entendimento e interpretação da saída de códigos de status e erros em um comando Linux. Esse tema é de fundamental importância para seguimento dessa jornada. Essa importância se dá uma vez que dará ao administrador Linux condições de fazer o tratamento devido de condições que possam ocorrer em uma rotina montada em Shell Script.
Construção sequencial das ideias
Na sequência dos artigos demonstraremos na prática que, para cada saída de status de comandos, podemos tomar ações diferentes. Hoje, iremos abordar apenas a interpretação e entendimento dessas saídas de status dos comandos
Pré-Requisitos
Antes de iniciar este artigo, é muito importante que tenhamos alinhados os conhecimentos apresentados nos artigos anteriores da Jornada Shell Script. Você poderá não compreender muitas das informações que apresentarei aqui se não tiver essa base inicial.
Na primeira parte da Jornada Shell Script apresentamos muitos comandos, alguns deles serão nesse ínterim utilizados hoje com alguns dos parâmetros apresentados anteriormente. Então, caso tenha alguma dúvida com relação aos comandos apresentados e parâmetros utilizados, recomendo que retorno para o artigo citado.
Já na parte 2 da Jornada Shell Script vimos sobre variáveis de ambiente, file globbing e Expressões regulares. Sendo que esse terceiro será citado com uma sintaxe mais complexa de uso neste artigo. Também como referência para este artigo, a parte 2 traz alguns dos comandos do editor de textos vi. Sendo que estes serão explorados na prática no artigo de hoje.
Do mesmo modo que os artigos passados, vamos precisar de uma máquina Linux. Para prática dos conceitos expostos neste artigo, seguiremos usando o Debian como distribuição base.
A estrutura de arquivos e diretórios utilizada na parte 3 desta Jornada servirá como base para execução dos comandos de exemplos que usaremos ao longo deste artigo.
Visão geral do Exit Code no Linux
Normalmente quando falamos de da execução de um comando com sucesso, temos como saída de Status o código ZERO. Por outro lado, qualquer comportamento diferente de sucesso, o Linux interpretará com uma saída de código DIFERENTE DE ZERO. Nesse caso o código de gerado, ou exit code, dependerá de como o desenvolvedor do programa definiu seus parâmetros de saída. Essa informação se está presente normalmente no manual do comando conforme veremos a seguir.
O valor do return code no Linux pode variar entre 0 e 255, sendo retornado 0 (zero) quando a saída do comando vai para o stdout, e um valor diferente de zero quando a saída do comando retorna alguma informação para o stderr. Lembrando que estamos falando aqui dos conceitos de redirecionamento de entrada e saída (stdout e stderr) vistos na parte 1 desta Jornada.
Exit Status, na prática
No Linux funciona assim, você executa um comando no shell e espera sempre um retorno de saída e outro retorno de erro. Para verificar o “exit code”, ou “exit status” gerado basta verificar o valor armazenado na variável “$?”
myuser@DEVOPS:~$ ls ~/RunbookBR/Scripts/ MeuPrimeiroScript.sh myuser@DEVOPS:~$ echo $? 0 myuser@DEVOPS:~$ ls /tmp/DIRETORIO_INEXISTENTE ls: cannot access '/tmp/DIRETORIO_INEXISTENTE': No such file or directory myuser@DEVOPS:~$ echo $? 2
Perceba no exemplo a seguir que usamos o comando ls em duas situações, onde na primeira listamos os arquivos em um diretório existente, logo o “exit code” gerado foi armazenado na variável $? como zero.
Por outro lado, na segunda situação tentamos listar os arquivos em um diretório que não existe. Dese modo o “exit status” retornou o valor 2, armazenando do mesmo modo na variável “$?”.
Em suma, a variável “$?” um valor de “exit code” a cada comando executado.
Exit code do comando ls
Veremos agora no manual do ls porque esses valores gerados de zero e dois, nos casos de sucesso no ls e insucesso pelo fato do diretório não existir.
myuser@DEVOPS:~$ man ls
Agora dentro do manual do ls, vamos procurar a informação do nosso Exit Status (cada manual pode chamar o “exit code” de uma forma, fique atente a isso).
Lembrando que para fazer a busca no man, usamos a mesma sintaxe que vimos na parte 2 da Jornada quando falamos do editor de textos vi
LS(1) User Commands LS(1) NAME ls - list directory contents SYNOPSIS ls [OPTION]... [FILE]... DESCRIPTION List information about the FILEs (the current directory by default). Sort entries alphabetically if none of -cftuvSUX nor --sort is specified. Mandatory arguments to long options are mandatory for short options too. -a, --all do not ignore entries starting with . -A, --almost-all /Exit status
Você será direcionado para essa parte do manual, perceba que o manual do man define valor 0 para ok, 1 para problemas menores e 2 para problemas que ele considera mais sérios.
Exit status: 0 if OK, 1 if minor problems (e.g., cannot access subdirectory), 2 if serious trouble (e.g., cannot access command-line argument).
Exit code do comando grep
Outro exemplo que vou citar apenas para reforçar o entendimento deste tema, tão importante para tratamento dos nossos scripts em Shell, será na utilização do comando grep.
Perceba no exemplo abaixo como o manual do grep nos passa orientações um pouco mais específicas e perceba também como essa informação pode ajudar no tratamento de cada comportamento esperado pelo stderr.
myuser@DEVOPS:~$ man grep
Do mesmo modo, procure pela expressão EXIT STATUS, observe que no caso do manual do grep a expressão aparece toda em caixa alta ou todo em caixa baixa, dependendo da linha lida.
GREP(1) User Commands GREP(1) NAME grep, egrep, fgrep, rgrep - print lines that match patterns SYNOPSIS grep [OPTION...] PATTERNS [FILE...] grep [OPTION...] -e PATTERNS ... [FILE...] grep [OPTION...] -f PATTERN_FILE ... [FILE...] DESCRIPTION grep searches for PATTERNS in each FILE. PATTERNS is one or more patterns separated by newline characters, and grep prints each line that matches a pattern. Typically PATTERNS should be quoted when grep is used in a shell command. A FILE of "-" stands for standard input. If no FILE is given, recursive searches examine the working directory, and nonrecursive searches read standard input. /EXIT STATUS
Veja que nesse comando ele define zero como saída de sucesso. Desse modo, esse “sucesso” vai depender de como você escreverá seu comando grep. Você pode querer encontrar uma expressão específica. Ou pode querer encontrar toda linha que não possua uma expressão específica. Deixarei alguns exemplos abaixo, mas não deixe de ver nosso artigo 1 para maiores referências dos modos de uso do grep.
Ainda sobre o que versa o manual do grep, ele definirá como “exit code” 1, caso ocorra uma NÃO ocorrência dos critérios buscados no seu comando grep.
Já o “exit status” 2 será definido quando um erro ocorrer no comando, por exemplo, erro de sintaxe.
EXIT STATUS Normally the exit status is 0 if a line is selected, 1 if no lines were selected, and 2 if an error occurred. However, if the -q or --quiet or --silent is used and a line is selected, the exit status is 0 even if an error occurred.
Exemplos de stdout e stderr para o comando grep
Vamos acessar nosso diretório de scripts criado no artigo 3 dessa jornada:
myuser@DEVOPS:~$ cd RunbookBR/Scripts/
grep com Exit code 0
Agora vamos simular uma ocorrência de sucesso para o comando grep a fim de que seja gerado o “exit code” zero:
myuser@DEVOPS:~/RunbookBR/Scripts$ grep OUT MeuPrimeiroScript.sh MSGM_OUTPUT="MEU PRIMEIRO PROGRAMA SHELL" $MSGM_OUTPUT myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 0
Até aqui, sem mistério, procuramos a expressão OUT e achamos. Logo tivemos o retorno do código zero na variável “$?”.
Agora, lembra que temos o comando “-v” tratado como “exceto” no comando grep?
Vamos ver o comportamento da variável stderr quando usamos esse parâmetro.
myuser@DEVOPS:~/RunbookBR/Scripts$ grep -v "#" MeuPrimeiroScript.sh MSGM_OUTPUT="MEU PRIMEIRO PROGRAMA SHELL" echo " Ola DevOps! Bem vindo ao: $MSGM_OUTPUT " myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 0
Perceba que não temos diferença na interpretação de sucesso do comando, isso porque o grep fez exatamente o que você pediu, mostrou com sucesso todos os resultados que NÃO continham o carácter “#” do nosso exemplo.
grep com Exit code 1
Agora, vamos testar o comando grep demonstrando que ele irá gerar o código 1 quando ele NÃO obtiver sucesso no seu critério de filtro. Do mesmo modo que os testes anteriores, vamos testar com e sem o parâmetro “-v”, o que eu percebo gerando mais confusão na cabeça do administrador Linux menos experiente.
myuser@DEVOPS:~/RunbookBR/Scripts$ grep OUTA MeuPrimeiroScript.sh myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 1
No exemplo acima o código 1 foi gerado porque estamos buscamos uma expressão que não existe dentro do arquivo MeuPrimeiroScript.sh
Agora vamos alterar apenas o parâmetro -v
myuser@DEVOPS:~/RunbookBR/Scripts$ grep -v OUTA MeuPrimeiroScript.sh #!/bin/bash ###################################################### # Programa: MeuPrimeiroScript.sh # Desenvolvido por: RunbookBR ([email protected]) # Data de Criacao: 08/09/2020 # Versao: 1.0.0 # Comentario: Versão inicial do Primeiro Programa criado para treinamento de Shell Script ######## # Edicao # Editado por: RunbookBR ([email protected]) # Data de Criacao: 08/10/2020 # Versao: 1.0.1 # Comentario: Corrigida ortografia no texto de saida para o usuario ###################################################### # VARIAVEIS DO PROGRAMA DATA=$(date +%d/%m/%Y) #Data no formato DIA/MES/ANO MSGM_OUTPUT="MEU PRIMEIRO PROGRAMA SHELL" # INICIO DO PROGRAMA clear #Limpa a tela do bash echo " ################################# Ola DevOps! Bem vindo ao: $MSGM_OUTPUT ################################# " myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 0
Perceba que o “exit code” mudou para zero. Isso ocorre porque o shell entender que achou o que você queria, ou seja, toda linha que NÃO possui a expressão OUTA.
Apenas para termos um exemplo do código de saída 1 para uma NÃO ocorrência usando o “grep -v”, vou fazer o uso de expressão regular e o uso do grep estendido. Trata-se de uma sintaxe um pouco mais complexa que exploramos nos artigos anteriores, mas o que preciso que você perceba aqui é simples, o grep retornará 1 porque não retornou resultados para o filtro gerado com o comando grep
myuser@DEVOPS:~/RunbookBR/Scripts$ egrep -v "[A-z]|^$|#|\"" MeuPrimeiroScript.sh myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 1
grep com Exit code 2
Agora vamos exemplificar o comando grep com uma saída de status igual a 2.
myuser@DEVOPS:~/RunbookBR/Scripts$ grep -k A MeuPrimeiroScript.sh grep: invalid option -- 'k' Usage: grep [OPTION]... PATTERNS [FILE]... Try 'grep --help' for more information. myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 2
Perceba no exemplo acima que temos um “exit code” 2, nesse caso porque houve um problema de sintaxe.
myuser@DEVOPS:~/RunbookBR/Scripts$ grep VAR INEXISTENTEScript.sh grep: INEXISTENTEScript.sh: No such file or directory myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 2
Do mesmo modo, temos nesse último exemplo um “exit status” igual a 2 porque o arquivo onde queremos fazer o grep simplesmente não existe.
Definindo nosso Exit Code no Shell Script
Basicamente, quando queremos informar um código de saída para nosso programa em Shell Script devemos executar o comando exit dentro do nosso script shell. Apenas o comando exit, guardará o último “exit status” apresentado no programa, no entanto, caso deseje informar seu próprio código de status você também poderá explicitar um valor conforme veremos nos exemplos a seguir:
Lembra que no artigo 3 criamos nosso primeiro script? Vamos mexer nele para simular e exemplificar as situações expostas acima.
No nosso servidor Debian, vamos acessar nosso diretório de trabalho e verificar se nosso script está criado com as devidas permissões (caso contrário siga os passos referenciados no artigo 3 desta Jornada):
myuser@DEVOPS:~/RunbookBR/Scripts$ cd ~/RunbookBR/Scripts/ myuser@DEVOPS:~/RunbookBR/Scripts$ ls -l total 4 -rwxrwxr-x 1 myuser myuser 818 Nov 4 12:49 MeuPrimeiroScript.sh
Vamos testar o script, apenas para garantir que está tudo conforme executado no artigo anterior:
myuser@DEVOPS:~/RunbookBR/Scripts$ ./MeuPrimeiroScript.sh ################################# Ola DevOps! Bem vindo ao: MEU PRIMEIRO PROGRAMA SHELL ################################# myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 0
Perceba que nosso “exit status” retornou 0 (zero) ou seja, tudo certo.
Agora vamos simular um problema no script, colocando adicionando um ls para uma pasta inexistente após a exibição da mensagem. Vamos adicionar também o comando exit para sair do programa com o código 100 e outro echo no final do script.
Em um script “real”, estamos simulando uma condição em que um problema na execução ira gerar um código especifico, saindo do programa sem executar as linhas que seguiriam caso não houvesse um erro. Perceba que não vou adicionar comandos condicionais ainda, estamos focados aqui apenas no “exit code”.
Adicione as linhas ls, exit e echo e saia do editor vi com o “:wq” conforme abaixo:
myuser@DEVOPS:~/RunbookBR/Scripts$ vi ./MeuPrimeiroScript.sh # INICIO DO PROGRAMA clear #Limpa a tela do bash echo " ################################# Ola DevOps! Bem vindo ao: $MSGM_OUTPUT ################################# " ls /tmp/INEXISTENTE exit 100 echo "NOVA MENSAGEM NAO SERA EXIBIBA" :wq
Veja no resultado da execução do programa agora que a primeira mensagem foi exibida, logo após, ele executa o ls que apresenta um erro mas ao invés de retornar o valor 2 descrito no manual do ls e visto anteriormente, o programa retornou o status level 100, além de não ter executado a ultima linha do programa.
myuser@DEVOPS:~/RunbookBR/Scripts$ ./MeuPrimeiroScript.sh ################################# Ola DevOps! Bem vindo ao: MEU PRIMEIRO PROGRAMA SHELL ################################# ls: cannot access '/tmp/INEXISTENTE': No such file or directory myuser@DEVOPS:~/RunbookBR/Scripts$ echo $? 100
Conclusão
Enfim, aprendemos no artigo de hoje como buscar e interpretar saídas de comandos, mesmo em caso de sucesso ou erro. Aprendemos que cada comando trará suas particularidades de uso, mas basicamente todo comando traz como saída de sucesso o código de erro zero. Vimos ainda que particularidade dos códigos usados em cada comando poderão ser explorados de maneira diferente a depender do que o manual do comando nos passa de informação.
Assim, o este artigo se mostra de extrema importância para os próximos artigos, quando veremos script que abordarão entendimentos e práticas que condicionaram sua execução ou comportamento dependendo do “status code” trazido em cada comando executado no nosso shell script.
Por hoje encerramos, espero sinceramente que você esteja gostando e aproveitando esse material. Estamos montando cada passo com bastante cuidado e atenção para que você, como falamos em outras ocasiões, se torne de fato um especialista Linux como enfase em shell script se diferenciando no mercado de trabalho.
Um forte abraço e até a próxima.