Parcours
/
Bash
Bash
/
Programme
/
L'importance du _quoting_
L'

L'importance du _quoting_ en Bash

{one: "%{count} exercice", many: "%{count} d'exercices", other: "%{count} exercices"}

À propos de L'importance du _quoting_

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.

Mise entre guillemets

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

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

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.

L'importance de la mise entre guillemets

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.

Découpage des mots

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
Désactiver le découpage des mots

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.

Expansion des noms de fichiers

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.

    Advanced
    Il y a beaucoup de détails à propos des ensembles de caractères. Clique ici pour les afficher.
    • [abc] correspond exactement à un caractère qui est soit un a, soit un b, soit un c.
      • pour inclure un crochet fermant littéral dans l'expression, place-le comme premier caractère après le crochet ouvrant (après un ^ facultatif)
        • [][] est un ensemble de caractères qui correspond soit à un crochet fermant, soit à un crochet ouvrant.
        • ou échappe-le avec un antislash : [[\]]
    • [x-y] correspond exactement à un caractère qui se trouve dans l'intervalle allant de x à y
      • pour inclure un tiret littéral dans un ensemble de caractères, il doit être soit le premier caractère après le crochet ouvrant (après un ^ 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
        • ou échappe-le avec un antislash : [x\-y]
      • on peut spécifier plusieurs intervalles, par exemple [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
      • les classes de caractères disponibles sont :
        • lower -- lettres minuscules
        • upper -- lettres majuscules
        • alpha -- lettres
        • digit -- chiffres
        • alnum -- lettres et chiffres
        • space -- espaces blancs
        • blank -- espaces blancs horizontaux (espace et tabulation)
        • xdigit -- chiffres hexadécimaux
        • word -- caractères autorisés dans un identifiant (lettres, chiffres, tiret bas)
        • punct -- ponctuation
        • cntrl -- caractères de contrôle
        • graph -- caractères « visibles »
        • print -- caractères visibles plus l'espace
      • on peut les combiner :
        • la classe [[:alpha:]] est composée de [[:lower:][:upper:]]
        • la classe [[:alnum:]] est composée de [[:alpha:][:digit:]]
        • la classe [[:word:]] est composée de [[:alnum:]_]
        • le tiret bas (octal 137) est à la fois [[:word:]] et [[:punct:]]
        • la classe [[:graph:]] est composée de [[:alnum:][:punct:]]
        • la classe [[:print:]] est composée de [[:graph:] ] -- uniquement l'espace (octal 040), et non les autres espaces blancs.
Parcourir une liste de fichiers

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

Les motifs glob ne sont pas des expressions régulières.

  • correspondre exactement à un caractère
    • regex .
    • glob ?
  • correspondre à zéro ou plusieurs caractères
    • regex .*
    • glob *
      • le motif glob .* 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 ».
  • correspondre à un ou plusieurs caractères
    • regex .+
    • glob ?*
  • les ensembles de caractères des regex et ceux des motifs glob se comportent de la même façon.
Advanced

Bash fournit des motifs étendus plus proches des expressions régulières.

Mise entre guillemets et expansion des noms de fichiers

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:]

Caution

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.

Désactiver l'expansion des noms de fichiers

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
Contrôler l'expansion des noms de fichiers

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 casse
  • shopt -s extglob -- activer les motifs étendus
  • shopt -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.
Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet