bash combinant l'expansion des caractères génériques avec l'expansion des accolades

8

J'essaie de développer une chaîne impliquant un caractère générique et une collection d'extensions spécifiées entre accolades. Rien ne semble fonctionner comme l'illustre l'exemple ci-dessous. la variable firstListse développe correctement , mais ni l'une ni l'autre secondList, thirdListou se fourthListdéveloppe correctement. J'ai également essayé différentes versions de evalmais aucune ne fonctionne non plus. Toute aide serait appréciée

#!/bin/bash
touch a.ext1
touch b.ext1
firstList='*.ext1'
ls  $firstList
touch a.ext2
touch b.ext2
secondList='*.{ext1,ext2}'
ls $secondList 
ls '$secondList'
ls "$secondList"
thirdList=*.{ext1,ext2}
ls $thirdList  
ls '$thirdList'
ls "$thirdList"
fourthList="*.{ext1,ext2}"
ls $fourthList
ls '$fourthList'
ls "$fourthList"
Leo Simon
la source
fwiw eval ls $secondListfonctionne bien ici ... qu'essayez-vous d'accomplir?
don_crissti
Vous devez vérifier l' ordre des extensions bash - l'expansion des accolades se produit avant les extensions des paramètres. Pour obtenir l'effet que vous espérez, vous devez evalobtenir une deuxième série d'extensions.
glenn jackman
@glennjackman Il existe une solution sans eval. Regardez la deuxième solution à la fin de ma réponse .

Réponses:

7

Le shell* ne se développe que s'il n'est pas cité, toute citation arrête l'expansion par le shell.

En outre, une expansion d'accolade doit être non citée pour être développée par le shell.

Ce travail (permet d'utiliser echo pour voir ce que fait le shell):

$ echo *.{ext1,ext2}
a.ext1 b.ext1 a.ext2 b.ext2

Même s'il existe des fichiers avec d'autres noms:

$ touch {a,b}.{ext1,ext2} {c,d}.{ext3,ext4} none
ls
a.ext1  a.ext2  b.ext1  b.ext2  c.ext3  c.ext4  d.ext3  d.ext4  none

$ echo *.{ext1,ext2}
a.ext1 b.ext1 a.ext2 b.ext2

Pourquoi ça marche?

Il est important que nous comprenions pourquoi cela fonctionne. C'est à cause de l'ordre d'expansion. D'abord l '"extension Brace" et plus tard (la dernière) "Expansion Pathname" (alias glob-expansion).

Brace --> Parameter (variable) --> Pathname

Nous pouvons désactiver "l'extension de chemin d'accès" pendant un moment:

$ set -f
$ echo *.{ext1,ext2}
*.ext1 *.ext2

Le "Pathname Expansion" reçoit deux arguments: *.ext1et *.ext2.

$ set +f
$ echo *.{ext1,ext2}
a.ext1 b.ext1 a.ext2 b.ext2

Le problème est que nous ne pouvons pas utiliser une variable pour l'expansion de l'accolade.
Il a été expliqué à plusieurs reprises pour l' utilisation d'une variable dans une "extension d'accolade"

Pour développer une "extension d'accolade" qui est le résultat d'une "extension variable", vous devez soumettre à nouveau la ligne de commande au shell avec eval.

$ list={ext1,ext2}
$ eval echo '*.'"$list"

Accolade -> Variable -> Glob || -> Accolade -> Variable -> Glob
........ cité ici -> ^^^^^^ || eval ^^^^^^^^^^^^^^^^^^^^^^^^^^

Les valeurs des noms de fichiers ne posent aucun problème d'exécution pour eval:

$ touch 'a;date;.ext1'
eval echo '*.'"$list"
a;date;.ext1 a.ext1 b.ext1 a.ext2 b.ext2

Mais la valeur de $listpourrait être dangereuse. Cependant, la valeur de $listest définie par le script scriptor. Le script scripteur contrôle eval: Ne pas utiliser de valeurs définies en externe pour $list. Essaye ça:

#!/bin/bash
touch {a,b,c}.ext{1,2}
list=ext{1,2}
eval ls -l -- '*.'"$list"

Une meilleure alternative.

Une alternative (sans eval) est d' utiliser les "Patterns étendus" Bash :

#!/bin/bash
shopt -s extglob
list='@(ext1|ext2)'
ls -- *.$list

Remarque: veuillez noter que les deux solutions (eval et patterns) (telles qu'elles sont écrites) sont sans danger pour les noms de fichiers avec des espaces ou de nouvelles lignes. Mais échouera pour un $listavec des espaces, car $listn'est pas entre guillemets ou l'évaluation supprime les guillemets.

Communauté
la source
Oui, globbing étendu
glenn jackman
2

Considérer:

secondList='*.{ext1,ext2}'
ls $secondList 

Le problème est que l' expansion d'accolade se fait avant l' expansion variable . Cela signifie que, dans ce qui précède, l'expansion de l'accolade n'est jamais effectuée.

En effet, lorsque bash voit la ligne de commande pour la première fois, il n'y a pas d'accolades. Après secondListest agrandi, il est trop tard.

Les éléments suivants fonctionneront:

$ s='*'
$ ls $s.{ext1,ext2}
a.ext1  a.ext2  b.ext1  b.ext2

Ici, la ligne de commande a des accolades afin que l' expansion d'accolade puisse être effectuée comme première étape. Après cela, la valeur de $sest substituée dans ( expansion variable ), et enfin une expansion de nom de chemin est effectuée.

Documentation

man bash explique l'ordre d'expansion:

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.

John1024
la source