Voici une série de cas où echo $var
peut afficher une valeur différente de celle qui vient d'être affectée. Cela se produit indépendamment du fait que la valeur affectée soit "double guillemet", "simple guillemet" ou sans guillemets.
Comment faire en sorte que le shell définisse correctement ma variable?
astérisques
La sortie attendue est /* Foobar is free software */
, mais je reçois une liste de noms de fichiers:
$ var="/* Foobar is free software */"
$ echo $var
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...
crochets
La valeur attendue est [a-z]
, mais je reçois parfois une seule lettre!
$ var=[a-z]
$ echo $var
c
sauts de ligne (nouvelles lignes)
La valeur attendue est une liste de lignes séparées, mais toutes les valeurs sont sur une seule ligne!
$ cat file
foo
bar
baz
$ var=$(cat file)
$ echo $var
foo bar baz
espaces multiples
Je m'attendais à un en-tête de tableau soigneusement aligné, mais plusieurs espaces disparaissent ou sont réduits en un!
$ var=" title | count"
$ echo $var
title | count
Onglets
Je m'attendais à deux valeurs séparées par des tabulations, mais j'ai plutôt deux valeurs séparées par des espaces!
$ var=$'key\tvalue'
$ echo $var
key value
Dans tous les cas ci-dessus, la variable est correctement définie, mais pas correctement lue! La bonne façon consiste à utiliser des guillemets doubles lors du référencement :
echo "$var"
Cela donne la valeur attendue dans tous les exemples donnés. Toujours citer des références variables!
Pourquoi?
Lorsqu'une variable est non entre guillemets , elle:
Subissez division du champ où la valeur est divisée en plusieurs mots sur des espaces (par défaut):
Avant: /* Foobar is free software */
Après: /*
, Foobar
, is
, free
, software
, */
Chacun de ces mots sera soumis à extension du nom du chemin , où les motifs sont développés dans des fichiers correspondants:
Avant: /*
Après: /bin
, /boot
, /dev
, /etc
, /home
, ...
Enfin, tous les arguments sont passés à echo, ce qui les écrit séparés par des espaces simples , donnant
/bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
au lieu de la valeur de la variable.
Lorsque la variable est citée , cela va:
C'est pourquoi vous devriez toujours citer toutes les références de variable , sauf si vous avez spécifiquement besoin du fractionnement de Word et de l'expansion du chemin d'accès. Des outils tels que shellcheck sont là pour vous aider et vous avertiront des citations manquantes dans tous les cas ci-dessus.
Vous voudrez peut-être savoir pourquoi cela se produit. Ensemble avec la grande explication de cet autre type , trouvez une référence à Pourquoi mon script Shell s'étouffe-t-il avec des espaces ou d'autres caractères spéciaux? écrit par Gilles = dans nix et Linux :
Pourquoi dois-je écrire
"$foo"
? Que se passe-t-il sans les citations?
$foo
ne signifie pas “prend la valeur de la variablefoo
”. Cela signifie quelque chose de beaucoup plus complexe:
- Tout d'abord, prenons la valeur de la variable.
- Fractionnement des champs: traitez cette valeur comme une liste de champs séparés par des espaces et construisez la liste résultante. Par exemple, si la variable contient
foo * bar
, le résultat de cette étape est la liste à 3 élémentsfoo
,*
,bar
.- Génération de noms de fichiers: traitez chaque champ comme un glob, c’est-à-dire un motif générique, et remplacez-le par la liste des noms de fichiers correspondant à ce motif. Si le modèle ne correspond à aucun fichier, il n'est pas modifié. Dans notre exemple, cela donne la liste contenant
foo
, suivie de la liste des fichiers du répertoire en cours, et enfinbar
. Si le répertoire en cours est vide, le résultat estfoo
,*
,bar
.Notez que le résultat est une liste de chaînes. Il existe deux contextes dans la syntaxe du shell: le contexte de liste et le contexte de chaîne. La division de champ et la génération de nom de fichier ne se produisent que dans un contexte de liste, mais c'est la plupart du temps. Les guillemets doubles délimitent un contexte de chaîne: la chaîne entière entre guillemets est une chaîne unique, à ne pas scinder. (Exception:
"$@"
pour développer la liste des paramètres de position, par exemple"$@"
est équivalent à"$1" "$2" "$3"
s'il existe trois paramètres de position. Voir Quelle est la différence entre $ * et $ @? )Il en va de même pour la substitution de commande avec
$(foo)
ou avec`foo`
. Par ailleurs, n'utilisez pas`foo`
: ses règles de citation sont étranges et non portables, et tous les shells modernes prennent en charge$(foo)
, ce qui est absolument équivalent, à l'exception des règles de citation intuitives.La sortie de la substitution arithmétique subit également les mêmes développements, mais cela ne pose normalement pas de problème, car elle ne contient que des caractères non extensibles (en supposant que
IFS
ne contient pas de chiffres ni-
).Voir Quand une double citation est-elle nécessaire? pour plus de détails sur les cas où vous pouvez omettre les citations.
À moins que vous ne vouliez dire que tout cela soit rigoureux, souvenez-vous de toujours utiliser des guillemets doubles autour des substitutions de variables et de commandes. Faites attention: omettre les guillemets peut mener non seulement à des erreurs, mais à trous de sécurité .
En plus d'autres problèmes causés par l'absence de citation, -n
et -e
peuvent être utilisés par echo
comme arguments. (Seul le premier est légal selon la spécification POSIX pour echo
, mais plusieurs implémentations courantes violent la spécification et consomment -e
également).
Pour éviter cela, tilisez printf
au lieu de echo
lorsque les détails sont importants.
Ainsi:
$ vars="-e -n -a"
$ echo $vars # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a
Cependant, les citations correctes ne vous sauveront pas toujours lorsque vous utilisez echo
:
$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed
... alors que cela va vous sauver avec printf
:
$ vars="-n"
$ printf '%s\n' "$vars"
-n
double devis utilisateur pour obtenir la valeur exacte. comme ça:
echo "${var}"
et il lira votre valeur correctement.
echo $var
la sortie dépend fortement de la valeur de IFS
variable. Par défaut, il contient des caractères d'espace, de tabulation et de nouvelle ligne:
[ks@localhost ~]$ echo -n "$IFS" | cat -vte
^I$
Cela signifie que lorsque Shell effectue la division de champs (ou la division de Word), il utilise tous ces caractères comme séparateurs de Word. C'est ce qui se produit lorsque vous référencez une variable sans guillemets doubles pour qu'elle répercute son écho ($var
) - et donc la sortie attendue est modifiée.
Une façon d'éviter le fractionnement de Word (en plus des guillemets) consiste à définir IFS
sur null. Voir http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :
Si la valeur de IFS est nulle, aucune division de champ ne doit être effectuée.
Définir sur null signifie définir sur valeur vide:
IFS=
Tester:
[ks@localhost ~]$ echo -n "$IFS" | cat -vte
^I$
[ks@localhost ~]$ var=$'key\nvalue'
[ks@localhost ~]$ echo $var
key value
[ks@localhost ~]$ IFS=
[ks@localhost ~]$ echo $var
key
value
[ks@localhost ~]$