Comment les guillemets doubles en bash sont-ils appariés (appariés)?

15

J'utilise GNU bash 4.3.48. Considérez les deux commandes suivantes qui ne diffèrent que par un seul signe dollar.

Commande 1:

echo "(echo " * ")"

Commande 2:

echo "$(echo " * ")"

Leur sortie est, respectivement,

(echo  test.txt ppcg.sh )

et

 * 

Donc, évidemment, dans le premier cas, le *est globulé, ce qui signifie que le premier guillemet va avec le second pour former une paire, et le troisième et le quatrième forment une autre paire.

Dans le second cas, le *n'est pas globlé, et il y a exactement deux espaces supplémentaires dans la sortie, un avant l'astérisque et un après, ce qui signifie que le deuxième guillemet va avec le troisième et le premier va avec le quatrième.

Y a-t-il d'autres cas en dehors de la $()construction que les guillemets ne correspondent pas au suivant, mais sont imbriqués à la place? Ce comportement est-il bien documenté et si oui, où puis-je trouver le document correspondant?

Weijun Zhou
la source
2
Connexes: Bash cite sans échappatoire sur la substitution de commandes .
G-Man dit `` Réintègre Monica '' le
Encore une fois, la mise en évidence de la syntaxe dans UL SE est erronée et trompeuse, bien que JF soit le seul à blâmer.
Weijun Zhou

Réponses:

14

Toutes les constructions imbriquées qui peuvent être interpolées à l'intérieur de chaînes peuvent avoir d'autres chaînes à l'intérieur: elles sont analysées comme un nouveau script, jusqu'au marqueur de fermeture, et peuvent même être imbriquées à plusieurs niveaux. Tout sauf un commence par un $. Tous sont documentés dans une combinaison du manuel Bash et de la spécification du langage de commande shell POSIX.

Il y a quelques cas de ces constructions:

  • Substitution de commandes avec$( ... ) , comme vous l'avez trouvé. POSIX spécifie ce comportement :

    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 ...

    Les citations font partie de scripts shell valides, elles sont donc autorisées avec leur signification normale.

  • Substitution de commandes utilisant `également.
  • L'élément "word" des instances de substitution de paramètres avancées telles que${parameter:-word} . La définition de "mot" est :

    Une séquence de caractères traités comme une unité par le shell

    - qui inclut du texte entre guillemets et même des guillemets mixtes a"b"c'd'e- bien que le comportement réel des extensions soit un peu plus libéral que cela, et par exemple ${x:-hello world}fonctionne aussi.

  • Expansion arithmétique avec $(( ... )), bien qu'elle soit largement inutile là-bas (mais vous pouvez également imbriquer la substitution de commandes ou les extensions de variables, puis avoir des guillemets utiles à l'intérieur). POSIX déclare que :

    L'expression doit être traitée comme si elle était entre guillemets doubles, sauf qu'un guillemet double à l'intérieur de l'expression n'est pas traité spécialement. Le shell doit développer tous les jetons dans l'expression pour l'expansion des paramètres, la substitution de commandes et la suppression des guillemets.

    ce comportement est donc explicitement requis. Cela signifie echo "abc $((4 "*" 5))"faire de l'arithmétique, plutôt que de globuler.

    Notez cependant que l' $[ ... ]expansion arithmétique à l' ancienne n'est pas traitée de la même manière: les guillemets seront une erreur s'ils apparaissent, que l'expansion soit citée ou non. Ce formulaire n'est plus du tout documenté et n'est pas destiné à être utilisé de toute façon.

  • Traduction spécifique aux paramètres régionaux avec$"..." , qui utilise en fait l' "élément central. $"est traité comme une seule unité.

Il y a un autre cas d'imbrication auquel vous ne vous attendez pas, n'impliquant pas de guillemets, qui est avec l' expansion d'accolade : se {a,b{c,d},e}développe en "a bc bd e". ${x:-a{b,c}d}ne niche pas , cependant; il est traité comme une substitution de paramètres donnant " a{b,c", suivi de " d}". Cela est également documenté :

Lorsque des accolades sont utilisées, l'accolade de fin correspondante est le premier '}' non échappé par une barre oblique inverse ou dans une chaîne entre guillemets, et non dans une expansion arithmétique intégrée, une substitution de commande ou une expansion de paramètre.


En règle générale, toutes les constructions délimitées analysent leur corps indépendamment du contexte environnant (et les exceptions sont traitées comme des bogues ). En substance, en voyant $(le code de commande de substitution demande que l'analyseur de consommer ce qu'il peut du corps comme si elle est un nouveau programme, puis vérifie que le marqueur de terminaison prévu (une séquence d' échappement )ou ))ou }) apparaît une fois que les courses sous-analyseur des choses qu'il peut consommer.

Si vous pensez au fonctionnement d'un analyseur à descente récursive , ce n'est qu'une simple récursivité dans le cas de base. C'est en fait plus facile à faire que dans l'autre sens, une fois que vous avez une interpolation de chaîne. Quelle que soit la technique d'analyse sous-jacente, les shells supportant ces constructions donnent le même résultat.

Vous pouvez imbriquer des citations aussi profondément que vous le souhaitez à travers ces constructions et cela fonctionnera comme prévu. Nulle part ne se confondra en voyant une citation au milieu; au lieu de cela, ce sera le début d'une nouvelle chaîne entre guillemets dans le contexte intérieur.

Michael Homer
la source
Merci. Dans "blah/blah\n$(cat "${tmpdir}/${filename}.jpdf")", pourquoi le deuxième guillemet double n'est-il pas la fin du premier guillemet double (comme le montre la coloration syntaxique dans votre réponse), mais le début d'une chaîne à l'intérieur $(...)? Est-ce parce que l'analyseur de bash est de haut en bas, plutôt que de bas en haut?
StackExchange for All
2
Il y a beaucoup de variations dans la manipulation de "${var-"foo"}"( echo "${-+"*"}"est la même que echo *dans le shell Bourne ou Korn par exemple) et le comportement sera clairement indéterminé dans la prochaine version de la norme . Voir aussi la discussion sur mail-archive.com/[email protected]/msg00167.html
Stéphane Chazelas
3

Peut-être que regarder les deux exemples avec printf(au lieu de echo) aidera:

$ printf '<%s> ' "(echo " * ")"; echo
<(echo > <test.txt> <ppcg.sh> <file1> <file2> <file3> <)>

Il imprime (echo (le premier mot, y compris un espace de fin), certains fichiers et le mot de fermeture ).
La parenthèse n'est qu'une partie de la chaîne entre guillemets (echo .
L'astérisque (désormais sans guillemets, car les deux guillemets doubles sont associés) est développé en tant que glob à une liste de fichiers correspondants.
Et puis, la parenthèse fermante.

Cependant, votre deuxième commande fonctionne comme suit:

$ printf '<%s> ' "$(echo " * ")" ; echo
< * >

Le $commence une substitution de commande. Cela commence une nouvelle fois la citation.
L'astérisque est cité " * "et c'est ce que la commande (ici c'est une commande et non une chaîne entre guillemets) echosort. Enfin, printfreformate le *et l'imprime sous < * >.

Isaac
la source