Como usar set e pipefail em scripts Bash no Linux

Terminal Linux em uma tela de laptop sobre um fundo azul.
fatmawati achmad zaenuri/Shutterstock.com

O Linux sete os pipefailcomandos ditam o que acontece quando ocorre uma falha em um script Bash . Há mais em que pensar do que deveria parar ou continuar.

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.

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 também podem ocorrer erros dentro do seu script, como tentar acessar uma variável não inicializada .

Os comandos sete pipefilepermitem 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á usarchmod :

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.

Recomendado:  Por que você deve usar o Proton em vez do Steam Linux Runtime

Vamos modificar um pouco o script. Pediremos lspara 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 esse script, vemos a mensagem de erro do ls.

./script-2.sh

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

Embora o lscomando 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 a $?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 set -eopção (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 set -eopção 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 do 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.

Recomendado:  Como habilitar o usuário root no macOS

Podemos ver os efeitos dos comandos de tubulação com diferentes códigos de retorno usando os recursos internos truee do falseshell. 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 entrarmos — representando um processo falsecom falha — obteremos o código de retorno de zero.truefalsetrue

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.

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

Este é o lugar onde set -o(opções) e pipefailentram. Este é “script-4.sh”. Isso tentará canalizar o conteúdo de um arquivo que não existe em 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 echocomando.

Vamos adicionar o -o pipefailarquivo , 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 echocomando não é executado. O código de retorno enviado ao shell é um, indicando corretamente uma falha.

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 echoo valor de uma variável não inicializada, echosimplesmente imprime uma linha em branco. Não gera uma mensagem de erro. O resto do script continuará a ser executado.

Recomendado:  Como alterar fontes em telefones Samsung

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.

Podemos capturar esse tipo de erro usando a set -uopção (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 -uopção (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.

Em “script-8.sh”, o script verifica se a variável New_Varestá 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 -uopção como a segunda opção na instrução set. A -o pipefailopção 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.