Pourquoi {1,2} imprimé par une commande dans $ () n'est pas interpolé?

8

Je me trouve dans un répertoire dans lequel j'ai deux fichiers texte:

$ touch test1.txt
$ touch test2.txt

Lorsque j'essaie de répertorier les fichiers (avec Bash) en utilisant un modèle, cela fonctionne:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

Cependant, lorsqu'un modèle est produit par une commande incluse dans $(), un seul des modèles fonctionne:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

Que se passe t-il ici? Pourquoi le motif {1,2}ne fonctionne pas?

Herosław Miraszewski
la source
4
L'expansion de l'accolade n'est pas effectuée entre guillemets simples ou doubles
Sergiy Kolodyazhnyy
3
@SergiyKolodyazhnyy Le point de la question est que le ?est également cité et est développé après son $(...)remplacement, mais l'expansion d'accolade ne le fait pas.
Michael Homer
1
@muru Non, ce n'est pas le même problème. Ici, l' ordre des extensions n'a pas d'importance, ce qui compte, c'est quelle expansion a lieu dans quel contexte. Je ne serais pas surpris si cette question était en double, mais je ne l'ai pas trouvée.
Gilles 'SO- arrête d'être méchant'
1
@mosvy Ksh et bash font des extensions dans le même ordre, mais ksh accélère l'expansion dans un cas où bash ne le fait pas du tout. Zsh-with-globsubst fait les mêmes extensions que bash, mais dans un ordre différent.
Gilles 'SO- arrête d'être méchant'
1
@ Gilles non, ils ne le font pas. Comme documenté et facilement démontré, ksh (et zsh) effectuera l'expansion de l'accolade juste avant la globalisation. zsh-avec-globsubst n'effectuer une expansion des accolades du tout sur les résultats de $-expansions: zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
mosvy

Réponses:

17

C'est une combinaison de deux choses. Tout d'abord, l'expansion d'accolade n'est pas un modèle qui correspond aux noms de fichiers: c'est une substitution purement textuelle - voir Quelle est la différence entre `a [bc] d` (crochets) et` a {b, c} d` (accolades)? . Deuxièmement, lorsque vous utilisez le résultat d'une substitution de commande en dehors des guillemets doubles ( ls $(…)), ce qui se produit est uniquement la correspondance de modèles (et la division de mots: l'opérateur «split + glob»), pas une nouvelle analyse complète.

Avec ls $(echo 'test?.txt'), la commande echo 'test?.txt'sort la chaîne test?.txt(avec un retour à la ligne final). La substitution de commande entraîne la chaîne test?.txt(sans retour à la ligne final, car la substitution de commande supprime les retours à la ligne de fin). Cette substitution sans guillemets subit un fractionnement de mots, produisant une liste constituée de la chaîne unique test?.txtcar il n'y a pas de caractères d'espacement (plus précisément, pas de caractères $IFSdedans). Chaque élément de cette liste à un élément subit ensuite une expansion générique conditionnelle, et comme il y a un caractère générique ?dans la chaîne, l'expansion générique se produit. Étant donné que le modèle test?.txtcorrespond à au moins un nom de fichier, l'élément de liste test?.txtest remplacé par la liste des noms de fichiers qui correspondent aux modèles, produisant la liste à deux éléments contenanttest1.txtet test2.txt. Enfin lsest appelé avec deux arguments test1et test2.

