Turandot: Gli enigmi sono tre, la morte una!
Caleph: No, no! Gli enigmi sono tre, una la vita!
Voici quelques pratiques d'écriture de script (non recommandées !) qui apportent du piment aux vies 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. Commencer les variables avec un tiret bas est OK. # Néanmoins... n'utiliser que le tiret bas ne fonctionnera pas. _=25 echo $_ # $_ est une variable spéciale initialisée comme étant le # dernier argument de la dernière commande. xyz((!*=value2 # Pose de sévères 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 des espaces blancs inappropriés. En contraste avec d'autres langages de programmation, Bash peut être assez chatouilleux avec les espaces blancs.
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 # 'let c=$a-$b' et 'let "c = $a - $b"' sont corrects. 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.
Supposer que des variables non initialisées (variables avant qu'une valeur ne leur soit affectée) 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 =
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 31.1. Les comparaisons d'entiers et de chaînes ne sont pas équivalentes
#!/bin/bash # bad-op.sh : Essaie d'utiliser une comparaison de chaînes sur des entiers. echo nombre=1 # La boucle "while" suivante contient deux "erreurs" : #+ une évidente et une plus subtile. while [ "$nombre" < 5 ] # Mauvais ! Devrait être : while [ "$nombre" -lt 5 ] do echo -n "$nombre " let "nombre += 1" done # Essayer de lancer ceci s'arrête avec ce message d'erreur : #+ bad-op.sh: line 10: 5: No such file or directory # À l'intérieur de crochets simples, "<" doit être échappé, #+ et, même là, c'est toujours mauvais pour comparer des entiers. echo "---------------------" while [ "$nombre" \< 5 ] # 1 2 3 4 do # echo -n "$nombre " # Ceci *semble* fonctionner mais... let "nombre += 1" #+ il fait réellement une comparaison ASCII done #+ et non pas une comparaison numérique. echo; echo "---------------------" # Ceci peut causer des problèmes. Par exemple : pluspetit=5 plusgrand=105 if [ "$plusgrand" \< "$pluspetit" ] then echo "$plusgrand est plus petit que $pluspetit" fi # 105 est plus petit que 5 # En fait, "105" est réellement plus petit que "5" #+ lors d'une comparaison de chaîne (ordre ASCII). echo exit 0
Quelquefois, des variables à l'intérieur des 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érification si une chaîne est nulle », l'Exemple 19.5, « Boucle while redirigée » et l'Exemple 9.6, « arglist : Affichage des arguments avec $* et $@ ».
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) résultera habituellement en une surprise peu plaisante.
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 shell Bourne (#!/bin/sh) sur une machine non Linux peut causer un comportement inattendu. Un système Linux crée habituellement un alias sh vers bash, mais ceci n'est pas nécessairement vrai pour une machine UNIX générique.
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 23.9, « Tester les valeurs de retour importantes dans une fonction ».
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.
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 31.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 14.18, « Réaffecter les paramètres de position »).
Exemple 31.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 relatif 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 # Le fichier "erreur.log" ne sera pas écrit.
Utiliser les commandes « suid » à l'intérieur de scripts est risqué et peut compromettre la sécurité de votre système. [88]
Utiliser des scripts shell en programmation CGI peut être assez problématique. Les variables des scripts shell ne sont pas « sûres » et ceci peut causer un comportement indésirable en ce qui concerne CGI. De plus, il est difficile de « sécuriser » des 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.