Que requiert POSIX pour les documents cités ici dans la substitution de commandes?

20

Dans cette question, quelqu'un signale un problème d'utilisation d'un document ici avec un mot délimiteur entre guillemets à l'intérieur de la $(...)substitution de commande , où une barre oblique inversée \à la fin d'une ligne à l'intérieur du document déclenche la poursuite de la ligne de jonction de nouvelle ligne , tandis que le même document ici à l' extérieur de la substitution de commande fonctionne comme prévu .

Voici un exemple de document simplifié:

cat <<'EOT'
abc ` def
ghi \
jkl
EOT

Cela comprend un backtick et un backslash à la fin d'une ligne. Le délimiteur est cité, donc aucune expansion ne se produit à l'intérieur du corps. Dans tous les Bourne-alikes, je trouve que cela génère le contenu textuellement. Si je mets le même document dans une substitution de commande comme suit:

x=$(cat <<'EOT'
abc ` def
ghi \
jkl
EOT
)
echo "$x"

alors ils ne se comportent plus de manière identique:

  • dash, ash, zsh, ksh93, BusyBox ash, mkshet SunOS 5.10 Posix shdonner tout le contenu in extenso du document, comme avant.
  • Bash 3.2 donne une erreur de syntaxe pour un backtick inégalé. Avec des raccourcis appariés, il tente d'exécuter le contenu en tant que commande.
  • Bash 4.3 réduit "ghi" et "jkl" sur une seule ligne, mais n'a aucune erreur. L' --posixoption n'affecte pas cela. Kusalananda me dit (merci!) Que le pdkshcomportement est le même .

Dans la question d'origine, j'ai dit que c'était un bug dans l'analyseur de Bash. C'est ça? [Mise à jour: oui ] Le texte pertinent de POSIX (tout de la définition du langage de commande Shell) que je peux trouver est:

  • §2.6.3 Substitution de commande :

    Avec le formulaire $ (commande), 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.

  • §2.7.4 Ici-Document :

    Si une partie du mot est citée, le délimiteur doit être formé en effectuant la suppression des guillemets sur le mot et les lignes du document ne doivent pas être développées.

  • §2.2.1 Caractère d'échappement (barre oblique inverse) :

    Si un <newline> suit le <backslash>, le shell doit interpréter cela comme une continuation de ligne. Les <backslash> et <newline> doivent être supprimés avant de diviser l'entrée en jetons.

  • §2.3 Reconnaissance des jetons :

    Lorsqu'un jeton io_here a été reconnu par la grammaire (voir Shell Grammar ), une ou plusieurs des lignes suivantes immédiatement après le prochain jeton NEWLINE forment le corps d'un ou plusieurs documents ici et doivent être analysées selon les règles de Here- Document .

    Lorsqu'il ne traite pas un io_here , le shell divisera son entrée en jetons en appliquant la première règle applicable ci-dessous au caractère suivant dans son entrée. ...

    ...

    1. Si le caractère actuel est <barre oblique inverse>, guillemet simple ou guillemet double et qu'il n'est pas cité, il affectera la citation des caractères suivants jusqu'à la fin du texte cité. Les règles de cotation sont celles décrites dans Devis . Au cours de la reconnaissance du jeton, aucune substitution ne doit être réellement effectuée, et le jeton de résultat doit contenir exactement les caractères qui apparaissent dans l'entrée (sauf pour la jonction <nouvelle>), non modifiés, y compris les guillemets ou opérateurs de substitution intégrés ou englobants, entre le et la fin du texte cité.

Mon interprétation de ceci est que tous les caractères après $(la fin )comprennent le script shell, textuellement; un document ici apparaît, donc le traitement ici-document a lieu au lieu de la tokenisation ordinaire; le document ici a alors un délimiteur entre guillemets, ce qui signifie que son contenu est traité textuellement; et le personnage d'évasion n'y entre jamais. Je peux voir un argument, cependant, que ce cas n'est tout simplement pas traité et que les deux comportements sont autorisés. Il est possible que j'ai également sauté un texte pertinent quelque part.


  • Cette situation est-elle rendue plus claire ailleurs?
  • Sur quoi un script portable devrait-il pouvoir s'appuyer (en théorie)?
  • Le traitement spécifique donné par l'un de ces obus (Bash 3.2 / Bash 4.3 / tout le monde) est-il imposé par la norme? Interdit? Permis?
