Pourquoi «|» n'est-il pas traité littéralement selon un modèle global?

13

Ma question vient de Comment le stockage de l'expression régulière dans une variable shell évite-t-il les problèmes de citation de caractères spéciaux pour le shell? .

  1. Pourquoi y a-t-il une erreur:

    $ [[ $a = a|b ]]  
    bash: syntax error in conditional expression: unexpected token `|'
    bash: syntax error near `|b'

    À [[ ... ]]l' intérieur du deuxième opérande de =devrait se trouver un motif de globulation.

    N'est-ce a|bpas un modèle de globbing valide? Pouvez-vous indiquer quelle règle de syntaxe il viole?

  2. Certains commentaires ci-dessous indiquent que cela |est interprété comme un tuyau.

    Puis changer =pour le modèle glob =~pour le modèle regex faire |fonctionner

    $ [[ $a =~ a|b ]]

    J'ai appris de Learning Bash p180 dans mon post précédent qui |est reconnu comme pipe au début de l'interprétation, avant même toute autre étape d'interprétation (y compris l'analyse des expressions conditionnelles dans les exemples). Alors, comment peut-on |être reconnu comme opérateur regex lors de l'utilisation =~, sans être reconnu comme tuyau dans une utilisation non valide, tout comme lors de l'utilisation =? Cela me fait penser que l'erreur de syntaxe dans la partie 1 ne signifie pas qu'elle |est interprétée comme un canal.

    Chaque ligne que le shell lit à partir de l'entrée standard ou d'un script est appelée pipeline; il contient une ou plusieurs commandes séparées par zéro ou plusieurs caractères de canal (|). Pour chaque pipeline lu, le shell le décompose en commandes, configure les E / S pour le pipeline, puis effectue les opérations suivantes pour chaque commande (figure 7-1):

Merci.

Tim
la source
1
Notez que dans certaines versions de bash, l'analyse extglob (où |est spécial) est activée par défaut dans la partie droite de [[ $var = $pattern ]]. Il serait intéressant d'isoler les versions et les shoptconfigurations d'options où ce comportement est vu - si ce n'est que celles où il extglobest activé, par défaut ou configuration explicite, eh bien, nous y sommes.
Charles Duffy
2
BTW, si vous vouliez exclure un peu plus complètement le cas où le caractère de pipe interférait avec une étape antérieure de l'analyse (ce qui, j'en conviens, ne se produit pas, mais ce n'est pas aussi évident pour le lecteur qu'il pourrait l'être), vous feriez utilisez pattern='a|b'puis développez sans $patternguillemets sur le RHS.
Charles Duffy
@CharlesDuffy, c'était le point soulevé dans le Q&A dont cette question fait suite.
Stéphane Chazelas
Ahh - le contexte a du sens; et votre réponse ici est exceptionnelle. Merci sur les deux plans.
Charles Duffy
Tim, dijd l'une des réponses ci-dessous répond à votre question? Veuillez envisager d'en accepter un dans l'affirmative. Je vous remercie!
Jeff Schaller

Réponses:

13

Il n'y a pas de bonne raison

[[ $a = a|b ]]

Doit signaler une erreur au lieu de tester si $ a est la a|bchaîne, tandis [[ $a =~ a|b ]]que ne renvoie pas d'erreur.

La seule raison est que |c'est généralement (extérieur et intérieur [[ ... ]]) un caractère spécial. Dans cette [[ $a =position, bashattend un type de jeton qui est un MOT normal comme les arguments ou les cibles des redirections dans une ligne de commande shell normale (mais comme si l' extgloboption avait été activée depuis bash 4.1).

(par WORD ici, je me réfère à un mot dans une grammaire de shell hypothétique comme celle décrite par la spécification POSIX , c'est quelque chose que le shell analyserait comme un jeton dans une ligne de commande de shell simple, pas une autre définition de mots comme l'anglais l' une d'une séquence de lettres ou d' une séquence de caractères non-espacement. foo"bar baz", $(echo x y)sont deux tels WORD s).

Dans une ligne de commande shell normale:

echo a|b

Est echo acanalisé vers b. a|bn'est pas un MOT , c'est trois jetons: un a MOT , un |jeton et un jeton b MOT .

Lorsqu'il est utilisé dans [[ $a = a|b ]], bashattend un MOT qu'il obtient ( a), mais trouve ensuite un |jeton inattendu qui provoque l'erreur.

Fait intéressant, bashne se plaint pas:

[[ $a = a||b ]]

Parce que c'est maintenant un ajeton suivi d'un ||jeton suivi de b, il est donc analysé de la même manière que:

[[ $a = a || b ]]

Qui teste qui $aest aou que la bchaîne n'est pas vide.

Maintenant en:

[[ $a =~ a|b ]]

bashne peut pas avoir la même règle d'analyse. Avoir la même règle d'analyse signifierait que ce qui précède donnerait une erreur et qu'il faudrait citer cela |pour s'assurer qu'il a|bs'agit d'un seul mot . Mais, depuis bash 3.2, si vous le faites:

[[ $a =~ 'a|b' ]]

Cela ne correspond plus à l' a|bexpression rationnelle, mais à l' a\|bexpression rationnelle. C'est-à-dire que la citation du shell a pour effet secondaire de supprimer la signification spéciale des opérateurs d'expression régulière. C'est une fonctionnalité, donc le comportement est similaire à [[ $a = "?" ]]celui, mais les modèles génériques (utilisés dans [[ $a = pattern ]]) sont des MOTS shell (utilisés dans les globes par exemple), contrairement aux expressions rationnelles.

Donc , bashdoit traiter tous les opérateurs de telle expression rationnelle qui sont par ailleurs normalement des caractères shell spéciaux comme |, (, )différemment lors de l' analyse d' un argument de l' =~opérateur.

Notez cependant que

 [[ $a =~ (ab)*c ]]

fonctionne maintenant,

 [[ $a =~ [)}] ]]

