Voici une série de cas où echo $var
peut afficher une valeur différente de celle qui vient d'être attribuée. Cela se produit indépendamment du fait que la valeur assignée ait été "guillemets doubles", "guillemets simples" ou non.
Comment faire pour que le shell définisse correctement ma variable?
Astérisques
La sortie attendue est /* Foobar is free software */
, mais à la place, j'obtiens 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 parfois je reçois une seule lettre à la place!
$ var=[a-z]
$ echo $var
c
Sauts de ligne (nouvelles lignes)
La valeur attendue est une liste de lignes séparées, mais à la place 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 à la place, plusieurs espaces disparaissent ou sont réduits en un seul!
$ var=" title | count"
$ echo $var
title | count
Onglets
Je m'attendais à deux valeurs séparées par des tabulations, mais à la place, j'obtiens deux valeurs séparées par des espaces!
$ var=$'key\tvalue'
$ echo $var
key value
var=$(cat file)
va bien, maisecho "$var"
c'est nécessaire.Réponses:
Dans tous les cas ci-dessus, la variable est correctement définie, mais pas correctement lue! La bonne façon est d' utiliser des guillemets doubles lors du référencement :
Cela donne la valeur attendue dans tous les exemples donnés. Citez toujours des références variables!
Pourquoi?
Lorsqu'une variable n'est pas entre guillemets , elle:
Faites l'objet d' un fractionnement de champ où la valeur est divisée en plusieurs mots sur un espace blanc (par défaut):
Avant:
/* Foobar is free software */
Après:
/*
,Foobar
,is
,free
,software
,*/
Chacun de ces mots subira une extension de chemin , où les modèles sont développés dans des fichiers correspondants:
Avant:
/*
Après:
/bin
,/boot
,/dev
,/etc
,/home
, ...Enfin, tous les arguments sont passés à echo, qui les écrit séparés par des espaces simples , donnant
au lieu de la valeur de la variable.
Lorsque la variable est citée, elle:
C'est pourquoi vous devez toujours citer toutes les références de variables , sauf si vous avez spécifiquement besoin de séparer les mots et de développer les chemins. Des outils comme shellcheck sont là pour vous aider et vous avertiront des guillemets manquants dans tous les cas ci-dessus.
la source
$(..)
supprime les sauts de ligne de fin. Vous pouvez utiliservar=$(cat file; printf x); var="${var%x}"
pour contourner ce problème.Vous voudrez peut-être savoir pourquoi cela se produit. Avec la bonne explication de cet autre gars , trouvez une référence de Pourquoi mon script shell s'étouffe-t-il avec des espaces ou d'autres caractères spéciaux? écrit par Gilles sous Unix et Linux :
la source
En plus d'autres problèmes causés par l'échec de la citation,
-n
et-e
peuvent être utilisésecho
comme arguments. (Seul le premier est légal selon la spécification POSIX pourecho
, mais plusieurs implémentations courantes violent la spécification et consomment-e
également).Pour éviter cela, utilisez
printf
plutôt queecho
lorsque les détails comptent.Donc:
Cependant, une citation correcte ne vous sauvera pas toujours lorsque vous utilisez
echo
:... alors que cela vous fera économiser avec
printf
:la source
-e
/-n
/ backslash n'apparaît pas?" Nous pouvons ajouter des liens à partir d'ici, le cas échéant.-n
aussi ?-e
. La norme pourecho
ne spécifie pas la sortie lorsque son premier argument est-n
, rendant toute sortie possible légale dans ce cas; il n'y a pas de telle disposition pour-e
.guillemet double de l'utilisateur pour obtenir la valeur exacte. comme ça:
et il lira votre valeur correctement.
la source
echo $var
la sortie dépend fortement de la valeur de laIFS
variable. Par défaut, il contient des caractères d'espace, de tabulation et de nouvelle ligne:Cela signifie que lorsque le shell effectue le fractionnement de champ (ou le fractionnement de mots), il utilise tous ces caractères comme séparateurs de mots. C'est ce qui se passe lors du référencement d'une variable sans guillemets doubles pour la faire écho (
$var
) et ainsi la sortie attendue est modifiée.Une façon d'éviter le fractionnement de mot (en plus d'utiliser des guillemets doubles) est de définir la valeur
IFS
null. Voir http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :Définir sur null signifie définir une valeur vide:
Tester:
la source
set -f
empêcher le globbingIFS
la valeur null,echo $var
sera étendu àecho '/* Foobar is free software */'
et l'expansion de chemin n'est pas effectuée à l'intérieur de chaînes entre guillemets simples.mkdir "/this thing called Foobar is free software etc/"
voyez qu'il s'agrandit encore. C'est évidemment plus pratique pour l'[a-z]
exemple.[a-z]
exemple.La réponse de ks1322 m'a aidé à identifier le problème lors de l'utilisation
docker-compose exec
:Si vous omettez l'
-T
indicateur,docker-compose exec
ajoutez un caractère spécial qui rompt la sortie, nous voyons à lab
place de1b
:Avec
-T
indicateur,docker-compose exec
fonctionne comme prévu:la source
En plus de mettre la variable entre guillemets, on pourrait également traduire la sortie de la variable en utilisant
tr
et en convertissant les espaces en nouvelles lignes.Bien que ce soit un peu plus compliqué, cela ajoute plus de diversité à la sortie car vous pouvez remplacer n'importe quel caractère comme séparateur entre les variables du tableau.
la source
tr
l'inverse pour créer des tableaux à partir de fichiers texte.tr
nécessaire de créer correctement / correctement un tableau à partir d'un fichier texte - vous pouvez spécifier le séparateur de votre choix en définissant IFS. Par exemple:IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')
fonctionne depuis bash 3.2 (la version la plus ancienne largement diffusée), et définit correctement l'état de sortie sur false si votrecat
échec. Et si vous vouliez, disons, des tabulations à la place des nouvelles lignes, vous remplaceriez simplement le$'\n'
par$'\t'
.arrayname=( $( cat file | tr '\n' ' ' ) )
, alors c'est cassé sur plusieurs couches: cela regroupe vos résultats (donc un*
se transforme en une liste de fichiers dans le répertoire actuel), et cela fonctionnerait aussi bien sans letr
( ou lecat
, d'ailleurs; on pourrait simplement utiliserarrayname=$( $(<file) )
et il serait cassé de la même manière, mais de manière moins inefficace).