guillemets doubles imbriqués dans une ligne très votée

20

Une réponse StackOverflow avec> 3,5 K votes présente cette ligne unique pour l'attribution au DIRrépertoire du script bash actuel:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Je suis perplexe devant les guillemets imbriqués. Pour autant que je sache, les fragments suivants sont entre guillemets:

"$( cd "
"${BASH_SOURCE[0]}"
" && pwd )"

... et tout le reste à droite de =(ie $( dirnameet )) n'est pas cité. En d'autres termes, je suppose que les 2e, 4e et 6e "caractères "ferment" respectivement les 1er, 3e et 5e "caractères.

Je comprends à quoi servent les guillemets doubles "${BASH_SOURCE[0]}", mais à quoi servent les deux autres paires de guillemets doubles?

Si, en revanche (et malgré le score de vote élevé), l'extrait ci-dessus est incorrect, quelle est la bonne façon d'atteindre son objectif nominal?

(Par intention nominale, je veux dire: collecter la valeur retournée par pwdaprès la première cdentrée dans le répertoire retourné par dirname "${BASH_SOURCE[0]}", et effectuer la cd-ing dans un sous-shell, de sorte que le $PWDshell parent reste inchangé).

kjo
la source
1
il est à cause d'une caractéristique de $ (...): $( here, it's a subshell, but you are writing code as if you were writing it on the "first level" of the shell .... ).
Olivier Dulac
Je suis venu ici à cause du script d'installation de docker. Pour trouver le nom de la distribution:lsb_dist="$(. /etc/os-release && echo "$ID")"; echo "$lsb_dist"
David Tonhofer
Notez que les espaces dans la ligne ne sont pas nécessaires: DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"fonctionne également.
David Tonhofer

Réponses:

12

Votre puzzle n'est pas juste sur la façon dont bash(et le shell en général) a analysé l'entrée. Dans:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Tout d'abord, bashanalysez le côté droit de l'affectation sur une longue chaîne $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )car les guillemets doubles peuvent apparaître à l'intérieur des guillemets doubles .

Ensuite, bashcommencez à analyser la substitution de commandes. Étant donné que tous les caractères qui suivent la parenthèse ouverte jusqu'aux parenthèses fermantes sont utilisés pour construire la commande à l'intérieur de la substitution de commande, vous obtiendrez:

cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd

Le shell continue d'analyser cette commande composée, la divise en deux parties:

  • cd "$( dirname "${BASH_SOURCE[0]}" )"
  • pwd

Ensuite, appliquez la même règle d'analyse pour cd "$( dirname "${BASH_SOURCE[0]}" )", mais cette fois, les guillemets doubles ne sont pas redondants, mais ont du sens. Ils empêchent le fractionnement de champ sur le résultat de $( dirname "${BASH_SOURCE[0]}" ), et également l'expansion de ${BASH_SOURCE[0]}(Contrairement aux guillemets les plus externes, il nesplit+glob sera pas nécessaire dans RHS d'attribuer des variables pour empêcher ).


Cette règle s'applique à la substitution de commandes dans tous les shell POSIX . Un puzzle plus détaillé que vous pouvez lire dans la section Reconnaissance de jetons de la spécification POSIX .

cuonglm
la source
21

Une fois à l'intérieur $(...), la citation recommence à zéro.

En d'autres termes, "..."et $(...)peuvent s'imbriquer les uns dans les autres. La substitution de processus $(...), peut contenir une ou plusieurs chaînes complètes entre guillemets. En outre, les chaînes entre guillemets doubles peuvent contenir une ou plusieurs substitutions de processus complètes . Mais, ils ne s'entrelacent pas. Ainsi, une chaîne entre guillemets doubles qui commence à l'intérieur d'une substitution de processus ne s'étendra jamais à l'extérieur de celle-ci ou vice versa.

Considérez donc:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

L'intérieur $(...)est:

dirname "${BASH_SOURCE[0]}"

Dans ce qui précède, ${BASH_SOURCE[0]}on cite deux fois. Toutes les citations, doubles ou simples, en dehors de la ne $(...)sont pas pertinentes pour déterminer que les ${BASH_SOURCE[0]}guillemets sont doubles.

L'extérieur $(...)contient:

cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd

Ici, l'expression $( dirname "${BASH_SOURCE[0]}" )est mise entre guillemets. Le fait qu'il y ait des guillemets à l'extérieur de l'extérieur $(...)n'est pas pertinent lorsque l'on considère ce qui est à l'intérieur. Le fait qu'il y ait des citations à l'intérieur $(...)n'est pas non plus pertinent.

Voici comment les guillemets doubles correspondent:

entrez la description de l'image ici

John1024
la source
Que voulez-vous dire irrelevant? À l'exception de la plupart des parenthèses extérieures, toutes les autres ont leur propre signification.
cuonglm
4
Et, c'est pourquoi vous ne devriez pas utiliser de backticks: les règles de citation sont extrêmement étranges et non intuitives.
Wildcard
La phrase « $(...)lie plus fort que "..."» n'a pas de sens. Ce ne sont pas des opérateurs d'infixe, et il n'y a pas de hiérarchie entre eux (s'il y en avait, cela signifierait que les guillemets ne peuvent pas être entre parenthèses ou les parenthèses ne peuvent pas être entre guillemets, mais ce n'est pas le cas). Le terme technique est cela $(…)et "…"nid.
Gilles 'SO- arrête d'être méchant'
@Gilles Très bien. Je viens de reformuler dans l'espoir de mieux saisir le concept.
John1024