2012-02-10 8 views
22

Mi colega, Ryan, vinieron a mí con un error en su escritura del golpe, y yo identificaron el problema con esta prueba:resultados Asignar de comodines a una variable en Bash

$ mkdir ryan 
$ mkdir ryan/smells-bad 
$ FOO=ryan/smells-* 
$ echo $FOO 
ryan/smells-bad 
$ touch $FOO/rotten_eggs 
touch: cannot touch `ryan/smells-*/rotten_eggs': No such file or directory 

De esto deduzco que la globbing ocurre durante el comando echo, no cuando se crea la variable FOO.

Tenemos un par de soluciones, en orden descendente de ungracefulness:

touch `echo $FOO`/rotten_eggs 

O:

pushd 
cd $FOO 
touch rotten_eggs 
popd 

Pero tampoco es satisfactoria. ¿Me estoy perdiendo un truco?

Respuesta

29

El problema es que el globo solo se expandirá si el archivo "rotten_eggs" existe, porque está incluido en el patrón global. Deberías usar una matriz.

FOO=(ryan/smells-*) 
touch "${FOO[@]/%//rotten_eggs}" 

La matriz FOO contiene todo lo que coincide con el glob. La expansión usa% anexa/rotten_eggs a cada elemento.

+0

Gracias, esto lo explica muy bien. –

+0

Ese no es el problema en absoluto. El problema es que bash asigna el glob a la variable antes de la expansión. Incluso si el archivo existiera, se asignaría un glob a la variable, no a un nombre de archivo; y ese glob se expandiría en el uso –

+0

@SamLiddicott El ejemplo del OP habría funcionado si existiera "rotten_eggs", por lo que fue más o menos el problema. El momento en que se expande el globo no es relevante en el breve ejemplo del PO. – jordanm

6

Considere

for dir in $FOO; do 
    touch "$dir/rotten_eggs" 
done 

en cuenta que esto touch varios archivos si el patrón global coincide con más de un nombre de ruta.

+0

Esto funcionará, pero requiere que el toque se ejecute varias veces, en lugar de una vez. – jordanm

+1

Esto es posiblemente más claro, dependiendo de la situación. –

3

lo haría así:

for FOO in ryan/smells-*; do 
    touch "$FOO"/rotten_eggs 
done 

De esta manera $FOO contiene el nombre del directorio actual, no la patrón global. Sin embargo, si hay más de una coincidencia, solo contendrá la última después del ciclo, por lo que la solución de matriz podría ser mejor para ese caso.

0

El código como se pretende con el resultado de la glob asignado a la variable sería así:

$ mkdir ryan 
$ mkdir ryan/smells-bad 
$ FOO=(ryan/smells-*) 
$ echo "${FOO[@]}" 
ryan/smells-bad 
$ echo "$FOO" 
ryan/smells-bad 
$ touch "$FOO/rotten_eggs" 
$ ls -l "$FOO" 
total 0 
-rw-r--r-- 1 ryan ryan 0 Mar 1 11:17 rotten_eggs 

$FOO es en realidad una serie aquí, pero $ FOO también trabaja para obtener el primer elemento de la matriz .

pero, ver cómo el pegote puede coincidir más de un archivo (de ahí la matriz es una buena idea)

$ mkdir ryan/clean 
$ FOO=(ryan/*) 
$ echo "$FOO" 
ryan/clean 
$ echo "${FOO[@]}" 
ryan/clean ryan/smells-bad 

En estos casos, los resultados de la glob se asigna a la variable como se desee, en lugar de la variable se expande como un globo en el punto de uso.

Por supuesto, esto significa que la variable realmente siempre debe ser utilizado en comillas dobles "..." de lo contrario si el nombre del archivo en sí (la expansión pegote) también tuvo un * en ella, sería glob de nuevo.

p. Ej.

$ touch ryan/'*ea*' 
$ FOO=(ryan/*ea*) 
$ echo "${FOO[@]}" 
ryan/clean ryan/*ea* 
$ echo ${FOO[@]} 
ryan/clean ryan/clean ryan/*ea* 
Cuestiones relacionadas