Turandot: Gli enigmi sono tre, la morte una!
Caleph: No, no! Gli enigmi sono tre, una la vita!
--Puccini
Voici quelques pratiques d'écriture de script (non recommandées !) qui apportent un peu de piment à des vies autrement sans relief.
Affecter des mots réservés à des noms de variables.
case=value0 # Pose problème. 23skidoo=value1 # Et là-aussi. # Les noms de variables avec un chiffre sont réservés par le shell. # Essayez _23skidoo=value1. On peut commencer une variable avec un tiret bas. # Néanmoins... utiliser seulement le tiret bas ne fonctionne pas. _=25 echo $_ # $_ est une variable spéciale initialisée par le # dernier argument de la dernière commande. # Mais... _ est un nom de fonction valide ! xyz((!*=value2 # Pose de sérieux problèmes. # À partir de la version 3 de Bash, les points ne sont plus autorisés dans les noms de variables.
Utiliser un tiret ou d'autres caractères réservés dans un nom de variable (ou un nom de fonction).
var-1=23 # Utilisez 'var_1' à la place. fonction-quoiquecesoit () # Erreur # Utilisez 'fonction_quoiquecesoit ()' à la place. # À partir de la version 3 de Bash, les points ne sont plus autorisés dans les noms de variables. fonction.quoiquecesoit () # Erreur # Utilisez 'fonctionQuoiquecesoit ()' à la place.
Utiliser le même nom pour une variable et une fonction. Ceci rend le script difficile à comprendre.
fais_quelquechose () { echo "Cette fonction fait quelque chose avec \"$1\"." } fais_quelquechose=fais_quelquechose fais_quelquechose fais_quelquechose # Tout ceci est légal, mais porte à confusion.
Utiliser les espaces de manière inapproriée. Contrairement à d'autres langages de programmation, Bash est parfois pointilleux sur les espaces.
var1 = 23 # 'var1=23' est correct. # Sur la ligne ci-dessus, Bash essaie d'exécuter la commande "var1" # avec les arguments "=" et "23". let c = $a - $b # Ou encore let c=$a-$b, ou let "c = $a - $b" if [ $a -le 5] # if [ $a -le 5 ] est correct. ^^ # if [ "$a" -le 5 ] est encore mieux. # [[ $a -le 5 ]] fonctionne aussi.
Ne pas terminer avec un point-virgule la commande finale d'un bloc de code compris dans des accolades.
{ ls -l; df; echo "Done." } # bash: syntax error: unexpected end of file { ls -l; df; echo "Done."; } # ^ ### La commande finale nécessite un point-virgule.
Supposons que des variables non initialisées (variables considérées avant toute affectation de valeur) sont « remplies de zéros ». Une variable non initialisée a une valeur null, et non pas zéro.
#!/bin/bash echo "variable_non_initialisee = $variable_non_initialisee" # variable_non_initialisee = # Mais... # if $BASH_VERSION >= 4.2; then if [[ ! -v uninitialized_var ]] then uninitialized_var=0 # Elle est initialisée à zéro ! fi
Mélanger = et -eq dans un test. Rappelez-vous, = permet la comparaison de variables littérales et -eq d'entiers.
if [ "$a" = 273 ] # $a est-il un entier ou une chaîne ? if [ "$a" -eq 273 ] # Si $a est un entier. # Quelquefois, vous pouvez mélanger -eq et = sans mauvaises conséquences. # Néanmoins... a=273.0 # pas un entier. if [ "$a" = 273 ] then echo "La comparaison fonctionne." else echo "La comparaison ne fonctionne pas." fi # La comparaison ne fonctionne pas. # Pareil avec a=" 273" et a="0273". # De même, problèmes en essayant d'utiliser "-eq" avec des valeurs non entières. if [ "$a" -eq 273.0 ] then echo "a = $a" fi # Échoue avec un message d'erreur. # test.sh: [: 273.0: integer expression expected
Mal utiliser les opérateurs de comparaison de chaînes.
Exemple 34.1. Les comparaisons d'entiers et de chaînes ne sont pas équivalentes
#!/bin/bash # bad-op.sh: Trying to use a string comparison on integers. echo number=1 # The following while-loop has two errors: #+ one blatant, and the other subtle. while [ "$number" < 5 ] # Wrong! Should be: while [ "$number" -lt 5 ] do echo -n "$number " let "number += 1" done # Attempt to run this bombs with the error message: #+ bad-op.sh: line 10: 5: No such file or directory # Within single brackets, "<" must be escaped, #+ and even then, it's still wrong for comparing integers. echo "---------------------" while [ "$number" \< 5 ] # 1 2 3 4 do # echo -n "$number " # It *seems* to work, but . . . let "number += 1" #+ it actually does an ASCII comparison, done #+ rather than a numerical one. echo; echo "---------------------" # This can cause problems. For example: lesser=5 greater=105 if [ "$greater" \< "$lesser" ] then echo "$greater is less than $lesser" fi # 105 is less than 5 # In fact, "105" actually is less than "5" #+ in a string comparison (ASCII sort order). echo exit 0
Essai d'utilisation de let pour assigner des valeurs à des variables au format chaîne de caractères.
let "a = bonjour, toi" echo "$a" # 0
Quelquefois, des variables entre crochets de « test » ([ ]) ont besoin d'être mises entre guillemets (doubles). Ne pas le faire risque de causer un comportement inattendu. Voir l'Exemple 7.6, « Vérifier si une chaîne est vide », l'Exemple 20.5, « Boucle while redirigée » et l'Exemple 9.6, « arglist : Affichage des arguments avec $* et $@ ».
Mettre entre guillement une variable contenant des espaces blancs empêche la division. Quelque fois, c'est la cause de conséquences inattendues.
Les commandes lancées à partir d'un script peuvent échouer parce que le propriétaire d'un script ne possède pas les droits d'exécution. Si un utilisateur ne peut exécuter une commande à partir de la ligne de commande, alors la placer dans un script échouera de la même façon. Essayer de changer les droits de la commande en question, peut-être même en initialisant le bit suid (en tant que root, bien sûr).
Tenter d'utiliser - comme opérateur de redirection (qu'il n'est pas) provoque en général une surprise désagréable.
commande1 2> - | commande2 # Essayer de rediriger la sortie d'erreurs dans un tube... # ... ne fonctionnera pas commande1 2>& - | commande2 # Aussi futile. Merci, S.C.
Utiliser les fonctionnalités de Bash version 2+ peut poser des soucis avec les messages d'erreur. Les anciennes machines Linux peuvent avoir une version 1.XX de Bash suite à une installation par défaut.
#!/bin/bash minimum_version=2 # Comme Chet Ramey ajoute constamment de nouvelles fonctionnalités à Bash, # vous pourriez configurer $minimum_version à 2.XX, 3.XX, ou quoi que ce soit # de plus approprié. E_MAUVAISE_VERSION=80 if [ "$BASH_VERSION" \< "$minimum_version" ] then echo "Ce script fonctionne seulement avec Bash, version $minimum ou ultérieure." echo "Une mise à jour est fortement recommandée." exit $E_MAUVAISE_VERSION fi ...
Utiliser les fonctionnalités spécifiques à Bash dans un script Bourne shell (#!/bin/sh) sur une machine non Linux provoque parfois un comportement inattendu. Les système Linux créent généralement un alias sh vers bash, mais ce n'est pas forcément le cas sur tous les systèmes UNIX.
Utiliser des fonctionnalités non documentées de Bash se révèle être un pratique dangereuse. Dans les précédentes versions de ce livre, plusieurs scripts dépendaient d'une « fonctionnalité » qui, bien que la valeur maximum d'un exit ou d'un return soit 255, faisait que cette limite ne s'appliquait pas aux entiers négatifs. Malheureusement, à partir de la version 2.05b et des suivantes, cela a disparu. Voir Exemple 24.9, « Tester les valeurs de retour importantes dans une fonction ».
Dans certains contextes, la valeur de retour renvoyée peut être trompeuse. Par exmple si on assigne une valeur à une variable locale à l'intérieur d'une fonction, ou si on assigne une valeur arithmétique à une variable.
La valeur de retour d'une opération arithmétique n'est pas l'équivalent d'un code d'erreur.
var=1 && ((--var)) && echo $var # ^^^^^^^^^ Ici la liste 'et' se termine avec le code 1. # $var n'est pas affichée ! echo $? # 1
Un script avec des retours à la ligne DOS (\r\n) ne pourra pas s'exécuter car #!/bin/bash\r\n n'est pas reconnu, pas la même chose que l'attendu #!/bin/bash\n. La correction est de convertir le script en des retours chariots style UNIX.
#!/bin/bash echo "Ici" unix2dos $0 # Le script se modifie lui-même au format DOS. chmod 755 $0 # et modifie son droit d'exécution. # La commande 'unix2dos' supprime le doit d'exécution. ./$0 # Le script essaie de se lancer de nouveau. # Mais cela ne fonctionnera pas en tant que format DOS. echo "Là" exit 0
Un script shell commençant par #!/bin/sh ne se lancera pas dans un mode de compatibilité complète avec Bash. Quelques fonctions spécifiques à Bash pourraient être désactivées. Les scripts qui ont besoin d'un accès complet à toutes les extensions spécifiques à Bash devraient se lancer avec #!/bin/bash.
Placer une espace blanche devant la chaîne de limite d'un document en ligne pourra causer un comportement inattendu dans un script.
Placer plus d'une instruction echo dans une fonction dont la sortie est capturée.
add2 () { echo "N'importe quoi... " # Supprimez cette ligne ! let "retval = $1 + $2" echo $retval } num1=12 num2=43 echo "Somme de $num1 et $num2 = $(add2 $num1 $num2)" # Somme de 12 et 43 = N'importe quoi... # 55 # Les "echo" se concatènent.
Ceci ne fonctionnera pas.
Un script peut ne pas faire un export de ses variables à son processus parent, le shell ou à l'environnement. Comme nous l'avons appris en biologie, un processus fils peut hériter de son parent, mais le contraire n'est pas vrai.
NIMPORTEQUOI=/home/bozo export NIMPORTEQUOI exit 0
bash$ echo $NIMPORTEQUOI
bash$
De façon certaine, au retour à l'invite de commande, $NIMPORTEQUOI reste sans valeur.
Initialiser et manipuler des variables dans un sous-shell, puis essayer d'utiliser ces mêmes variables en dehors du sous-shell résultera en une mauvaise surprise.
Exemple 34.2. Problèmes des sous-shell
#!/bin/bash # Problèmes des variables dans un sous-shell. variable_externe=externe echo echo "variable_externe = $variable_externe" echo ( # Début du sous-shell echo "variable_externe à l'intérieur du sous-shell = $variable_externe" variable_interne=interne # Configure echo "variable_interne à l'intérieur du sous-shell = $variable_interne" variable_externe=interne # Sa valeur va-t'elle changer globalement? echo "variable_externe à l'intérieur du sous-shell = $variable_externe" # Est-ce qu'un export fera une différence ? # export variable_interne # export variable_externe # Essayez. # Fin du sous-shell ) echo echo "variable_interne à l'extérieur du sous-shell = $variable_interne" # Désinitialise. echo "variable_externe à l'extérieur du sous-shell = $variable_externe" # Non modifié. echo exit 0 # Qu'arrive-t'il si vous décommentez les lignes 19 et 20 ? # Cela fait-il une différence ?
Envoyer dans un tube la sortie de echo pour un read peut produire des résultats inattendus. Dans ce scénario, read agit comme si elle était lancée dans un sous-shell. À la place, utilisez la commande set (comme dans l'Exemple 15.18, « Réaffecter les paramètres de position »).
Exemple 34.3. Envoyer la sortie de echo dans un tube pour un read
#!/bin/bash # badread.sh : # Tentative d'utiliser 'echo' et 'read' #+ pour affecter non interactivement des variables. a=aaa b=bbb c=ccc echo "un deux trois" | read a b c # Essaie d'affecter a, b et c. echo echo "a = $a" # a = aaa echo "b = $b" # b = bbb echo "c = $c" # c = ccc # L'affectation a échoué. # ------------------------------ # Essaie l'alternative suivante. var=`echo "un deux trois"` set -- $var a=$1; b=$2; c=$3 echo "-------" echo "a = $a" # a = un echo "b = $b" # b = deux echo "c = $c" # c = trois # Affectation réussie. # ------------------------------ # Notez aussi qu'un echo pour un 'read' fonctionne à l'intérieur d'un #+ sous-shell. # Néanmoins, la valeur de la variable change *seulement* à l'intérieur du #+ sous-shell. a=aaa # On recommence. b=bbb c=ccc echo; echo echo "un deux trois" | ( read a b c; echo "À l'intérieur du sous-shell : "; echo "a = $a"; echo "b = $b"; echo "c = $c" ) # a = un # b = deux # c = trois echo "-----------------" echo "À l'extérieur du sous-shell : " echo "a = $a" # a = aaa echo "b = $b" # b = bbb echo "c = $c" # c = ccc echo exit 0
En fait, comme l'indique Anthony Richardson, envoyer via un tube à partir de n'importe quelle boucle peut amener des problèmes similaires.
# Problèmes des tubes dans des boucles. # Exemple de Anthony Richardson #+ avec un ajout de Wilbert Berendsen. trouve=false find $HOME -type f -atime +30 -size 100k | while true do read f echo "$f a une taille supérieure à 100 Ko et n'a pas été utilisé depuis au moins 30 jours." echo "Prenez en considération le déplacement de ce fichier dans les archives." trouve=true # ------------------------------------ echo "Niveau de sous-shell = $BASH_SUBSHELL" # Niveau de sous-shell = 1 # Oui, nous sommes dans un sous-shell. # ------------------------------------ done # trouve sera toujours faux car il est initialisé dans un sous-shell. if [ $trouve = false ] then echo "Aucun fichier ne doit être archivé." fi # ================Maintenant, voici une façon correcte de le faire :============ trouve=false for f in $(find $HOME -type f -atime +30 -size 100k) # Pas de tube ici. do echo "$f a une taille supérieure à 100 Ko et n'a pas été utilisé depuis au moins 30 jours." echo "Prenez en considération le déplacement de ce fichier dans les archives." trouve=true done if [ $trouve = false ] then echo "Aucun fichier ne doit être archivé." fi # ==================Et voici une autre alternative================== # Place la partie du script lisant les variables à l'intérieur d'un bloc de #+ code de façon à ce qu'ils partagent le même sous-shell. # Merci, W.B. find $HOME -type f -atime +30 -size 100k | { trouve=false while read f do echo "$f a une taille supérieure à 100 Ko et n'a pas été utilisé depuis au moins 30 jours." echo "Prenez en considération le déplacement de ce fichier dans les archives." trouve=true done if ! $trouve then echo "Aucun fichier ne doit être archivé." fi }
Un problème analogue survient lors de la tentative d'écriture sur stdout par un tail -f envoyé via un tube sur grep.
tail -f /var/log/messages | grep "$MSG_ERREUR" >> erreur.log # Rien ne sera écrit dans le fichier "erreur.log". # Ainsi que Samuli Kaipiainen le fait remarquer, cette commande #+ empêche grep d'envoyer la sortie dans le tampon. # Pour corriger ce comportement, ajouter à grep le paramètre # "--line-buffered".
Utiliser les commandes « suid » à l'intérieur de scripts est risqué et peut compromettre la sécurité de votre système. [119]
Utiliser des scripts shell en programmation CGI peut être assez problématique. Les variables des scripts shell ne sont pas « sûres, » ce qui entraîne parfois un comportement indésirable pour un script CGI. De plus, il est difficile de « sécuriser » les scripts shell.
Bash ne gère pas la chaîne double slash (//) correctement.
Les scripts Bash écrits pour Linux ou BSD peuvent nécessiter des corrections pour fonctionner sur une machine UNIX commerciale (ou Apple OSX). De tels scripts emploient souvent l'ensemble GNU de commande et de filtres qui ont plus de fonctionnalités que leur contrepartie UNIX. Ceci est particulièrement vrai pour les utilitaires texte comme tr.
Danger is near thee --
Beware, beware, beware, beware.
Many brave hearts are asleep in the deep.
So beware --
Beware.