Avec ls $(echo 'test{1,2}'), la commande echo 'test{1,2}'sort la chaîne test{1,2}(avec un retour à la ligne final). La substitution de commande entraîne la chaîne test{1,2}. Cette substitution sans guillemets subit un fractionnement de mots, produisant une liste composée de la chaîne unique test{1,2}. Chaque élément de cette liste à un élément subit ensuite une expansion conditionnelle générique, ce qui ne fait rien (l'élément est laissé tel quel) car il n'y a pas de caractère générique dans la chaîne. Ainsi lsest appelé avec l'argument unique test{1,2}.

À titre de comparaison, voici ce qui se passe avec ls $(echo test{1,2}). La commande echo test{1,2}génère la chaîne test1 test2(avec un retour à la ligne final). La substitution de commande entraîne la chaîne test1 test2(sans un retour à la ligne final). Cette substitution sans guillemets subit une séparation de mots, produisant deux chaînes test1et test2. Puis, comme aucune des chaînes ne contient de caractère générique, elles sont laissées seules, elles sont donc lsappelées avec deux arguments test1et test2.

Gilles 'SO- arrête d'être méchant'
la source
3
Notez que pdksh et ksh93 effectuent l'expansion de l'accolade lors des extensions (avant la globalisation; pas avec noglob, mais dans le cas de ksh93, toujours lorsque braceexpand est désactivé!)
Stéphane Chazelas
Vous semblez avoir oublié .txtdans la deuxième explication.
val dit Réintégrer Monica le
10

L'ordre des expansions est le suivant: expansion de l'accolade; expansion de tilde, expansion de paramètres et de variables, expansion arithmétique et substitution de commandes (effectuées de gauche à droite); division de mots; et l'expansion du nom de fichier.

L'expansion de l'accolade ne se produira pas après la substitution de commande. Vous pouvez utiliser eval pour forcer un autre cycle d'expansion:

eval echo $(echo '{1,2}lala')

Son résultat est:

1lala 2lala
dedowsdi
la source
6

Ce problème est très spécifique à bash, et c'est parce qu'ils ont décidé bashde séparer l'expansion de l'accolade de l'expansion du nom de fichier (globbing), et de l'exécuter en premier, avant toutes les autres extensions.

Depuis la bashpage de manuel:

L'ordre des expansions est le suivant: expansion de l'accolade; expansion de tilde, expansion de paramètres et de variables, expansion arithmétique et substitution de commandes (effectuées de gauche à droite); division de mots; et expansion du nom de chemin.

Dans votre exemple, bashne verra vos accolades qu'après avoir effectué la substitution de commande (la $(echo ...)), lorsqu'il est trop tard.

Ceci est différent de tous les autres shells, qui effectuent l'expansion de l'accolade juste avant (et certains même dans le cadre de) l'expansion du nom de chemin (globbing). Cela comprend, sans s'y limiter, cshoù les extensions de corset ont été inventées pour la première fois.

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

Ce dernier exemple est le même csh, zsh, ksh93, mkshou fish.

Notez également que l'expansion d'accolade dans le cadre de la globalisation est également disponible via la glob(3)fonction de bibliothèque (au moins sur Linux et tous les BSD), et dans d'autres implémentations indépendantes (par exemple, dans perl:) perl -le 'print join " ", <test{1,2}.txt>'.

Pourquoi cela a été fait différemment dans basha probablement une histoire derrière elle, mais FWIW Je n'ai pas pu trouver d'explication logique, et je trouve toutes les rationalisations post-hoc peu convaincantes.

mosvy
la source
3
Notez que perlutilisé pour invoquer l' cshexpansion des globes, il n'est donc pas surprenant qu'il reconnaisse toujours les mêmes opérateurs de globbing quecsh
Stéphane Chazelas
1

S'il vous plaît essayez:::

ls $ (test d'écho {1,2} \. txt)

Avec une barre oblique inverse. Ça fonctionne maintenant. Supprimez également ce que l'affiche précédente, les citations. Le point n'est pas pour le motif correspondant, mais doit être pris littéralement comme période ici.

mkzia
la source
(1) La question demande «Que se passe-t-il ici? Pourquoi [le] motif {1,2}se comporte-t-il comme il le fait? La question ne demande pas "Comment puis-je obtenir une commande en utilisant {1,2}pour se comporter comme la commande ?fonctionne?" Vous répondez à la mauvaise question. (2) La barre oblique inverse n'a rien à voir avec cela. Votre commande fonctionne comme elle le fait car vous avez supprimé les guillemets qui se trouvaient dans la commande dans la question.
G-Man dit `` Réintègre Monica '' le
0

Cela fonctionne si vous supprimez les guillemets

$ ls $(echo test{1,2})
test1  test2
darxmurf
la source
9
L'expansion se produit maintenant avant la substitution de commande, ce qui, je pense, n'est pas ce que la question demande (comparer ce qui se passe ?).
Michael Homer