Como usar set e pipefail em scripts Bash no Linux

Publicados: 2022-06-25
Terminal Linux em uma tela de laptop sobre um fundo azul.
fatmawati achmad zaenuri/Shutterstock.com

Os comandos set e pipefail do Linux ditam o que acontece quando ocorre uma falha em um script Bash. Há mais em que pensar do que deveria parar ou continuar.

RELACIONADO: O Guia do Iniciante para Shell Scripting: O Básico

Scripts Bash e Condições de Erro

Os scripts de shell Bash são ótimos. Eles são rápidos para escrever e não precisam ser compilados. Qualquer ação repetitiva ou de vários estágios que você precise executar pode ser agrupada em um script conveniente. E como os scripts podem chamar qualquer um dos utilitários padrão do Linux, você não está limitado aos recursos da própria linguagem shell.

Mas podem surgir problemas quando você chama um utilitário ou programa externo. Se falhar, o utilitário externo será fechado e enviará um código de retorno ao shell, podendo até imprimir uma mensagem de erro no terminal. Mas seu script continuará sendo processado. Talvez não seja isso que você queria. Se ocorrer um erro no início da execução do script, isso poderá levar a problemas piores se o restante do script puder ser executado.

O que é o Bash Shell e por que é tão importante para o Linux?
RELACIONADO O que é o Bash Shell e por que é tão importante para o Linux?

Você pode verificar o código de retorno de cada processo externo à medida que eles são concluídos, mas isso se torna difícil quando os processos são canalizados para outros processos. O código de retorno será do processo no final do pipe, não daquele no meio que falhou. É claro que erros também podem ocorrer dentro do seu script, como tentar acessar uma variável não inicializada.

Os comandos set e pipefile permitem que você decida o que acontece quando ocorrem erros como esses. Eles também permitem detectar erros mesmo quando ocorrem no meio de uma cadeia de tubulação.

Veja como usá-los.

Demonstrando o problema

Aqui está um script Bash trivial. Ele ecoa duas linhas de texto para o terminal. Você pode executar este script se copiar o texto em um editor e salvá-lo como “script-1.sh”.

 #!/bin/bash

echo Isso vai acontecer primeiro 
echo Isso acontecerá em segundo lugar

Para torná-lo executável, você precisará usar chmod :

 chmod +x script-1.sh

Você precisará executar esse comando em cada script se quiser executá-los em seu computador. Vamos executar o script:

 ./script-1.sh 

Executando um script simples sem erros.

As duas linhas de texto são enviadas para a janela do terminal conforme o esperado.

Vamos modificar um pouco o script. Pediremos a ls para listar os detalhes de um arquivo que não existe. Isso falhará. Nós salvamos isso como “script-2.sh” e o tornamos executável.

 #!/bin/bash

echo Isso vai acontecer primeiro
ls nome-de-arquivo-imaginário
echo Isso acontecerá em segundo lugar

Quando executamos este script, vemos a mensagem de erro de ls .

 ./script-2.sh 

Executando um script e gerando uma condição de falha.

Embora o comando ls tenha falhado, o script continuou a ser executado. E mesmo que tenha ocorrido um erro durante a execução do script, o código de retorno do script para o shell é zero, o que indica sucesso. Podemos verificar isso usando echo e o $? variável que contém o último código de retorno enviado ao shell.

 eco $? 

Verificando o código de retorno do último script executado.

O zero que é relatado é o código de retorno do segundo eco no script. Portanto, há dois problemas com esse cenário. A primeira é que o script teve um erro, mas continuou em execução. Isso pode levar a outros problemas se o resto do script esperar ou depender da ação que falhou realmente foi bem-sucedida. E a segunda é que, se outro script ou processo precisar verificar o sucesso ou a falha desse script, ele obterá uma leitura falsa.

A opção set -e

A opção set -e (exit) faz com que um script saia se algum dos processos que ele chama gerar um código de retorno diferente de zero. Qualquer coisa diferente de zero é considerada uma falha.

Adicionando a opção set -e ao início do script, podemos alterar seu comportamento. Este é "script-3.sh."

 #!/bin/bash 
set -e

echo Isso vai acontecer primeiro
ls nome-de-arquivo-imaginário
echo Isso acontecerá em segundo lugar

Se executarmos este script, veremos o efeito de set -e .

 ./script-3.sh
 eco $? 

Finalizando um script em uma condição de erro e configurando corretamente o código de retorno.

O script é interrompido e o código de retorno enviado ao shell é um valor diferente de zero.

Lidando com falhas em tubulações

A tubulação adiciona mais complexidade ao problema. O código de retorno que sai de uma sequência canalizada de comandos é o código de retorno do último comando na cadeia. Se houver uma falha com um comando no meio da cadeia, voltamos à estaca zero. Esse código de retorno é perdido e o script continuará o processamento.

Podemos ver os efeitos dos comandos de tubulação com diferentes códigos de retorno usando os embutidos true e false do shell. Esses dois comandos não fazem mais do que gerar um código de retorno de zero ou um, respectivamente.

 verdadeiro
 eco $?
 falso
 eco $? 

Os comandos internos true e false do shell bash.

Se canalizarmos false para true — com false representando um processo com falha — obteremos o código de retorno de true igual a zero.

 falso | verdadeiro
 eco $? 

