web-dev-qa-db-fra.com

Citer dans $ (substitution de commande) dans Bash

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 dois-je citer en dehors de la substitution?

DIRNAME="$(dirname $FILE)"

ou les deux?

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

ou dois-je utiliser des back-ticks?

DIRNAME=`dirname "$FILE"`

Quel est le bon moyen de le faire? Et comment puis-je facilement vérifier si les devis sont corrects?

136
CousinCocaine

Du pire au meilleur:

  • DIRNAME="$(dirname $FILE)"ne fera pas ce que vous voulez si $FILE contient des espaces ou des caractères globaux \[?*.
  • DIRNAME=`dirname "$FILE"` Est techniquement correct, mais les raccourcis ne sont pas recommandés pour l'extension des commandes en raison de la complexité supplémentaire lors de leur imbrication.
  • DIRNAME=$(dirname "$FILE") est correct, mais niquement parce qu'il s'agit d'une affectation . Si vous utilisez la substitution de commandes dans tout 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'expansion contient des espaces ou des caractères globales.
  • DIRNAME="$(dirname "$FILE")" est la méthode recommandée. Vous pouvez remplacer DIRNAME= Par une commande et un espace sans rien changer d'autre, et dirname reçoit la chaîne correcte.

Pour améliorer encore plus:

  • DIRNAME="$(dirname -- "$FILE")" fonctionne si $FILE commence par un tiret.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}" fonctionne même si $FILE se termine par une nouvelle ligne, puisque $() coupe les nouvelles lignes à la fin de la sortie et dirname sort une nouvelle ligne après le résultat . Sheesh dirname, pourquoi tu dois être différent?

Vous pouvez imbriquer des extensions de commande autant que vous le souhaitez. Avec $() vous créez toujours un nouveau contexte de citation, vous pouvez donc faire des choses comme ceci:

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

Vous ne voulez pas essayer cela avec des astuces.

196
l0b0

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

Séparation de mots effectuée sur var1:

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

var1 Cité, donc pas de division de mots:

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

Découpage de mots sur var1 Dans $(), équivalent à echo "hello" "world":

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

Pas de division de mot sur var1, Pas de problème pour ne pas citer la $():

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

Découpage de mots sur var1 À nouveau:

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

Citant les deux, le moyen le plus simple d'être sûr.

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

Problème global

Ne pas citer une variable peut également entraîner 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 une fois 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 -f Pour désactiver ce comportement:

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

Et set +f Pour le réactiver:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
19
Graeme

Ajout à la réponse acceptée:

Bien que je sois généralement d'accord avec la réponse de @ l0b ici, je soupçonne que le placement de backticks nus dans la liste "du pire au meilleur" est au moins en partie le résultat de l'hypothèse que $(...) est disponible partout. Je me rends compte que la question spécifie Bash, mais il arrive souvent que Bash signifie /bin/sh, Ce qui n'est pas toujours le Bourne Again Shell complet.

En particulier, le Bourne Shell simple ne saura pas quoi faire avec $(...), donc les scripts qui prétendent être compatibles avec lui (par exemple via une ligne Shebang #!/bin/sh) Se comporteront probablement mal si ils sont en fait gérés par le "vrai" /bin/sh - ceci est particulièrement intéressant lorsque, par exemple, la production de scripts d'initialisation ou l'empaquetage de pré- et post-scripts, et peut en atterrir un à un endroit surprenant lors de l'installation d'un système de base.

Si tout cela ressemble à quelque chose que vous envisagez de faire avec cette variable, l'imbrication est probablement moins préoccupante que l'exécution réelle et prévisible du script. Quand c'est un cas assez simple et que la portabilité est un problème, même si je m'attends à ce que le script s'exécute généralement sur des systèmes où /bin/sh Est Bash , J'ai souvent tendance à utiliser des backticks pour cette raison, avec plusieurs affectations au lieu de s'emboîter.

Cela dit, l'omniprésence des shells qui implémentent $(...) (Bash, Dash, et al.), Nous laisse dans un bon endroit pour rester avec le plus joli, plus facile à imbriquer, et plus récemment préféré Syntaxe POSIX dans la plupart des cas, pour toutes les raisons mentionnées par @ l0b0.

Mis à part: cela est apparu parfois sur StackOverflow aussi -

12
rsandwick3