Uma linha de código indispensável no seu shell script
TL;DR: coloque set -euo pipefail
no início do seu script e evite efeitos colaterais indesejados como a leitura de variáveis indefinidas, a não interrupção do fluxo de execução em erros e o retorno do exit code apenas do último comando dentro do pipeline.
Introdução
Shell Script é uma linguagem que adoro devido a seu fácil acesso e integração com as ferramentas e programas do sistema. No entanto, é necessário ficar atento a um detalhe importante para evitar efeitos colaterais indesejados em seus programas: diferentemente de muitas linguagens, por padrão, o Shell Script não interrompe a execução caso receba um exit code diferente de zero (erro) e também não interpreta variáveis indefinidas como erros. Por isso hoje quero lhe apresentar uma opção para ajudar a evitar problemas como esses.
O comando set
O set
é um comando built-in do shell presente em sistemas GNU/Linux que configura e mostra os nomes e valores das variáveis de ambiente do shell.
A sintaxe para usa-lo é bem simples:
set [opções] [argumentos]
Existem muitas opções dísponiveis, dentre as quais você pode verificar aqui, mas hoje abordaremos somente as opções -e
, -u
e -o pipefail
, que já são suficientes para o nosso propósito.
A opção -u
Antes de tudo, crie um arquivo chamado testando_set.sh, copie o código abaixo e dê permissão de execução através do chmod +x testando_set.sh
#!/bin/bash
parametro=$1
echo "O parâmetro passado foi: $parametro"
echo "Fim do programa"
Agora tente executa-lo no terminal assim:
./testando_set.sh
Percebeu o problema? No terminal nós não passamos o parâmetro requisitado pela variável da linha 3 e até mesmo quando tentamos imprimir essa variável na linha 4 nenhum erro foi reportado ou programa foi interrompido. Para evitar esse tipo de problema, podemos usar o set -u
, que tratará o uso de toda variável não definida como um erro e automaticamente interromperá a execução.
ℹ️ | Para deixar um pouco mais claro o que o comando pretende fazer, também é possível utilizar o set -o nounset ao invés do set -u . |
---|
Execute o código abaixo e note que o fluxo do programa será interrompido dessa vez:
#!/bin/bash
set -u
parametro=$1
echo "O parâmetro passado foi: $parametro"
echo "Fim do programa"
A opção -e
Essa opção diz ao interpretador shell que ele deve interromper o fluxo se algum comando retornar um valor diferente de zero como status. Isso ajuda a detectar problemas antes que se tornem mais complexos.
ℹ️ | Para deixar um pouco mais claro o que o comando pretende fazer, também é possível utilizar o set -o errexit ao invés do set -e . |
---|
Faça o teste executando o script abaixo:
#!/bin/bash
set -e
ls "Cristóvão_Colombo.txt" # Arquivo que não existe
echo "Fim do programa" # Não será executado!
A opção -o pipefail
Por padrão, dentro de um pipeline o shell retorna o exit code do último comando, ou seja, ele poderá retornar o exit code 0 (sucesso), mesmo que todos os outros comandos dentro do pipeline retornem um exit code de erro.
Para ilustrar melhor essa situação, tente rodar o seguinte script no seu terminal:
#!/bin/sh
# Tenta imprimir o conteúdo do arquivo "Napoleão.txt" que não existe
cat Napoleão.txt
# Verifica o exit code do comando anterior
echo "exit code: $?"
# Tenta imprimir o conteúdo do arquivo "Napoleão.txt" que não existe e substituir a string "perdeu" por "ganhou" do conteúdo redirecionado pelo pipe
cat Napoleão.txt | sed 's/perdeu/ganhou/'
# Verifica o exit code do comando anterior
echo "exit code: $?"
Perceba que o primeiro exit code retornado é 1 (Operação não permitida), porém o segundo é 0 (Sucesso), mesmo o arquivo Napoleão.txt não existindo, pois o segundo comando, que possui um exit code de 1, é quem definirá o exit code do pipeline por ser o último. Para evitar esse comportamento e garantir que o pipeline retorne um exit code de sucesso apenas quando todos os comando tiverem sucesso, podemos usar o set -o pipefail
.
Execute o código abaixo e perceba que agora ambos os exit codes retornados são 1, ou seja, erro.
#!/bin/sh
set -o pipefail
# Tenta imprimir o conteúdo do arquivo "Napoleão.txt" que não existe
cat Napoleão.txt
# Verifica o status code do comando anterior
echo "status code: $?"
# Tenta imprimir o conteúdo do arquivo "Napoleão.txt" que não existe e substituir a string "perdeu" por "ganhou"
cat Napoleão.txt | sed 's/perdeu/ganhou/'
# Verifica o status code do comando anterior
echo "status code: $?"
Bônus
Caso você queira fazer o contrário e garantir que determinada variável de ambiente seja desligada, você pode utilizar o sinal de +
ao invés do -
.
#!/bin/sh
# Ativa a checagem de erro
set -e
ls / # Lista o diretório /
echo -e "Essa mensagem será impressa, pois não possui nenhum erro até aqui\n"
# Desativa a checagem de erro
set +e
ls "Vasco_da_Gama.txt" # Esse arquivo não existe!
echo -e "Essa mensagem será impressa mesmo com o erro da linha anterior\n"
# Ativa a checagem de erro novamente
set -e
ls "Vasco_da_Gama.txt" # Esse arquivo não existe!
echo "Essa mensagem NÃO será impressa, pois existe um erro na linha anterior"
Conclusão
Pronto, agora é só juntar tudo assim set -euo pipefail
e colocar no topo do seu código que já vai conseguir evitar muitos problemas bobos que poderiam se tornar complexos com o tempo.
Espero que tenha te ajudado e até a próxima! :)