2012-06-24 38 views
15

¿Cómo puedo hacer que este código funcione?¿Cómo iterar sobre una matriz usando referencia indirecta?

#!/bin/bash 
ARRAYNAME='FRUITS' 
FRUITS=(APPLE BANANA ORANGE) 
for FRUIT in ${!ARRAYNAME[@]} 
do 
    echo ${FRUIT} 
done 

este código:

echo ${!ARRAYNAME[0]} 

imprime APPLE. Estoy intentando hacer algo similar pero con "[@]" para iterar sobre la matriz.

Gracias de antemano,

+4

favor ver [BashFAQ/006] (http://mywiki.wooledge.org/BashFAQ/006). –

Respuesta

18

${!ARRAYNAME[@]} significa "los índices de ARRAYNAME". Como se indica en el bash man page ya que ARRAYNAME está establecido, pero como una cadena, no como una matriz, devuelve 0.

Aquí hay una solución usando eval.

#!/usr/bin/env bash 

ARRAYNAME='FRUITS' 
FRUITS=(APPLE BANANA ORANGE) 

eval array=\(\${${ARRAYNAME}[@]} \) 

for fruit in "${array[@]}"; do 
    echo ${fruit} 
done 

Lo que estaba originalmente tratando de hacer era crear un Indirect Reference. Estos se introdujeron en la versión 2 de bash y se suponía que reemplazaban en gran medida la necesidad de eval cuando se intentaba lograr un comportamiento de reflexión en el shell.

Lo que tiene que hacer al utilizar referencias indirectas con matrices es incluir el [@] en su conjetura en el nombre de la variable:

#!/usr/bin/env bash 

ARRAYNAME='FRUITS' 
FRUITS=(APPLE BANANA ORANGE) 

array="${ARRAYNAME}[@]" 
for fruit in "${!array}"; do 
    echo $fruit 
done 

Dicho todo esto, una cosa es utilizar referencias indirectas en este ejemplo trivial, pero, como se indica en el enlace proporcionado por Dennis Williamson, debe vacilar en utilizarlos en scripts del mundo real. Están todos garantizados para hacer que su código sea más confuso de lo necesario. Por lo general, puede obtener la funcionalidad que necesita con una matriz asociativa.

+0

Tim: su segunda parte de la respuesta no es útil, porque ARRAYNAME es un parámetro de entrada de mi script. Pero su primera solución me permite encontrar una solución más elegante: ARRAY = $ {! ARRAYNAME}; para FRUIT en $ {ARRAY [@]} ... Gracias – Neuquino

+0

@Neuquino Vea mi respuesta editada. Todo lo que necesita hacer es concatenar '[@]' al final de su variable de entrada. –

+2

@Neuquino Si ARRAYNAME es un parámetro de entrada para su script, entonces está haciendo algo terriblemente incorrecto. Hay cero razones para mezclar la entrada del usuario con nombres de variables. En este caso (combinado con expansión indirecta), permite la inyección de código arbitrario. La ÚNICA razón válida para estas técnicas es para usarlas en funciones, nunca en el alcance global y nunca combinadas con la entrada del usuario. – ormaaj

0

Solo quería agregar otro caso de uso útil. Estaba buscando en la web para una solución a un diferente, pero problema relacionado

ARRAYNAME=(FRUITS VEG) 
FRUITS=(APPLE BANANA ORANGE) 
VEG=(CARROT CELERY CUCUMBER) 
for I in "${ARRAYNAME[@]}" 
do 
    array="${I}[@]" 
    for fruit in "${!array}"; do 
     echo $fruit 
    done 
done 
+0

'array =" $ {I} [@] "' es exactamente igual que 'tmp = $ arrayname [@]'! ¡No hay mejora! –

0

A pesar de la simple pregunta OP, estas respuestas no se escala para los casos de uso, reales más comunes, es decir, elementos de la matriz que contiene espacios en blanco o comodines que aún no deberían expandirse a nombres de archivo.

FRUITS=(APPLE BANANA ORANGE 'not broken' '*.h') 
ARRAYNAME=FRUITS 
eval ARRAY=\(\${$ARRAYNAME[@]}\) 

$ echo "${ARRAY[4]}" 
broken 
$ echo "${ARRAY[5]}" 
config.h 
$ 

Esto funciona:

FRUITS=(APPLE BANANA ORANGE 'not broken' '*.h') 
ARRAYNAME=FRUITS 
eval ARRAY="(\"\${$ARRAYNAME[@]}\")" 

$ echo "${ARRAY[3]}" 
not broken 
$ echo "${ARRAY[4]}" 
*.h 
$ 

Del mismo modo que debe adquirir el hábito de usar "[email protected]" no [email protected], siempre cito el interior () para ampliaciones de la matriz, a menos que desee expansión nombre de archivo o sabe que no hay posibilidad de elementos de matriz que contienen espacios en blanco.

hacer esto: X=("${Y[@]}")

No esto: X=(${Y[@]})

8

Aquí está una manera de hacerlo sin eval.

Ver Bash truco # 2 describe aquí: http://mywiki.wooledge.org/BashFAQ/006

parece funcionar en bash 3 en adelante.

#!/bin/bash 

ARRAYNAME='FRUITS' 
tmp=$ARRAYNAME[@] 
FRUITS=(APPLE BANANA ORANGE "STAR FRUIT") 
for FRUIT in "${!tmp}" 
do 
    echo "${FRUIT}" 
done 

Aquí hay un ejemplo más realista que muestra cómo pasar una matriz por referencia a una función:

pretty_print_array() { 
    local arrayname=$1 
    local tmp=$arrayname[@] 
    local array=("${!tmp}") 
    local FS=', ' # Field seperator 
    local var 
    # Print each element enclosed in quotes and separated by $FS 
    printf -v var "\"%s\"$FS" "${array[@]}" 
    # Chop trailing $FS 
    var=${var%$FS} 
    echo "$arrayname=($var)" 
} 
FRUITS=(APPLE BANANA ORANGE "STAR FRUIT") 
pretty_print_array FRUITS 
# prints FRUITS=("APPLE", "BANANA", "ORANGE", "STAR FRUIT") 
+0

cualquier forma de obtener el tamaño de dicha matriz indirecta sin eval también? –

0

eval ejecuta código que contiene elementos de la matriz, incluso si contienen, por ejemplo, sustituciones de mando. También cambia los elementos de la matriz interpretando los metacaracteres bash en ellos.

La única herramienta que evita estos problemas es una referencia :

#!/bin/bash 
declare -n ARRAYNAME='FRUITS' 
FRUITS=(APPLE BANANA ORANGE "BITTER LEMON") 
for FRUIT in "${ARRAYNAME[@]}" 
do 
    echo "${FRUIT}" 
done