bash que combina la expansión comodín con la expansión de llaves

8

Estoy tratando de expandir una cadena que involucra un comodín y una colección de extensiones especificadas dentro de llaves. Nada parece funcionar como lo ilustra el siguiente ejemplo. la variable se firstListexpande bien, pero tampoco secondList, thirdListo se fourthListexpande correctamente. También probé varias versiones evalpero ninguna funciona. Cualquier ayuda sería apreciada

#!/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
fuente
fwiw eval ls $secondListfunciona bien aquí ... ¿qué estás tratando de lograr?
don_crissti
Debe verificar el orden de las expansiones bash: la expansión de llaves se produce antes de las expansiones de parámetros. Para obtener el efecto que espera, debe evalobtener una segunda ronda de expansiones.
Glenn Jackman
¿Dupe de esto? unix.stackexchange.com/q/117144/28489
Runium
@glennjackman Hay una solución sin eval. Mira la segunda solución al final de mi respuesta .

Respuestas:

7

El shell se expande *solo si no se cita, cualquier cita detiene la expansión del shell.

Además, una expansión de llaves debe estar sin comillas para que el shell la expanda.

Este trabajo (usemos echo para ver qué hace el shell):

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

Incluso si hay archivos con otros nombres:

$ 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

¿Por qué eso funciona?

Es importante que comprendamos por qué eso funciona. Es por el orden de expansión. Primero la "Expansión de la llave" y luego (la última) "Expansión del nombre de ruta" (también conocida como expansión global).

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

Podemos desactivar la "expansión de nombre de ruta" por un momento:

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

La "Expansión del nombre de ruta" recibe dos argumentos: *.ext1y *.ext2.

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

El problema es que no podemos usar una variable para la expansión de llaves.
Se ha explicado muchas veces antes para usar una variable dentro de una "Expansión de llaves"

Para expandir una "Expansión de llaves" que es el resultado de una "Expansión variable", debe volver a enviar la línea de comando al shell con eval.

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

Brace -> Variable -> Glob || -> Brace -> Variable -> Glob
........ citado aquí -> ^^^^^^ || eval ^^^^^^^^^^^^^^^^^^^^^^^^^

Los valores de los nombres de archivo no generan problemas de ejecución para eval:

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

Pero el valor de $listpodría ser inseguro. Sin embargo, $listel guionista establece el valor de . El guionista tiene el control de eval: Simplemente no use valores establecidos externamente para $list. Prueba esto:

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

Una mejor alternativa

Una alternativa (sin evaluación) es usar Bash "Extended Patterns" :

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

Nota: Tenga en cuenta que ambas soluciones (eval y patrones) (tal como están escritas) son seguras para los nombres de archivo con espacios o líneas nuevas. Pero fallará para a $listcon espacios, porque $listno está entre comillas o la evaluación elimina las comillas.

Comunidad
fuente
Sí, globbing extendido
glenn jackman
2

Considerar:

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

El problema es que la expansión de llaves se realiza antes de la expansión variable . Eso significa que, en lo anterior, la expansión de llaves nunca se realiza.

Esto se debe a que, cuando bash ve por primera vez la línea de comando, no hay llaves. Después de que secondListse expande, ya es demasiado tarde.

Lo siguiente funcionará:

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

Aquí, la línea de comando tiene llaves para que la expansión de llaves se pueda realizar como primer paso. Después de eso, el valor de $sse sustituye en ( expansión variable ), y finalmente se realiza la expansión del nombre de ruta .

Documentación

man bash explica el orden de expansión:

El orden de las expansiones es: expansión de llaves; expansión de tilde, expansión de parámetros y variables, expansión aritmética y sustitución de comandos (hecho de izquierda a derecha); división de palabras; y expansión de nombre de ruta.

John1024
fuente