2010-07-27 9 views
42

Estoy tomando una puñalada al escribir una finalización de Bash por primera vez, y estoy un poco confundido acerca de las dos formas de desreferenciar Bash arrays (${array[@]} y ${array[*]}).

Aquí está la porción relevante del código (que funciona, por cierto, pero me gustaría entenderlo mejor):

_switch() 
{ 
    local cur perls 
    local ROOT=${PERLBREW_ROOT:-$HOME/perl5/perlbrew} 
    COMPREPLY=() 
    cur=${COMP_WORDS[COMP_CWORD]} 
    perls=($ROOT/perls/perl-*) 
    # remove all but the final part of the name 
    perls=(${perls[*]##*/}) 

    COMPREPLY=($(compgen -W "${perls[*]} /usr/bin/perl" -- ${cur})) 
} 

de Bash documentation says:

Cualquier elemento de una matriz puede ser referenciado usando $ {name [subscript]}. Los refuerzos son necesarios para evitar conflictos con los operadores de expansión de nombre de archivo del intérprete de comandos. Si el subíndice es '@' o '*', la palabra se expande a todos los miembros del nombre de la matriz. Estos subíndices difieren solo cuando la palabra aparece entre comillas dobles. Si la palabra tiene comillas dobles, $ {nombre [*]} se expande a una sola palabra con el valor de cada miembro de la matriz separado por el primer carácter de la variable IFS, y $ {nombre [@]} expande cada elemento del nombre a una palabra separada.

Ahora creo que entiendo que compgen -W espera una cadena que contiene una lista de palabras de alternativas posibles, pero en este contexto no entiendo lo que "$ {nombre [@]} expande cada elemento de nombre a una palabra separada "significa.

Cuento largo corto: ${array[*]} funciona; ${array[@]} no. Me gustaría saber por qué, y me gustaría entender mejor en qué se expande exactamente ${array[@]}.

Respuesta

53

(Esta es una expansión de mi comentario sobre la respuesta de Kaleb Pederson - ver esa respuesta para una más general tratamiento de [@] vs [*].)

Cuando bash (o cualquier shell similar) analiza una línea de comando, que la divide en una serie de "palabras" (que llamaré "shell-palabras" para evitar la confusión más adelante). En general, las palabras de shell están separadas por espacios (u otros espacios en blanco), pero los espacios se pueden incluir en una palabra de shell escapándolos o cindiéndolos. La diferencia entre [@] y [*] matrices expandidas en comillas dobles es que "${myarray[@]}" lleva a que cada elemento de la matriz se trate como una palabra de shell separada, mientras que "${myarray[*]}" genera una única palabra de shell con todos los elementos de la matriz separados por espacios (o lo que sea que sea el primer personaje de IFS).

Normalmente, el comportamiento [@] es lo que desea. Supongamos que tenemos perls=(perl-one perl-two) y usamos ls "${perls[*]}" - eso es equivalente a ls "perl-one perl-two", que buscará un único archivo llamado perl-one perl-two, que probablemente no sea lo que usted quería. ls "${perls[@]}" es equivalente a ls "perl-one" "perl-two", que es mucho más probable que haga algo útil.

Proporcionar una lista de palabras de finalización (que llamaré comp-words para evitar confusiones con shell-words) a compgen es diferente; la opción -W toma una lista de comp-words, pero debe tener la forma de una única palabra de shell con las comp-words separadas por espacios. Tenga en cuenta que las opciones de comando que toman argumentos siempre (al menos hasta donde yo sé) toman una única palabra de shell; de lo contrario, no habría manera de saber cuándo finalizan los argumentos para la opción y los argumentos del comando normal (/ otro banderas de opción) comenzar.

En más detalle:

perls=(perl-one perl-two) 
compgen -W "${perls[*]} /usr/bin/perl" -- ${cur} 

es equivalente a:

compgen -W "perl-one perl-two /usr/bin/perl" -- ${cur} 

... el que hace lo que quiere. Por otro lado,

perls=(perl-one perl-two) 
compgen -W "${perls[@]} /usr/bin/perl" -- ${cur} 

es equivalente a:

compgen -W "perl-one" "perl-two /usr/bin/perl" -- ${cur} 

... que es un completo disparate: "perl-uno" es la única palabra comp-adjunta a la bandera -W, y el primer argumento real, que compgen tomará como la cadena que se completará, es "perl-two/usr/bin/perl". Esperaría que compgen se queje de que se le han dado argumentos adicionales ("-" y lo que sea en $ cur), pero aparentemente solo los ignora.

+2

Esto es excelente; Gracias. Realmente me gustaría que explotara más fuerte, pero al menos aclara por qué no funcionó. – Telemachus

31

Su título le pregunta sobre ${array[@]} frente ${array[*]} pero luego se preguntó por $array[*] frente $array[@] que es un poco confuso. Voy a responder a ambos:

Cuando cita una variable de matriz y usa @ como subíndice, cada elemento de la matriz se expande a su contenido completo independientemente de los espacios en blanco (en realidad, uno de $IFS) que pueden estar presentes en esa contenido. Cuando utiliza el asterisco (*) como subíndice (independientemente de si está entre comillas o no), puede ampliarse a contenido nuevo creado al dividir el contenido de cada elemento de matriz en $IFS.

Aquí está el script de ejemplo:

#!/bin/sh 

myarray[0]="one" 
myarray[1]="two" 
myarray[3]="three four" 

echo "with quotes around myarray[*]" 
for x in "${myarray[*]}"; do 
     echo "ARG[*]: '$x'" 
done 

echo "with quotes around myarray[@]" 
for x in "${myarray[@]}"; do 
     echo "ARG[@]: '$x'" 
done 

echo "without quotes around myarray[*]" 
for x in ${myarray[*]}; do 
     echo "ARG[*]: '$x'" 
done 

echo "without quotes around myarray[@]" 
for x in ${myarray[@]}; do 
     echo "ARG[@]: '$x'" 
done 

Y aquí está su salida:

with quotes around myarray[*] 
ARG[*]: 'one two three four' 
with quotes around myarray[@] 
ARG[@]: 'one' 
ARG[@]: 'two' 
ARG[@]: 'three four' 
without quotes around myarray[*] 
ARG[*]: 'one' 
ARG[*]: 'two' 
ARG[*]: 'three' 
ARG[*]: 'four' 
without quotes around myarray[@] 
ARG[@]: 'one' 
ARG[@]: 'two' 
ARG[@]: 'three' 
ARG[@]: 'four' 

personalmente por lo general quieren "${myarray[@]}". Ahora, para responder la segunda parte de su pregunta, ${array[@]} contra $array[@].

Citando la documentación de golpe, que citó: Es necesario

Las llaves para evitar conflictos con los operadores de expansión de nombre de archivo de la concha.

$ myarray= 
$ myarray[0]="one" 
$ myarray[1]="two" 
$ echo ${myarray[@]} 
one two 

Pero, cuando lo hace $myarray[@], el signo del dólar está estrechamente ligada a myarray por lo que se evalúa antes de la [@].Por ejemplo:

$ ls $myarray[@] 
ls: cannot access one[@]: No such file or directory 

Pero, como se ha señalado en la documentación, los paréntesis son para la expansión de nombre de archivo, así que vamos a probar esto:

$ touch [email protected] 
$ ls $myarray[@] 
[email protected] 

Ahora podemos ver que la expansión de nombre de archivo que sucedió después de la $myarray exapansion .

Y una nota más, $myarray sin un subíndice se expande hasta el primer valor de la matriz:

$ myarray[0]="one four" 
$ echo $myarray[5] 
one four[5] 
+1

Ver también [esto] (http: // stackoverflow.com/questions/3307672/whats-the-difference-between-and-in-unix/3308046 # 3308046) con respecto a cómo 'IFS' afecta la salida de forma diferente dependiendo de' @ 'contra' * 'y de citado vs. sin cita. –

+0

Pido disculpas, ya que es bastante importante en este contexto, pero siempre quise decir '$ {matriz [*]}' o '$ {matriz [@]}'. La falta de frenos era simplemente descuido. Más allá de eso, ¿puedes explicar en qué '$ {array [*]}' se expandiría en el comando 'compgen'? Es decir, en ese contexto, ¿qué significa expandir la matriz en cada uno de sus elementos por separado? – Telemachus

+0

Para decirlo de otra manera, usted (como casi todas las fuentes) dice que '$ {matriz [@]}' suele ser el camino a seguir. Lo que trato de entender es por qué en este caso * solo * '$ {matriz [*]}' funciona. – Telemachus

Cuestiones relacionadas