Citations dans $ (substitution de commande) dans Bash

126

Dans mon environnement Bash, j'utilise des variables contenant des espaces et j'utilise ces variables dans la substitution de commandes. Malheureusement, je ne trouve pas la réponse sur SE.

Quelle est la bonne façon de citer mes variables? Et comment dois-je le faire si ceux-ci sont imbriqués?

DIRNAME=$(dirname "$FILE")

ou est-ce que je cite en dehors de la substitution?

DIRNAME="$(dirname $FILE)"

ou les deux?

DIRNAME="$(dirname "$FILE")"

ou est-ce que j'utilise des anti-tiques?

DIRNAME=`dirname "$FILE"`

Quel est le bon moyen de le faire? Et comment puis-je facilement vérifier si les citations sont définies correctement?

Cousin cocaine
la source
1
C'est une bonne question, mais étant donné tous les problèmes liés aux flans incorporés, pourquoi voudriez-vous vous compliquer la vie en les utilisant exprès?
Joe
3
@ Joe, avec des blancs incorporés, vous voulez dire un espace dans les noms de fichiers? Personnellement, je ne les utilise pas souvent, mais je travaille avec des répertoires et des fichiers appartenant à d’autres peuples dont je ne suis pas certain s’ils contiennent des espaces. En outre, je pense qu'il est préférable de bien faire les choses immédiatement afin de ne pas m'inquiéter à l'avenir.
CousinCocaine
4
Oui. Qu'allons-nous faire avec ces "autres personnes"? <G>
Joe

Réponses:

188

En ordre du pire au meilleur:

  • DIRNAME="$(dirname $FILE)" ne fera pas ce que vous voulez si $FILEcontient des espaces ou des caractères globants \[?*.
  • DIRNAME=`dirname "$FILE"`est techniquement correct, mais les backticks ne sont pas recommandés pour l'expansion des commandes en raison de la complexité supplémentaire liée à leur imbrication.
  • DIRNAME=$(dirname "$FILE")est correct, mais seulement parce que ceci est une cession . Si vous utilisez la substitution de commande dans un autre contexte, tel que export DIRNAME=$(dirname "$FILE")ou du $(dirname "$FILE"), l'absence de guillemets causera des problèmes si le résultat de l'extension contient des espaces ou des caractères globaux.
  • DIRNAME="$(dirname "$FILE")"est la manière recommandée. Vous pouvez remplacer DIRNAME=par une commande et un espace sans rien changer d'autre et dirnamerecevoir la chaîne correcte.

Pour améliorer encore plus:

  • DIRNAME="$(dirname -- "$FILE")"fonctionne si $FILEcommence par un tiret.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"fonctionne même si $FILEse termine par une nouvelle ligne, car elle $()découpe les lignes à la fin de la sortie et génère dirnameune nouvelle ligne après le résultat. Sheesh dirname, pourquoi faut-il être différent?

Vous pouvez imbriquer des extensions de commandes autant que vous le souhaitez. Avec $()vous, créez toujours un nouveau contexte de citation pour pouvoir faire les choses suivantes:

foo "$(bar "$(baz "$(ban "bla")")")"

Vous ne pas voulez essayer cela avec des accents graves.

l0b0
la source
3
Réponse claire à ma question. Quand je niche ces variables, puis-je continuer à citer comme je le fais maintenant?
CousinCocaine
3
Existe-t-il une référence / ressource détaillant le comportement des guillemets dans une sous-commande, entre guillemets?
AmadeusDrZaius
12
@AmadeusDrZaius "Avec $()vous, créez toujours un nouveau contexte de citation", c'est donc comme en dehors des guillemets extérieurs. Il n'y a rien de plus, à ma connaissance.
l0b0
4
@ l0b0 Merci, j'ai trouvé votre explication très claire. Je me demandais simplement si cela figurait aussi dans un manuel. Je l'ai trouvé (non seulement officiellement) à wooledge . Je suppose que si vous lisez sur ordre de substitution attentivement , vous pouvez déduire ce fait en conséquence.
AmadeusDrZaius
1
Les guillemets imbriqués sont donc acceptables, mais ils nous déconcertent car la plupart des schémas de coloration de la syntaxe ne détectent pas les circonstances spéciales. Soigné.
Luke Davis
19

Vous pouvez toujours montrer les effets de la cotation de variable avec printf.

Fractionnement des mots effectué le var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 cité, donc pas de mots qui se séparent:

$ printf '[%s]\n' "$var1"
[hello     world]

Fractionnement du mot à l' var1intérieur $(), équivalent à echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

Pas de mots qui se séparent var1, pas de problème pour ne pas citer le $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Le mot se fractionnant à var1nouveau:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Citer les deux, le moyen le plus simple d’être sûr.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Problème globulaire

Ne pas citer de variable peut également conduire à une expansion globale de son contenu:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Notez que cela se produit après que la variable est développée uniquement. Il n'est pas nécessaire de citer un glob lors de l'affectation:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Utilisez set -fpour désactiver ce comportement:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

Et set +fpour le réactiver:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
Graeme
la source
8
Les gens ont tendance à oublier que la division des mots n'est pas le seul problème. Vous voudrez peut-être changer votre exemple var1='hello * world'pour illustrer également le problème.
Stéphane Chazelas
10

Ajout à la réponse acceptée:

Bien que je sois généralement d’accord avec la réponse de @ l0b0 ici, je soupçonne que le placement de coudes nus dans la liste «pire au meilleur» est au moins en partie le résultat de l’hypothèse qui $(...)est disponible partout. Je me rends compte que la question spécifie Bash, mais il y a de nombreuses fois où Bash se veut méchant /bin/sh, ce qui n'est peut-être pas toujours le plein shell Bourne Again.

En particulier, le simple shell Bourne ne saura pas quoi faire $(...). Par conséquent, les scripts qui prétendent être compatibles avec ce dernier (par exemple, via une #!/bin/shligne shebang) risquent de se comporter de manière erronée s’ils sont gérés par le "réel" /bin/sh- c’est de Un intérêt particulier lorsque, par exemple, vous produisez des scripts d’initialisation ou des packages de pré-et post-scripts, vous pouvez en placer un dans un endroit surprenant lors de l’installation d’un système de base.

Si vous envisagez de faire cela avec cette variable, l'imbrication est probablement moins un problème que l'exécution du script de manière prévisible. Quand c'est assez simple et que la portabilité est une préoccupation, même si je m'attends à ce que le script s'exécute généralement sur des systèmes où /bin/shest Bash, j'ai souvent tendance à utiliser des backticks pour cette raison, avec plusieurs affectations au lieu d'imbrication.

Cela dit, l’omniprésence des coques qui implémentent $(...)(Bash, Dash, et autres) nous laisse un bon endroit pour nous en tenir à la syntaxe plus jolie, plus facile à imbriquer, et plus récemment préférée de POSIX, dans la plupart des cas. toutes les raisons @ l0b0 mentionne.

A part: cela est apparu occasionnellement sur StackOverflow, aussi -

rsandwick3
la source
// Excellente réponse. J'ai déjà eu des problèmes de compatibilité avec / bin / sh par le passé. Avez-vous des conseils sur la façon de traiter son problème en utilisant des méthodes rétrocompatibles?
Nathan Basanese
Selon mon expérience, il peut arriver que vous deviez changer les backticks en dollars paren fermés, et jamais une seule fois (cela a été nécessaire, pour préciser) de changer les dollars paren enfermés dans des backticks. Je n'écris que des backticks dans mon code javascript et non dans le code shell.
Steven Lu