Michael Homer
la source
Pouvez-vous nous montrer comment vous produisez votre sortie dans le deuxième cas?
Julie Pelletier
@JuliePelletier echo "$x", mais toute façon d'inspecter la variable fonctionne. J'ai édité cette ligne en bas.
Michael Homer
2
On dirait que c'est une solution facile. Ce correctif semble fonctionner au moins: ignore_quoted_newline_in_quoted_heredoc.patch
geirha
1
Je pense que vous interprétez cela correctement et imo la norme est assez claire puisque "Le shell doit étendre la substitution de commandes en exécutant la commande dans un environnement de sous-shell [...] et en remplaçant la substitution de commandes [...] par la sortie standard de la commande [...] " Donc, il exécute la commande dans un sous-shell et remplace $(...)par quoi que ce soit la sortie ... Maintenant, lorsque vous exécutez la commande dans votre exemple dans un sous-shell (en bash), il produit le résultat attendu. Ce n'est qu'en le transformant en substitution de commandes qu'il effondre "ghi" et "jkl". C'est donc un bug imo
don_crissti
2
@geirha J'ai signalé un bug Bash ; Je ne vais pas me soucier de pdksh car il ne semble même pas avoir une ombre de maintenance actuelle.
Michael Homer

Réponses:

5

Cela a été demandé sur la liste de diffusion de Bash, et le responsable a confirmé qu'il s'agissait d'un bogue

Ils ont également mentionné que le texte de POSIX "n'est pas nécessairement ambigu, mais qu'il nécessite une lecture attentive". J'ai donc demandé des éclaircissements à ce sujet. Leur réponse, y compris une description du problème et l'interprétation de la norme, était la suivante:

La substitution de commande est un hareng rouge; il n'est pertinent que dans la mesure où il indique où se trouve le bogue.

Le délimiteur du document ici est cité, donc les lignes ne sont pas développées. Dans ce cas, le shell lit les lignes de l'entrée comme si elles étaient entre guillemets. Si une barre oblique inversée apparaît dans un contexte où elle est citée, elle n'agit pas comme un caractère d'échappement (voir ci-dessous) et la gestion spéciale de la barre oblique inversée n'a pas lieu. En fait, si une partie du délimiteur est citée, les lignes du document ici sont lues comme si elles étaient entre guillemets simples.

Le texte de Posix 2.2.1 est maladroitement écrit, mais signifie que la barre oblique inverse n'est traitée spécialement que lorsqu'elle n'est pas citée. Vous pouvez citer une barre oblique inverse et empêcher toute expansion uniquement avec des guillemets simples ou une autre barre oblique inverse.

La partie de lecture rapprochée est le texte "non développé" impliquant les guillemets simples. La norme dit en 2.2 qu'ici les documents sont "une autre forme de citation", mais la seule forme de citation dans laquelle les mots ne sont pas du tout développés est les guillemets simples. C'est donc une forme de citation qui est à peu près exactement comme les guillemets simples, mais pas les guillemets simples.

Kevin
la source
@Scott (1) Je crois que cela répond à toutes les questions et rien n'est superflu. Mon commentaire qui commence la réponse concerne une suppression faite par un modérateur qui a mal compris la situation. (2) Je n'ai pas assez de réputation. (3) J'aurais apprécié un comportement similaire de la part de ceux qui ont supprimé mes réponses, mais je garderai certainement cela à l'esprit à l'avenir. Merci pour les pensées.
Kevin
Mon point était que la plupart de votre premier paragraphe est une conversation avec Michael Mrozek et non une réponse à la question. Je me rends compte que vous n'avez pas assez de réputation pour commenter un post, mais je crois que vous en avez assez pour les méta et le chat.
Scott
1
@Scott Je comprends et apprécie que vous essayez de rationaliser la réponse, mais j'ai posté cette réponse exactement rationalisée précédemment (juste la citation et un lien vers celle-ci), et elle a été supprimée par ledit modérateur (sans aucune discussion!) Et j'ai ne voyez aucun lien dans le message supprimé pour discuter et contester cette décision. J'espérais qu'en répondant à sa critique non fondée, elle survivrait à la suppression, serait acceptée par le demandeur et ensuite je modifierais la réponse pour supprimer le préambule.
Kevin