non. Vous avez besoin:

 [[ $a =~ [\)}] ]]
 [[ $a =~ [')'}] ]]

Qui dans les versions précédentes de bashne correspondrait pas correctement à la barre oblique inverse. Celui-là a été corrigé, mais

 [[ $a =~ [^]')'] ]]

Ne correspond pas à la barre oblique inverse comme il le devrait par exemple. Parce que bashne parvient pas à réaliser que )se trouve entre crochets, échappe donc à )pour aboutir à une [^]\)]expression rationnelle qui correspond à n'importe quel caractère mais ], \et ).

ksh93 a des bogues bien pires sur ce front.

Dans zsh, c'est un mot shell normal qui est attendu et le fait de citer des opérateurs regexp n'affecte pas la signification des opérateurs regexp.

[[ $a =~ 'a|b' ]]

Correspond à l' a|bexpression rationnelle.

Cela signifie que le =~peut également être ajouté à la commande [/ test:

[ "$a" '=~' 'a|b' ]
test "$a" '=~' 'a|b'

(fonctionne également dans yash. Les =~besoins doivent être cités zshcomme =somethingest un opérateur shell spécial là-bas).

bash 3.1 se comportait comme auparavant zsh. Il a changé en 3.2, vraisemblablement pour s'aligner avec ksh93(même si bashc'était le shell qui est apparu en premier [[ =~ ]]), mais vous pouvez toujours le faire BASH_COMPAT=31ou shopt -s compat31pour revenir au comportement précédent (sauf que tandis [[ $a =~ a|b ]]que retournerait une erreur en bash3.1, il ne le fait plus dans bash -O compat31les versions plus récentes de bash).

J'espère que cela clarifie pourquoi j'ai dit que les règles prêtaient à confusion et pourquoi utiliser:

[[ $a =~ $var ]]

aide notamment à la portabilité vers d'autres coques.

Stéphane Chazelas
la source
zsh signale également une erreur sur [[ $a = a|b ]].
Isaac
@isaac, oui, c'est le point que je fais ici. a|bn'est pas une coquille WORD ici, c'est le a, |et bjetons. Comme echo a|bne produit pas a|bou ne développe pas un a|bglob, vous devez le citer |car c'est un caractère shell spécial qui n'est pas valide dans ce contexte. [[ $a = (a|b) ]]fonctionnerait comme echo (a|b)fonctionnerait comme (a|b)un opérateur générique zsh.
Stéphane Chazelas
Le libellé et l'explication de votre réponse ne portent que le nom de bash. Ce n'est pas toute la vérité.
Isaac
11

Sont globs standard ( "extension de nom de fichier"): *, ?et [ ... ]. |n'est pas un opérateur glob valide dans les paramètres standard (non extglob).

Essayer:

shopt -s extglob
[[ a = @(a|b) ]] && echo matched
Jeff Schaller
la source
1
Merci. Mais pourquoi n'est-il pas |littéralement interprété? Pourquoi y a-t-il une erreur de syntaxe?
Tim
1
Ce n'était pas cité.
Jeff Schaller
3
Dans les paramètres standard, |n'est-ce pas un opérateur glob, donc n'est-il pas |interprété littéralement sans être cité? Alors pourquoi y a-t-il une erreur de syntaxe?
Tim
1
|est un caractère de contrôle; il n'est jamais traité comme un caractère littéral de la même manière qu'une lettre ou un chiffre.
chepner
3
Parce que dans ce mode, le shell ne s'attendait pas à un caractère de redirection de canal au milieu d'un [[]] pas encore fermé. [[ $a = an'est pas une commande valide dont la sortie peut être dirigée vers un autre processus (du moins c'est ce que le shell pensait que vous essayiez de faire).
Jason C
5

Si vous voulez une correspondance regex, le test serait:

[[ "$a" =~ a|b ]]
Deathgrip
la source
@Tim Vous devez ouvrir de nouvelles questions et ne pas modifier en permanence votre question actuelle.
gardenhead
@gardenhead: Ma mise à jour est de clarifier mes questions, au lieu de les changer, au cas où vous les manqueriez. La deuxième partie que j'ai ajoutée est de montrer l' explication du tuyau d' un commentaire sur ma question d'origine (pourquoi l'erreur de syntaxe) n'est pas correcte.
Tim