Quelle est la bonne façon de citer $ (commande $ arg)?

17

Il est grand temps de résoudre cette énigme qui me dérange depuis des années ...

Je l'ai rencontré de temps en temps et j'ai pensé que c'était la voie à suivre:

$(comm "$(arg)")

Et j'ai pensé que mon point de vue était fortement soutenu par l'expérience. Mais je n'en suis plus si sûr. Shellcheck ne peut pas non plus se décider. C'est à la fois:

"$(dirname $0)"/stop.bash
           ^-- SC2086: Double quote to prevent globbing and word splitting.

Et:

$(dirname "$0")/stop.bash
^-- SC2046: Quote this to prevent word splitting.

Quelle est la logique derrière?

(Il s'agit de la version 0.4.4 de Shellcheck, btw.)

Tomasz
la source
1
Citez toutes les substitutions.
Ignacio Vazquez-Abrams
3
Juste comme ça "$(dirname "$0")"/stop.bash:? Cela semble fonctionner ... Quelle est l'histoire?
Tomasz
1
bash est assez intelligent pour savoir quand le contexte a changé.
Ignacio Vazquez-Abrams
1
Pensez-y comme ceci:: shell_level_1 $(shell_level_2) shell_level_1lorsque vous êtes à l'intérieur du, $(....)vous entrez un "sous-niveau" de shell, MAIS vous pouvez l'écrire comme si vous étiez au niveau primaire (c'est-à-dire que vous pouvez directement écrire "au lieu de \", etc.). ex: touch "/tmp/a file" ; echo "its size is: $(find "/tmp/a file" -ls | awk '{print $5}) ..." : if you used backticks you'd have to trouver \ "/ tmp / un fichier \" `et print \$5. Avec $(...)pas besoin: les adapte shell au nouveau niveau et vous pouvez écrire directement comme si votre interprète est maintenant à ce niveau aussi.
Olivier Dulac
"Shellcheck ne peut pas non plus se décider." - Les deux pistes ont deux messages différents et pointent à des endroits différents. Il veut les deux.
Izkata

Réponses:

45

Vous devez utiliser "$(somecmd "$file")".

Sans les guillemets, un chemin avec un espace sera divisé dans l'argument to somecmdet ciblera le mauvais fichier. Vous avez donc besoin de citations à l'intérieur.

Tous les espaces dans la sortie de somecmdprovoqueront également le fractionnement, vous avez donc besoin de guillemets à l'extérieur de la substitution de commande entière.

Les citations à l'intérieur de la substitution de commande n'ont aucun effet sur les citations à l'extérieur. Le manuel de référence de Bash n'est pas trop clair à ce sujet, mais BashGuide le mentionne explicitement . Le texte dans POSIX l' exige également, car "tout script shell valide" est autorisé à l'intérieur $(...):

Avec le $(command)formulaire, tous les caractères suivant la parenthèse ouvrante à la parenthèse fermante correspondante constituent la commande. Tout script shell valide peut être utilisé pour la commande, à l'exception d'un script composé uniquement de redirections qui produit des résultats non spécifiés.


Exemple:

$ file="./space here/foo"

une. Pas de devis, dirnametraite les deux ./spaceet here/foo:

$ printf "<%s>\n" $(dirname $file)
<.>
<here>

b. Citations à l'intérieur, dirnameprocessus ./space here/foo, dons ./space here, divisés en deux:

$ printf "<%s>\n" $(dirname "$file")
<./space>
<here>

c. Les citations à l'extérieur dirnametraitent les deux ./spaceet les here/foosorties sur des lignes distinctes, mais maintenant les deux lignes forment un seul argument :

$ printf "<%s>\n" "$(dirname $file)"
<.
here>

ré. Citations à l'intérieur et à l'extérieur, cela donne la bonne réponse:

$ printf "<%s>\n" "$(dirname "$file")"
<./space here>

(cela aurait peut-être été plus simple si dirnameseulement traité le premier argument, mais cela ne montrerait pas la différence entre les cas a et c.)

Notez qu'avec dirname(et éventuellement d'autres), vous devez également ajouter --, pour éviter que le nom de fichier ne soit pris en option au cas où il commencerait par un tiret, alors utilisez "$(dirname -- "$file")".

ilkkachu
la source
16
Remarque: En général, les guillemets ne sont pas imbriqués dans bash; mais dans ce cas, le $( )crée un nouveau contexte, donc les guillemets à l'intérieur sont indépendants des guillemets à l'extérieur. Et vous voulez généralement des guillemets dans les deux contextes.
Gordon Davisson