Una confusión sobre $ {array [*]} versus $ {array [@]} en el contexto de una finalización de bash

89

Estoy intentando escribir una finalización de bash por primera vez, y estoy un poco confundido acerca de las dos formas de desreferenciar matrices de bash ( ${array[@]}y ${array[*]}).

Aquí está el fragmento de código relevante (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} ) )
}

La documentación de bash dice :

Se puede hacer referencia a cualquier elemento de una matriz usando $ {nombre [subíndice]}. Las llaves son necesarias para evitar conflictos con los operadores de expansión de nombre de archivo del shell. 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 está entre 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 -Wespera una cadena que contenga una lista de palabras de posibles alternativas, pero en este contexto no entiendo qué significa "$ {nombre [@]} expande cada elemento del nombre a una palabra separada".

En pocas palabras: ${array[*]}funciona; ${array[@]}no lo hace. Me gustaría saber por qué, y me gustaría entender mejor en qué se ${array[@]}expande exactamente .

Telémaco
fuente

Respuestas:

119

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

Cuando bash (o cualquier shell similar) analiza una línea de comando, la divide en una serie de "palabras" (que llamaré "palabras de shell" para evitar confusiones más adelante). Generalmente, las palabras shell están separadas por espacios (u otro espacio en blanco), pero los espacios se pueden incluir en una palabra shell escapándolos o citandolos. La diferencia entre las matrices [@]y [*]-expanded entre comillas dobles es que hace que "${myarray[@]}"cada elemento de la matriz se trate como una palabra shell separada, mientras que "${myarray[*]}"da como resultado una palabra shell única con todos los elementos de la matriz separados por espacios (o cualquiera que sea el primer carácter de IFS).

Por lo general, 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 solo archivo con el nombre perl-one perl-two, que probablemente no sea lo que deseaba. 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 terminación (a las que llamaré comp-words para evitar confusiones con shell-words) compgenes diferente; la -Wopción toma una lista de palabras de compilación, pero debe tener la forma de una sola palabra de shell con las palabras de compilación separadas por espacios. Tenga en cuenta que las opciones de comando que toman argumentos siempre (al menos hasta donde yo sé) toman una sola palabra de shell; de lo contrario, no habría forma de saber cuándo terminan los argumentos de la opción y los argumentos de comando regulares (/ otro banderas de opciones) comienzan.

Con 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}

... que hace lo que quiere. Por otra parte,

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}

... lo cual es una completa tontería: "perl-one" es la única palabra de composición adjunta al indicador -W, y el primer argumento real, que compgen tomará como la cadena a 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 simplemente los ignora.

Gordon Davisson
fuente
3
Esto es excelente; Gracias. Realmente desearía que explotara más fuerte, pero esto al menos aclara por qué no funcionó.
Telemachus
60

Tu título pregunta sobre ${array[@]}versus, ${array[*]}pero luego preguntas sobre $array[*]versus, lo $array[@]cual es un poco confuso. Responderé a ambos:

Cuando cita una variable de matriz y la usa @como subíndice, cada elemento de la matriz se expande a su contenido completo independientemente del espacio en blanco (en realidad, uno de $IFS) que pueda estar presente dentro de ese contenido. Cuando usa el asterisco ( *) como subíndice (independientemente de si está citado o no), puede expandirse a nuevo contenido creado al dividir el contenido de cada elemento de la 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'

Yo personalmente suelo querer "${myarray[@]}". Ahora, para responder a la segunda parte de su pregunta, ${array[@]}versus $array[@].

Citando los documentos de bash, que citó:

Las llaves son necesarias para evitar conflictos con los operadores de expansión de nombre de archivo del shell.

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

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

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

Pero, como se indica en la documentación, los corchetes son para la expansión del nombre de archivo, así que intentemos esto:

$ touch one@
$ ls $myarray[@]
one@

Ahora podemos ver que la expansión del nombre de archivo ocurrió después de la $myarrayexpansión.

Y una nota más, $myarraysin un subíndice, se expande al primer valor de la matriz:

$ myarray[0]="one four"
$ echo $myarray[5]
one four[5]
Kaleb Pederson
fuente
1
También vea esto con respecto a cómo IFSafecta la salida de manera diferente según @vs. *y cotizado vs. no cotizado.
Pausado hasta nuevo aviso.
Pido disculpas, ya que es bastante importante en este contexto, pero siempre quise decir ${array[*]}o ${array[@]}. La falta de frenillos fue simplemente un descuido. Más allá de eso, ¿puede explicar en qué ${array[*]}se expandiría el compgencomando? Es decir, en ese contexto, ¿qué significa expandir la matriz en cada uno de sus elementos por separado?
Telemachus
Para decirlo de otra manera, usted (como casi todas las fuentes) dice que ${array[@]}suele ser el camino a seguir. Lo que estoy tratando de entender es por qué en este caso solo ${array[*]} funciona.
Telemachus
1
Esto se debe a que la lista de palabras proporcionada con la opción -W debe proporcionarse como una sola palabra (que compgen luego se divide en función de IFS). Si se divide en palabras separadas antes de ser entregado a compgen (que es lo que hace [@]), compgen pensará que solo el primero va con -W, y el resto son argumentos regulares (y creo que solo espera un argumento, y por lo tanto vomitará).
Gordon Davisson
@Gordon: Mueva eso a una respuesta, y la aceptaré. Eso es lo que realmente quería saber. Gracias. (Por cierto, no vomita de una manera obvia. Eructa silenciosamente, lo que hace que sea difícil saber qué salió mal.)
Telemachus