Les bogues et les fautes de frappe dans les scripts Linux Bash peuvent avoir des conséquences désastreuses lors de l’exécution du script. Voici quelques façons de vérifier la syntaxe de vos scripts avant même de les exécuter.
Ces insectes embêtants
Écrire du code est difficile. Ou pour être plus précis, écrire du code non trivial sans bogue est difficile. Et plus il y a de lignes de code dans un programme ou un script, plus il y a de chances qu’il y ait des bogues.
Le langage dans lequel vous programmez a une incidence directe sur cela. La programmation en assembleur est beaucoup plus difficile que la programmation en C, et la programmation en C est plus difficile que la programmation en Python. Plus le langage dans lequel vous programmez est de bas niveau, plus vous avez de travail à faire vous-même. Python peut profiter des routines intégrées de récupération de place, mais le C et l’assembly ne le font certainement pas.
L’écriture de scripts shell Linux pose ses propres défis. Avec un langage compilé comme C, un programme appelé compilateur lit votre code source – les instructions lisibles par l’homme que vous tapez dans un fichier texte – et le transforme en un fichier exécutable binaire. Le fichier binaire contient les instructions du code machine que l’ordinateur peut comprendre et agir.
Le compilateur ne générera un fichier binaire que si le code source qu’il lit et analyse respecte la syntaxe et les autres règles du langage. Si vous épelez un mot reservé—un des mots de commande du langage—ou un nom de variable incorrect, le compilateur renverra une erreur.
Par exemple, certains langages insistent pour que vous déclariez une variable avant de l’utiliser, d’autres ne sont pas si pointilleux. Si le langage dans lequel vous travaillez vous oblige à déclarer des variables mais que vous oubliez de le faire, le compilateur lancera un message d’erreur différent. Aussi ennuyeuses que soient ces erreurs de compilation, elles détectent de nombreux problèmes et vous obligent à les résoudre. Mais même lorsque vous avez un programme qui n’a pas bogues syntaxiques cela ne veut pas dire qu’il n’y a pas de bugs. Loin de là.
Les bugs dus à défauts logiques sont généralement beaucoup plus difficiles à repérer. Si vous dites à votre programme d’ajouter deux et trois mais que vous vouliez vraiment qu’il ajoute deux et deux, vous n’obtiendrez pas la réponse que vous attendiez. Mais le programme fait ce pour quoi il a été écrit. Il n’y a rien de mal avec la composition ou la syntaxe du programme. Le problème, c’est vous. Vous avez écrit un programme bien formé qui ne fait pas ce que vous vouliez.
Les tests sont difficiles
Tester minutieusement un programme, même simple, prend du temps. L’exécuter plusieurs fois n’est pas suffisant ; vous devez vraiment tester tous les chemins d’exécution dans votre code, afin que toutes les parties du code soient vérifiées. Si le programme demande une entrée, vous devez fournir une plage suffisante de valeurs d’entrée pour tester toutes les conditions, y compris les entrées inacceptables.
Pour les langages de niveau supérieur, les tests unitaires et les tests automatisés permettent de faire des tests approfondis un exercice gérable. La question est donc de savoir s’il existe des outils que nous pouvons utiliser pour nous aider à écrire des scripts shell Bash sans bogue.
La réponse est oui, y compris le shell Bash lui-même.
Utilisation de Bash pour vérifier la syntaxe du script
La fête -n
(noexec) indique à Bash de lire un script et de vérifier les erreurs de syntaxe, sans exécuter le script. Selon ce que votre script est destiné à faire, cela peut être beaucoup plus sûr que de l’exécuter et de rechercher des problèmes.
Voici le script que nous allons vérifier. Ce n’est pas compliqué, c’est principalement un ensemble de if
déclarations. Il demande et accepte un nombre représentant un mois. Le script décide à quelle saison appartient le mois. Évidemment, cela ne fonctionnera pas si l’utilisateur ne fournit aucune entrée, ou s’il fournit une entrée invalide comme une lettre au lieu d’un chiffre.
#! /bin/bash read -p "Enter a month (1 to 12): " month # did they enter anything? if [ -z "$month" ] then echo "You must enter a number representing a month." exit 1 fi # is it a valid month? if (( "$month" < 1 || "$month" > 12)); then echo "The month must be a number between 1 and 12." exit 0 fi # is it a Spring month? if (( "$month" >= 3 && "$month" < 6)); then echo "That's a Spring month." exit 0 fi # is it a Summer month? if (( "$month" >= 6 && "$month" < 9)); then echo "That's a Summer month." exit 0 fi # is it an Autumn month? if (( "$month" >= 9 && "$month" < 12)); then echo "That's an Autumn month." exit 0 fi # it must be a Winter month echo "That's a Winter month." exit 0
Cette section vérifie si l’utilisateur a saisi quoi que ce soit. Il teste si le $month
la variable n’est pas définie.
if [ -z "$month" ] then echo "You must enter a number representing a month." exit 1 fi
Cette section vérifie s’ils ont saisi un nombre compris entre 1 et 12. Elle intercepte également une entrée non valide qui n’est pas un chiffre, car les lettres et les symboles de ponctuation ne se traduisent pas en valeurs numériques.
# is it a valid month? if (( "$month" < 1 || "$month" > 12)); then echo "The month must be a number between 1 and 12." exit 0 fi
Toutes les autres clauses If vérifient si la valeur dans $month
variable est entre deux valeurs. Si c’est le cas, le mois appartient à cette saison. Par exemple, si le mois saisi par l’utilisateur est 6, 7 ou 8, il s’agit d’un mois d’été.
# is it a Summer month? if (( "$month" >= 6 && "$month" < 9)); then echo "That's a Summer month." exit 0 fi
Si vous souhaitez travailler sur nos exemples, copiez et collez le texte du script dans un éditeur et enregistrez-le sous « seasons.sh ». Ensuite, rendez le script exécutable en utilisant le chmod
commander:
chmod +x seasons.sh
Nous pouvons tester le script en
- Ne fournissant aucune contribution.
- Fournir une entrée non numérique.
- Fournir une valeur numérique qui est en dehors de la plage de 1 à 12.
- Fournir des valeurs numériques comprises entre 1 et 12.
Dans tous les cas, on démarre le script avec la même commande. La seule différence est l’entrée que l’utilisateur fournit lorsqu’il est promu par le script.
./seasons.sh
Cela semble fonctionner comme prévu. Laissons Bash vérifier la syntaxe de notre script. Nous le faisons en invoquant le -n
(noexec) et en passant le nom de notre script.
bash -n ./seasons.sh
C’est un cas de « pas de nouvelles, bonnes nouvelles ». Nous renvoyer silencieusement à l’invite de commande est la manière de Bash de dire que tout semble OK. Sabotons notre script et introduisons une erreur.
Nous supprimerons le then
Depuis le premier if
clause.
# is it a valid month? if (( "$month" < 1 || "$month" > 12)); # "then" has been removed echo "The month must be a number between 1 and 12." exit 0 fi
Exécutons maintenant le script, d’abord sans puis avec l’entrée de l’utilisateur.
./seasons.sh
La première fois que le script est exécuté, l’utilisateur n’entre pas de valeur et le script se termine donc. La section que nous avons sabotée n’est jamais atteinte. Le script se termine sans message d’erreur de Bash.
La deuxième fois que le script est exécuté, l’utilisateur fournit une valeur d’entrée et la première clause if est exécutée pour vérifier l’intégrité de l’entrée de l’utilisateur. Cela déclenche le message d’erreur de Bash.
Notez que Bash vérifie la syntaxe de cette clause – et de toutes les autres lignes de code – car il ne se soucie pas de la logique du scénario. L’utilisateur n’est pas invité à entrer un nombre lorsque Bash vérifie le script, car le script n’est pas en cours d’exécution.
Les différents chemins d’exécution possibles du script n’affectent pas la façon dont Bash vérifie la syntaxe. Bash travaille simplement et méthodiquement du haut du script vers le bas, en vérifiant la syntaxe pour chaque ligne.
L’utilitaire ShellCheck
Un linter – du nom d’un outil de vérification de code source C de l’apogée d’Unix – est un outil d’analyse de code utilisé pour détecter les erreurs de programmation, les erreurs de style et l’utilisation suspecte ou douteuse du langage. Les linters sont disponibles pour de nombreux langages de programmation et sont réputés pour être pédants. Tout ce qu’un linter trouve n’est pas un bogue en soimais tout ce qu’ils font porter à votre attention mérite probablement l’attention.
ShellCheck est un outil d’analyse de code pour les scripts shell. Il se comporte comme un linter pour Bash.
Mettons nos disparus then
mot réservé dans notre script, et essayez autre chose. Nous allons retirer la parenthèse d’ouverture « [” from the very first if
clause.
# did they enter anything? if -z "$month" ] # parenthèse ouvrante "[" removed then echo "You must enter a number representing a month." exit 1 fi
if we use Bash to check the script it doesn’t find a problem.
bash -n seasons.sh
./seasons.sh
But when we try to run the script we see an error message. And, despite the error message, the script continues to execute. This is why some bugs are so dangerous. If the actions taken further on in the script rely on valid input from the user, the script’s behavior will be unpredictable. It could potentially put data at risk.
The reason the Bash -n
(noexec) option doesn’t find the error in the script is the opening bracket “[” is an external program called [
. It isn’t part of Bash. It is a shorthand way of using the test
command.
Bash doesn’t check the use of external programs when it is validating a script.
Installing ShellCheck
ShellCheck requires installation. To install it on Ubuntu, type:
sudo apt install shellcheck
To install ShellCheck on Fedora, use this command. Note that the package name is in mixed case, but when you issue the command in the terminal window it is all in lowercase.
sudo dnf install ShellCheck
On Manjaro and similar Arch-based distros, we use pacman
:
sudo pacman -S shellcheck
Using ShellCheck
Let’s try running ShellCheck on our script.
shellcheck seasons.sh
ShellCheck finds the issue and reports it to us, and provides a set of links for further information. If you right-click a link and choose “Open Link” from the context menu that appears, the link will open in your browser.
ShellCheck also finds another issue, which isn’t as serious. It is reported in green text. This indicates it is a warning, not an out-and-out error.
Let’s correct our error and replace the missing “[.” One bug-fix strategy is to correct the highest priority issues first and work down to the lower priority issues like warnings later.
We replaced the missing “[” and ran ShellCheck once more.
shellcheck seasons.sh
The only output from ShellCheck refers to our previous warning, so that’s good. We have no high-priority issues needing fixing.
The warning tells us that using the read
command without the -r
(read as-is) option will cause any backslashes in the input to be treated as escape characters. This is a good example of the type of pedantic output a linter can generate. In our case the user shouldn’t be entering a backslash anyway—we need them to enter a number.
Warnings like this require a judgment call on the part of the programmer. Make the effort to fix it, or leave it as it is? It’s a simple two-second fix. And it’ll stop the warning cluttering up ShellCheck’s output, so we might as well take its advice. We’ll add an “r” to option the flags on the read
command, and save the script.
read -pr "Enter a month (1 to 12): " month
Running ShellCheck once more gives us a clean bill of health.
ShellCheck Is Your Friend
ShellCheck can detect, report, and advise on a whole range of issues. Check out their gallery of bad code, which shows how many types of problems it can detect.
It’s free, fast, and takes a lot of the pain out of writing shell scripts. What’s not to like?