Conduzindo falso em verdadeiro.

O Bash tem uma variável de matriz chamada PIPESTATUS , e isso captura todos os códigos de retorno de cada programa na cadeia de tubos.

 falso | verdadeiro | falso | verdadeiro
 echo "${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]} ${PIPESTATUS[3]}" 

Usando PIPESTATUS para ver o código de retorno de todos os programas em uma cadeia de pipe.

PIPESTATUS apenas mantém os códigos de retorno até que o próximo programa seja executado, e tentar determinar qual código de retorno combina com qual programa pode ficar muito confuso muito rapidamente.

É aqui que entram set -o (opções) e pipefail . Este é “script-4.sh”. Isso tentará canalizar o conteúdo de um arquivo que não existe para wc .

 #!/bin/bash 
set -e

echo Isso vai acontecer primeiro
cat script-99.sh | wc -l
echo Isso acontecerá em segundo lugar

Isso falha, como seria de esperar.

 ./script-4.sh
 eco $? 

Executando um script com um erro em uma cadeia de pipe.

O primeiro zero é a saída de wc , nos dizendo que não leu nenhuma linha para o arquivo ausente. O segundo zero é o código de retorno do segundo comando echo .

Vamos adicionar o -o pipefail , salvá-lo como “script-5.sh” e torná-lo executável.

 #!/bin/bash 
set -eo pipefail

echo Isso vai acontecer primeiro
cat script-99.sh | wc -l
echo Isso acontecerá em segundo lugar

Vamos executar isso e verificar o código de retorno.

 ./script-5.sh
 eco $? 

Executar um script que intercepta erros em cadeias de pipe e define corretamente o código de retorno.

O script é interrompido e o segundo comando echo não é executado. O código de retorno enviado ao shell é um, indicando corretamente uma falha.

RELACIONADO: Como usar o comando Echo no Linux

Capturando variáveis ​​não inicializadas

Variáveis ​​não inicializadas podem ser difíceis de identificar em um script do mundo real. Se tentarmos echo o valor de uma variável não inicializada, echo simplesmente imprime uma linha em branco. Não gera uma mensagem de erro. O resto do script continuará a ser executado.

Este é o script-6.sh.

 #!/bin/bash 
set -eo pipefail

echo "$notset" 
echo "Outro comando de eco"

Vamos executá-lo e observar seu comportamento.

 ./script-6.sh
 eco $? 

Executando um script que não captura variáveis ​​não inicializadas.

O script passa pela variável não inicializada e continua a ser executado. O código de retorno é zero. Tentar encontrar um erro como esse em um script muito longo e complicado pode ser muito difícil.

Como trabalhar com variáveis ​​no Bash
RELACIONADO Como trabalhar com variáveis ​​no Bash

Podemos capturar esse tipo de erro usando a opção set -u (unset). Vamos adicionar isso à nossa coleção crescente de opções definidas no topo do script, salvá-lo como “script-7.sh” e torná-lo executável.

 #!/bin/bash 

set -eou pipefail

echo "$notset" 

echo "Outro comando de eco"

Vamos executar o script:

 ./script-7.sh
 eco $? 

Executando um script que captura variáveis ​​não inicializadas.

A variável não inicializada é detectada, o script é interrompido e o código de retorno é definido como um.

A opção -u (unset) é inteligente o suficiente para não ser acionada por situações em que você pode interagir legitimamente com uma variável não inicializada.

No “script-8.sh”, o script verifica se a variável New_Var está inicializada ou não. Você não quer que o script pare aqui, em um script do mundo real, você executará processamento adicional e lidará com a situação você mesmo.

Observe que adicionamos a opção -u como a segunda opção na instrução set. A opção -o pipefail deve vir por último.

 #!/bin/bash 

set -euo pipefail

if [ -z "${New_Var:-}" ]; então 

echo "New_Var não tem valor atribuído a ela." 

fi

Em “script-9.sh”, a variável não inicializada é testada e, se não for inicializada, um valor padrão é fornecido.

 #!/bin/bash
set -euo pipefail

valor_padrão=484
Valor=${New_Var:-$default_value}
echo "New_Var=$Value"

Os scripts podem ser executados até sua conclusão.

 ./script-8.sh
 ./script-9.sh 

Executando dois scripts em que as variáveis ​​não inicializadas são tratadas internamente e a opção -u não é acionada.

Selado com machado

Outra opção útil para usar é a opção set -x (executar e imprimir). Quando você está escrevendo scripts, isso pode ser um salva-vidas. imprime os comandos e seus parâmetros à medida que são executados.

Ele fornece uma forma rápida e “pronta e pronta” de rastreamento de execução. Isolar falhas lógicas e detectar bugs se torna muito, muito mais fácil.

Adicionaremos a opção set -x a “script-8.sh”, salvá-la como “script-10.sh” e torná-la executável.

 #!/bin/bash
set -euxo pipefail

if [ -z "${New_Var:-}" ]; então
  echo "New_Var não tem valor atribuído a ela."
fi

Execute-o para ver as linhas de rastreamento.

 ./script-10.sh 

Executando um script com -x linhas de rastreamento gravadas no terminal.

Detectar bugs nesses scripts de exemplo triviais é fácil. Quando você começar a escrever scripts mais complexos, essas opções provarão seu valor.