Linux sans magie #31 — Variables Bash et guillemets : comprendre $VAR, "..." et '...'
Comprendre les variables Bash, export, l’expansion $VAR et ${VAR}, les guillemets simples et doubles, et pourquoi citer ses variables évite des erreurs.
Dans le #30, nous avons vu qu’une commande retourne un statut et que Bash peut utiliser ce statut pour décider de la suite.
Pour écrire des commandes plus souples, il faut maintenant savoir conserver une valeur, la réutiliser et contrôler la manière dont Bash l’interprète.
C’est le rôle des variables et du quoting.
Les erreurs classiques arrivent vite :
nom='rapport final.txt'
printf '<%s>\n' $nom
La variable contient une seule chaîne, mais l’expansion non citée peut être découpée en plusieurs mots.
Dans ce #31, nous allons comprendre les variables Bash, export, $VAR, ${VAR}, les guillemets simples et doubles, puis la substitution de commande $(...).
Une variable Bash associe un nom à une valeur
Une affectation simple s’écrit sans espace autour du signe = :
nom='rapport final.txt'
Pour afficher sa valeur :
printf '%s\n' "$nom"
Résultat attendu :
rapport final.txt
La syntaxe suivante n’est pas équivalente :
nom = 'rapport final.txt'
Bash n’y voit plus une affectation simple. Les espaces changent la structure de la commande.
Dans cet article, nous utiliserons des noms de variables simples composés de lettres, chiffres et underscores, sans commencer par un chiffre.
$VAR et ${VAR} : développer la valeur
Lorsque Bash rencontre :
printf '%s\n' "$nom"
il effectue une expansion de paramètre : $nom est remplacé par la valeur associée à nom.
Les accolades rendent la frontière du nom explicite.
Exemple :
service='web'
printf '%s\n' "${service}_backup"
Résultat :
web_backup
Sans accolades :
printf '%s\n' "$service_backup"
Bash cherche une variable nommée service_backup, pas la variable service suivie du texte _backup.
Les accolades ne sont donc pas obligatoires partout, mais elles deviennent utiles dès qu’un caractère voisin pourrait être interprété comme faisant partie du nom.
Guillemets simples : conserver le texte littéralement
Les guillemets simples empêchent Bash d’interpréter les caractères qu’ils contiennent comme des expansions.
Exemple :
nom='serveur'
printf '%s\n' '$nom'
Résultat :
$nom
La chaîne $nom est affichée telle quelle.
Même logique avec une substitution de commande :
printf '%s\n' '$(date)'
Bash n’exécute pas date dans cette chaîne : il affiche les caractères littéralement.
Les guillemets simples sont donc adaptés lorsque le contenu doit rester textuel et ne doit pas subir d’expansion.
Guillemets doubles : permettre les expansions tout en conservant la valeur groupée
Les guillemets doubles ont un comportement différent.
Exemple :
nom='serveur'
printf '%s\n' "$nom"
Résultat :
serveur
L’expansion de $nom est effectuée.
Les guillemets doubles autorisent notamment :
- l’expansion de paramètres comme
$nomou${nom}; - la substitution de commande
$(...); - l’expansion arithmétique
$((...)).
Mais ils conservent la valeur résultante comme un seul mot dans les cas ordinaires que nous étudions ici.
C’est pour cette raison qu’une règle opérationnelle très utile est :
"$variable"
lorsque vous voulez transmettre le contenu d’une variable comme un argument unique.
Pourquoi une variable non citée peut être découpée
Créons une valeur contenant un espace :
fichier='rapport final.txt'
Observons d’abord l’expansion non citée :
printf '<%s>\n' $fichier
Une sortie typique est :
<rapport>
<final.txt>
La variable contenait pourtant une seule chaîne.
Après une expansion non citée, Bash peut effectuer du word splitting. L’espace contenu dans la valeur devient alors un séparateur.
Avec des guillemets doubles :
printf '<%s>\n' "$fichier"
Résultat :
<rapport final.txt>
Cette fois, printf reçoit un seul argument provenant de la variable.
Une expansion non citée peut aussi déclencher le filename expansion
Le risque ne se limite pas aux espaces.
Préparons un petit lab :
LAB_DIR="$(mktemp -d)"
printf 'Lab : %s\n' "$LAB_DIR"
cd "$LAB_DIR"
touch 'alpha.txt'
touch 'beta.txt'
touch 'notes.log'
Définissons une variable contenant un motif :
motif='*.txt'
Puis :
printf '<%s>\n' $motif
Dans ce répertoire, Bash peut développer *.txt en noms de fichiers :
<alpha.txt>
<beta.txt>
Avec des guillemets doubles :
printf '<%s>\n' "$motif"
le motif reste un seul argument littéral :
<*.txt>
Ce comportement explique pourquoi une expansion non citée peut produire des résultats très différents selon le contenu de la variable et les fichiers présents dans le répertoire courant.
Variable shell et variable d’environnement
Une variable définie dans Bash existe d’abord dans le shell courant.
Exemple :
message='bonjour depuis le shell parent'
Un processus enfant ne reçoit pas automatiquement toutes les variables internes du shell.
Pour marquer une variable afin qu’elle soit incluse dans l’environnement des commandes exécutées ensuite, Bash fournit export.
Exemple :
export message
Nous pouvons alors démarrer un Bash enfant :
bash -c 'printf "%s\n" "$message"'
Résultat attendu :
bonjour depuis le shell parent
On peut aussi affecter et exporter en une seule commande :
export environnement='lab'
Puis vérifier :
bash -c 'printf "environnement=%s\n" "$environnement"'
export ne signifie donc pas « sauvegarder globalement une variable dans Linux ». Il prépare sa transmission dans l’environnement des processus enfants lancés depuis ce shell.
Vérifier la différence avec un shell enfant
Créons une variable non exportée. Pour rendre le lab reproductible même si une variable locale_shell existait déjà dans votre environnement, supprimons d’abord toute définition précédente :
unset locale_shell
locale_shell='visible seulement dans ce Bash'
Dans le shell courant :
printf '%s\n' "$locale_shell"
La valeur apparaît.
Dans un Bash enfant :
bash -c 'printf "locale_shell=<%s>\n" "$locale_shell"'
La valeur est vide si la variable n’a pas été exportée.
Exportons-la :
export locale_shell
Puis recommençons :
bash -c 'printf "locale_shell=<%s>\n" "$locale_shell"'
Le Bash enfant reçoit maintenant la valeur dans son environnement.
Ce lab permet d’observer directement la frontière entre une variable du shell courant et une variable exportée vers les commandes enfants.
Substitution de commande avec $(...)
La syntaxe $(commande) exécute une commande et remplace l’expression par sa sortie standard.
Exemple :
repertoire="$(pwd)"
printf 'Répertoire : %s\n' "$repertoire"
Bash exécute pwd, récupère sa sortie et affecte le résultat à repertoire.
Autre exemple :
nombre="$(printf '%s\n' alpha beta gamma | wc -l)"
printf 'Nombre : %s\n' "$nombre"
Résultat attendu :
Nombre : 3
Bash supprime les retours à la ligne finaux de la sortie capturée par une substitution de commande.
Comme pour les variables, il est généralement préférable de citer la substitution lorsqu’elle doit former un seul argument :
printf '<%s>\n' "$(pwd)"
Ne pas utiliser les guillemets comme décoration
Ces trois lignes n’ont pas le même sens :
printf '%s\n' '$HOME'
printf '%s\n' "$HOME"
printf '%s\n' $HOME
La première affiche littéralement :
$HOME
La deuxième développe la variable HOME tout en conservant son résultat comme un argument unique.
La troisième développe également HOME, mais laisse ensuite le résultat exposé aux traitements associés à une expansion non citée.
Même si une valeur simple semble fonctionner sans guillemets, le comportement peut changer dès que son contenu comporte espaces ou caractères spéciaux pour les expansions du shell.
Construire une commande de façon prévisible
Prenons une variable de fichier :
fichier='rapport final.txt'
Cette forme est fragile :
printf 'Fichier : %s\n' $fichier
Cette forme préserve la valeur :
printf 'Fichier : %s\n' "$fichier"
Même principe avec une commande :
repertoire="$(pwd)"
printf 'Répertoire courant : %s\n' "$repertoire"
Le but n’est pas d’ajouter des guillemets au hasard, mais de décider explicitement si une expansion doit produire un argument unique ou si l’on souhaite réellement les mécanismes de découpage et de développement de motifs.
Bonnes pratiques
Citez les expansions de variables lorsqu’elles représentent une valeur qui doit rester un argument unique :
"$variable"
Utilisez ${variable} lorsque la frontière du nom doit être explicite :
"${service}_backup"
Utilisez les guillemets simples lorsque vous voulez du texte littéral sans expansion :
'$HOME'
Utilisez les guillemets doubles lorsque vous voulez permettre les expansions tout en préservant le regroupement de la valeur :
"$HOME"
N’exportez que les variables qui doivent réellement être transmises aux commandes enfants.
Pour observer une valeur sans ambiguïté pendant un diagnostic Bash, printf est généralement plus prévisible que des constructions complexes basées sur plusieurs expansions implicites.
Sécurité
Une variable peut contenir des données inattendues : espaces, motifs comme *, chemins ou valeurs provenant d’une entrée externe.
Une expansion non citée peut alors modifier le nombre d’arguments transmis à une commande ou déclencher un développement de noms de fichiers.
Citer une variable ne transforme pas son contenu en donnée « sûre » pour tous les usages : cela évite certaines réinterprétations par le shell, mais la commande appelée doit encore valider les valeurs qu’elle reçoit lorsque le contexte l’exige.
Il faut aussi éviter de placer des secrets dans des exemples ou de supposer qu’une variable d’environnement constitue un stockage secret. Cet article traite uniquement du mécanisme de transmission des variables, pas d’une stratégie de gestion des secrets.
Coûts
Le lab s’exécute localement et n’utilise aucun service cloud ni ressource facturable.
Limites et points de vigilance
Cet article ne couvre pas encore :
- les paramètres positionnels comme
$1ou$@; - les tableaux Bash ;
- les expansions avancées comme
${var:-valeur}; readet la saisie utilisateur ;- les conditions
[[ ... ]]; - les fichiers de configuration du shell comme
.bashrc; - la gestion des secrets dans un système réel.
L’objectif reste limité aux variables, à leur expansion, au quoting, à export et à $(...).
Rollback ou nettoyage
Le lab a créé un répertoire temporaire avec trois petits fichiers.
Avant la suppression, vérifiez sa valeur :
printf '%s\n' "$LAB_DIR"
Puis quittez le répertoire et supprimez uniquement ce lab :
cd /
rm -rf -- "$LAB_DIR"
Les variables créées dans le shell courant disparaîtront à la fermeture de ce shell. Vous pouvez aussi les supprimer explicitement avec unset si vous souhaitez nettoyer la session :
unset fichier motif message environnement locale_shell repertoire nombre
Références officielles
- Bash Reference Manual — Shell Parameters — GNU Project
- Bash Reference Manual — Quoting — GNU Project
- Bash Reference Manual — Double Quotes — GNU Project
- Bash Reference Manual — Shell Parameter Expansion — GNU Project
- Bash Reference Manual — Command Substitution — GNU Project
- Bash Reference Manual — Bourne Shell Builtins — GNU Project
Conclusion
Les variables Bash deviennent prévisibles dès que l’on sépare trois questions :
- quelle valeur est stockée ;
- quelles expansions Bash doit effectuer ;
- comment le résultat doit être transmis à la commande suivante.
Retenez surtout la différence entre '$VAR', "$VAR" et $VAR. Les guillemets ne sont pas décoratifs : ils déterminent une partie essentielle de la manière dont Bash construit les arguments d’une commande.
