Un conseil que les nouveaux programmeurs bash entendront encore et encore :
Mets tes variables entre guillemets !
Pourquoi donc ?
Quand on développe une variable (comme $name), on veut presque toujours utiliser la valeur de la variable comme une seule string (ou un seul nombre).
On ne veut pas que cette valeur soit traitée de façon inattendue comme deux strings distinctes ou plus.
La mise entre guillemets est le principal mécanisme qui garantit qu'un morceau de texte est traité comme un seul mot. Il existe deux principaux types de guillemets.
Les guillemets doubles permettent l'expansion des paramètres, l'expansion arithmétique et la substitution de commande à l'intérieur.
echo "This is bash version $BASH_VERSION"
On peut inclure un guillemet double littéral à l'intérieur d'une string entre guillemets doubles en le précédant d'un antislash.
echo "She said \"Let me teach you about bash.\""
Les guillemets simples ne permettent aucune expansion à l'intérieur.
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
Il est impossible d'intégrer un guillemet simple à l'intérieur d'une string entre guillemets simples.
Qu'est-ce qui ne va pas, exactement, avec echo $name ?
Quand le shell analyse une ligne de code, il effectue plusieurs types d'expansions, dans l'ordre. Deux des dernières expansions sont le découpage des mots et l'expansion des noms de fichiers.
Les résultats d'une expansion de paramètre, d'une substitution de commande ou d'une expansion arithmétique qui n'ont pas eu lieu entre guillemets doubles peuvent faire l'objet d'un « découpage des mots ».
Le shell prend le résultat développé et le découpe sur toute séquence de caractères présente dans la valeur de $IFS.
Par défaut, il s'agit de l'espace, de la tabulation et du saut de ligne.
On peut le voir en action avec une boucle du shell :
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
affiche
1 The quick brown fox jumps.
Mais sans guillemets :
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
affiche
1 The
2 quick
3 brown
4 fox
5 jumps.
Voyons ce que ça donne avec une autre valeur de $IFS :
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
affiche
first
second
third
Une technique pour désactiver le découpage des mots consiste à définir $IFS comme une string vide
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
affiche
1 The quick brown fox jumps.
Cependant, il n'est pas recommandé de laisser les variables sans guillemets, sauf si on désactive aussi l'expansion des noms de fichiers. On verra comment faire plus bas.
Les motifs « glob » servent à exprimer de façon concise un motif qui correspond à un ensemble de fichiers.
Par exemple, on peut faire correspondre tous les fichiers Markdown d'un répertoire avec *.md.
Les motifs glob existent depuis l'invention d'Unix.
Les caractères génériques des motifs glob sont :
* correspond à zéro ou plusieurs caractères quelconques,
? correspond exactement à un caractère quelconque,
[...] correspond exactement à un caractère de l'ensemble des caractères listés entre les crochets.
[abc] correspond exactement à un caractère qui est soit un a, soit un b, soit un c.
^ facultatif)
[][] est un ensemble de caractères qui correspond soit à un crochet fermant, soit à un crochet ouvrant.[[\]]
[x-y] correspond exactement à un caractère qui se trouve dans l'intervalle allant de x à y
^ facultatif), soit le dernier caractère avant le crochet fermant.
[-xy] ou [xy-] correspondent chacun exactement à un caractère qui est un -, un x ou un y
[x\-y]
[0-9A-Fa-f] correspond à un chiffre hexadécimal[^abc] correspond exactement à un caractère qui n'est PAS un a, un b ou un c,[^x-y] correspond exactement à un caractère qui n'est PAS dans l'intervalle allant de x à y
[[:character_class:]] correspond à un caractère qui appartient à la « character_class » nommée
lower -- lettres minusculesupper -- lettres majusculesalpha -- lettresdigit -- chiffresalnum -- lettres et chiffresspace -- espaces blancsblank -- espaces blancs horizontaux (espace et tabulation)xdigit -- chiffres hexadécimauxword -- caractères autorisés dans un identifiant (lettres, chiffres, tiret bas)punct -- ponctuationcntrl -- caractères de contrôlegraph -- caractères « visibles »print -- caractères visibles plus l'espace[[:alpha:]] est composée de [[:lower:][:upper:]]
[[:alnum:]] est composée de [[:alpha:][:digit:]]
[[:word:]] est composée de [[:alnum:]_]
[[:word:]] et [[:punct:]]
[[:graph:]] est composée de [[:alnum:][:punct:]]
[[:print:]] est composée de [[:graph:] ] -- uniquement l'espace (octal 040), et non les autres espaces blancs.On peut parcourir une liste de fichiers avec une boucle for
for file in *.csv; do
do_something_with "$file"
done
Note que la variable "$file" est entre guillemets.
Le motif glob peut renvoyer des fichiers dont le nom contient des espaces.
Il faut mettre la variable entre guillemets pour que la commande do_something_with reçoive exactement un argument : le nom du fichier.
Les motifs glob ne sont pas des expressions régulières.
.
?
.*
*
.* signifie « correspondre à un nom de fichier dont le premier caractère est un point littéral suivi de zéro ou plusieurs caractères quelconques »..+
?*
Bash fournit des motifs étendus plus proches des expressions régulières.
Il arrive parfois que l'on veuille empêcher l'expansion des noms de fichiers. La gestion des entrées utilisateur en est un exemple. Prenons cet extrait de code
read -p "Enter something: " user_input
echo $user_input
Si l'utilisateur saisit un astérisque (*), qu'est-ce qui sera affiché ?
La liste des fichiers du répertoire courant.
Ce n'est probablement pas ce que tu veux faire.
Un autre exemple consiste à passer des motifs en arguments à des programmes qui attendent des motifs.
Le programme tr sert souvent à manipuler du texte. Pour mettre en minuscules la valeur d'une variable, on peut faire ceci
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
affiche
my puppet
Une erreur fréquente consiste à oublier de mettre les arguments de tr entre guillemets : tr [:upper:] [:lower:]
Pourquoi est-ce un problème ?
Les motifs glob sans guillemets sont développés en la liste des fichiers qui correspondent.
Suppose que la personne qui utilise ton script a des fichiers nommés p, u et w dans le répertoire courant :
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
Cela provoque une erreur !
Le motif [:upper:] sans guillemets correspond à l'un des caractères :, u, p, e ou r.
Le motif [:lower:] sans guillemets correspond à l'un des caractères :, l, o, w, e ou r.
Bash fait correspondre les fichiers p et u au premier motif, et le fichier w au second, puis tr est invoqué ainsi
echo "$var" | tr p u w
Et ce n'est pas le bon nombre d'arguments pour tr.
Tu peux utiliser la commande set pour désactiver l'expansion des noms de fichiers.
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
La commande interne shopt possède plusieurs options qui contrôlent le fonctionnement de l'expansion des noms de fichiers.
En voici quelques-unes intéressantes :
shopt -s nocaseglob -- effectuer une correspondance insensible à la casseshopt -s extglob -- activer les motifs étendusshopt -s nullglob -- si aucun fichier ne correspond, remplacer le motif par rien.
Par défaut, le motif est laissé en place en tant que string littérale.