Bugs e erros de digitação em scripts Linux Bash podem fazer coisas terríveis quando o script é executado. Aqui estão algumas maneiras de verificar a sintaxe de seus scripts antes mesmo de executá-los.
Índice
Esses insetos chatos
Escrever código é difícil. Ou, para ser mais preciso, escrever código não trivial livre de bugs é difícil. E quanto mais linhas de código houver em um programa ou script, mais provável será que haja bugs nele.
A linguagem em que você programa tem uma relação direta com isso. Programar em assembly é muito mais difícil do que programar em C, e programar em C é mais desafiador do que programar em Python . Quanto mais baixo nível for a linguagem em que você estiver programando, mais trabalho terá que fazer sozinho. Python pode desfrutar de rotinas de coleta de lixo embutidas, mas C e assembly certamente não.
Escrever scripts de shell do Linux apresenta seus próprios desafios. Com uma linguagem compilada como C, um programa chamado compilador lê seu código-fonte – as instruções legíveis por humanos que você digita em um arquivo de texto – e o transforma em um arquivo executável binário. O arquivo binário contém as instruções do código de máquina que o computador pode entender e agir.
O compilador só irá gerar um arquivo binário se o código fonte que está lendo e analisando obedecer a sintaxe e outras regras da linguagem. Se você soletrar uma palavra reservada — uma das palavras de comando do idioma — ou um nome de variável incorretamente, o compilador lançará um erro.
Por exemplo, algumas linguagens insistem que você declare uma variável antes de usá-la, outras não são tão exigentes. Se a linguagem em que você está trabalhando exigir que você declare variáveis, mas você se esqueça de fazer isso, o compilador lançará uma mensagem de erro diferente. Por mais irritantes que sejam esses erros em tempo de compilação, eles detectam muitos problemas e forçam você a resolvê-los. Mas mesmo quando você tem um programa que não tem bugs sintáticos , isso não significa que não haja bugs nele. Longe disso.
Bugs que são devidos a falhas lógicas são geralmente muito mais difíceis de detectar. Se você disser ao seu programa para somar dois e três, mas você realmente queria que ele somasse dois e dois, você não obterá a resposta que esperava. Mas o programa está fazendo o que foi escrito para fazer. Não há nada de errado com a composição ou sintaxe do programa. O problema é você. Você escreveu um programa bem formado que não faz o que você queria.
Testar é difícil
Testar completamente um programa, mesmo um simples, é demorado. Executá-lo algumas vezes não é suficiente; você realmente precisa testar todos os caminhos de execução em seu código, para que todas as partes do código sejam verificadas. Se o programa solicitar entrada, você precisará fornecer um intervalo suficiente de valores de entrada para testar todas as condições, incluindo entradas inaceitáveis.
Para linguagens de alto nível, testes de unidade e testes automatizados ajudam a tornar o teste completo um exercício gerenciável. Portanto, a questão é: existem ferramentas que podemos usar para nos ajudar a escrever scripts de shell Bash livres de bugs?
A resposta é sim, incluindo o próprio shell Bash.
Usando o Bash para verificar a sintaxe do script
A -n
opção Bash (noexec) diz ao Bash para ler um script e verificar se há erros sintáticos, sem executar o script. Dependendo do que seu script pretende fazer, isso pode ser muito mais seguro do que executá-lo e procurar problemas.
Aqui está o script que vamos verificar. Não é complicado, é principalmente um conjunto de if
declarações. Ele solicita e aceita um número que representa um mês. O script decide a qual estação o mês pertence. Obviamente, isso não funcionará se o usuário não fornecer nenhuma entrada ou se fornecer uma entrada inválida como uma letra em vez de um dígito.
#! /bin/bash read -p "Digite um mês (1 a 12): " mês # eles digitaram alguma coisa? if [ -z "$mês" ] então echo "Você deve inserir um número representando um mês." saída 1 fi # é um mês válido? if (("$mês" < 1 || "$mês" > 12)); então echo "O mês deve ser um número entre 1 e 12." saída 0 fi # é um mês de primavera? if (( "$mês" >= 3 && "$mês" < 6)); então echo "Isso é um mês de primavera." saída 0 fi # é um mês de verão? if (( "$mês" >= 6 && "$mês" < 9)); então echo "Esse é um mês de verão." saída 0 fi # é um mês de outono? if (( "$mês" >= 9 && "$mês" < 12)); então echo "Esse é um mês de outono." saída 0 fi # deve ser um mês de inverno echo "É um mês de inverno." saída 0
Esta seção verifica se o usuário digitou alguma coisa. Ele testa se a $month
variável não está definida.
if [ -z "$mês" ] então echo "Você deve inserir um número representando um mês." saída 1 fi
Esta seção verifica se eles inseriram um número entre 1 e 12. Ela também intercepta entradas inválidas que não são um dígito, porque letras e símbolos de pontuação não se traduzem em valores numéricos.
# é um mês válido? if (("$mês" < 1 || "$mês" > 12)); então echo "O mês deve ser um número entre 1 e 12." saída 0 fi
Todas as outras cláusulas If verificam se o valor na $month
variável está entre dois valores. Se for, o mês pertence a essa estação. Por exemplo, se o mês inserido pelo usuário for 6, 7 ou 8, é um mês de verão.
# é um mês de verão? if (( "$mês" >= 6 && "$mês" < 9)); então echo "Esse é um mês de verão." saída 0 fi
Se você quiser trabalhar com nossos exemplos, copie e cole o texto do script em um editor e salve-o como “seasons.sh”. Em seguida, torne o script executável usando o chmod
comando :
chmod +x seasons.sh
Podemos testar o script por
- Fornecendo nenhuma entrada em tudo.
- Fornecendo uma entrada não numérica.
- Fornecendo um valor numérico que está fora do intervalo de 1 a 12.
- Fornecendo valores numéricos dentro do intervalo de 1 a 12.
Em todos os casos, iniciamos o script com o mesmo comando. A única diferença é a entrada que o usuário fornece quando promovido pelo script.
./estações.sh
Isso parece funcionar como esperado. Vamos fazer com que o Bash verifique a sintaxe do nosso script. Fazemos isso invocando a -n
opção (noexec) e passando o nome do nosso script.
bash -n ./seasons.sh
Este é um caso de “nenhuma notícia é uma boa notícia”. Retornar silenciosamente ao prompt de comando é a maneira de Bash dizer que tudo parece estar bem. Vamos sabotar nosso script e introduzir um erro.
Vamos remover o then
da primeira if
cláusula.
# é um mês válido? if (("$mês" < 1 || "$mês" > 12)); # "então" foi removido echo "O mês deve ser um número entre 1 e 12." saída 0 fi
Agora vamos executar o script, primeiro sem e depois com entrada do usuário.
./estações.sh
Na primeira vez que o script é executado, o usuário não insere um valor e, portanto, o script termina. A seção que sabotamos nunca é alcançada. O script termina sem uma mensagem de erro do Bash.
Na segunda vez que o script é executado, o usuário fornece um valor de entrada e a primeira cláusula if é executada para verificar a integridade da entrada do usuário. Isso aciona a mensagem de erro do Bash.
Observe que o Bash verifica a sintaxe dessa cláusula – e todas as outras linhas de código – porque não se importa com a lógica do script. O usuário não é solicitado a inserir um número quando o Bash verifica o script, porque o script não está em execução.
Os diferentes caminhos de execução possíveis do script não afetam como o Bash verifica a sintaxe. O Bash funciona de forma simples e metódica do início ao fim do script, verificando a sintaxe de cada linha.
O utilitário ShellCheck
Um linter – nomeado para uma ferramenta de verificação de código fonte C do auge do Unix – é uma ferramenta de análise de código usada para detectar erros de programação, erros estilísticos e uso suspeito ou questionável da linguagem. Linters estão disponíveis para muitas linguagens de programação e são conhecidos por serem pedantes. Nem tudo que um linter encontra é um bug em si , mas qualquer coisa que eles tragam ao seu conhecimento provavelmente merece atenção.
ShellCheck é uma ferramenta de análise de código para scripts de shell. Ele se comporta como um linter para Bash.
Vamos colocar nossa then
palavra reservada ausente de volta ao nosso script e tentar outra coisa. Removeremos o colchete de abertura “[” da primeira if
cláusula.
# eles digitaram alguma coisa? if -z "$month" ] # colchete de abertura "[" removido então echo "Você deve inserir um número representando um mês." saída 1 fi
se usarmos o Bash para verificar o script, ele não encontrará um problema.
bash -n temporadas.sh
./estações.sh
Mas quando tentamos executar o script, vemos uma mensagem de erro. E, apesar da mensagem de erro, o script continua a ser executado. É por isso que alguns bugs são tão perigosos. Se as ações executadas posteriormente no script dependerem de uma entrada válida do usuário, o comportamento do script será imprevisível. Isso poderia colocar os dados em risco.
A razão pela qual a opção Bash -n
(noexec) não encontra o erro no script é o colchete de abertura “[” é um programa externo chamado [
. Não faz parte do Bash. É uma forma abreviada de usar o test
comando .
O Bash não verifica o uso de programas externos ao validar um script.
Instalando o ShellCheck
ShellCheck requer instalação. Para instalá-lo no Ubuntu, digite:
sudo apt install shellcheck
Para instalar o ShellCheck no Fedora, use este comando. Observe que o nome do pacote está em maiúsculas e minúsculas, mas quando você emite o comando na janela do terminal, tudo está em minúsculas.
sudo dnf install ShellCheck
No Manjaro e distribuições semelhantes baseadas em Arch , usamos pacman
:
sudo pacman -S shellcheck
Usando ShellCheck
Vamos tentar executar ShellCheck em nosso script.
shellcheck seasons.sh
ShellCheck encontra o problema e o relata para nós, e fornece um conjunto de links para mais informações. Se você clicar com o botão direito do mouse em um link e escolher “Abrir link” no menu de contexto que aparece, o link será aberto em seu navegador.
O ShellCheck também encontra outro problema, que não é tão sério. É relatado em texto verde. Isso indica que é um aviso, não um erro completo.
Vamos corrigir nosso erro e substituir o “[.” Uma estratégia de correção de bug é corrigir os problemas de prioridade mais alta primeiro e trabalhar para os problemas de prioridade mais baixa, como avisos depois.
Substituímos o “[” ausente e executamos ShellCheck mais uma vez.
shellcheck seasons.sh
A única saída de ShellCheck refere-se ao nosso aviso anterior, então isso é bom. Não temos problemas de alta prioridade que precisam ser corrigidos.
O aviso nos diz que usar o read
comando sem a -r
opção (ler como está) fará com que quaisquer barras invertidas na entrada sejam tratadas como caracteres de escape. Este é um bom exemplo do tipo de saída pedante que um linter pode gerar. No nosso caso, o usuário não deveria estar digitando uma barra invertida de qualquer maneira – precisamos que ele digite um número.
Avisos como este requerem um julgamento por parte do programador. Fazer o esforço para corrigi-lo, ou deixá-lo como está? É uma correção simples de dois segundos. E isso impedirá que o aviso atrapalhe a saída do ShellCheck, então podemos seguir seu conselho. Adicionaremos um “r” para optar pelos sinalizadores no read
comando e salvaremos o script.
read -pr "Digite um mês (1 a 12): " mês
A execução do ShellCheck mais uma vez nos dá um atestado de saúde.
ShellCheck é seu amigo
ShellCheck pode detectar, relatar e aconselhar sobre uma ampla gama de problemas . Confira a galeria de código ruim , que mostra quantos tipos de problemas ele pode detectar.
É grátis, rápido e tira muito do trabalho de escrever scripts de shell. O que há para não gostar?