O Linux set
e os pipefail
comandos ditam o que acontece quando ocorre uma falha em um script Bash . Há mais em que pensar do que deveria parar ou continuar.
Índice
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 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á 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
As duas linhas de texto são enviadas para a janela do terminal conforme o esperado.
Vamos modificar um pouco o script. Pediremos 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 esse script, vemos a mensagem de erro do ls
.
./script-2.sh
Embora o ls
comando 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 $?
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 -e
opçã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 -e
opçã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 $?
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 recursos internos true
e do false
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 $?
Se entrarmos — representando um processo false
com falha — obteremos o código de retorno de zero.true
false
true
falso | verdadeiro
eco $?
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]}"
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.
Este é o lugar onde set -o
(opções) e pipefail
entram. 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 $?
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 echo
comando.
Vamos adicionar o -o pipefail
arquivo , 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 $?
O script é interrompido e o segundo echo
comando 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 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 $?
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 -u
opçã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 $?
A variável não inicializada é detectada, o script é interrompido e o código de retorno é definido como um.
A -u
opçã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_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 -u
opção como a segunda opção na instrução set. A -o pipefail
opçã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
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
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.