Cómo usar una variable como parte de un nombre de matriz

11

Tengo dos matrices:

arrayA=(1 2 3)
arrayB=(a b c)

y quiero imprimir uno de ellos usando un argumento de línea de comando, es decir, sin ninguno if else.

Intenté algunas variaciones en la sintaxis sin éxito. Quiero hacer algo como esto:

ARG="$1"

echo ${array${ARG}[@]}

pero recibo un error de "mala sustitución". ¿Cómo puedo conseguir esto?

Aaron
fuente
Esto no es enfáticamente una fiesta idiomática. Por favor, no hagas esto.
Comodín el

Respuestas:

22

Intenta hacer esto:

$ arrayA=(1 2 3)
$ x=A
$ var=array$x[@]
$ echo ${!var}
1 2 3

NOTA

  • de man bash(expansión de parámetros):
    ${parameter}
           The value of parameter is substituted.
 The braces are required when parameter is a positional parameter with
  more than one

dígito, o cuando el parámetro va seguido de un carácter que no debe interpretarse como parte de su nombre.
* Si el primer carácter del parámetro es un signo de exclamación (!), Se introduce un nivel de indirección variable. Bash usa el valor de la variable formada a partir del resto del parámetro como el nombre de la variable; esta variable se expande y ese valor se usa en el resto de la sustitución, en lugar del valor del parámetro en sí. Esto se conoce como expansión indirecta. * Las excepciones a esto son las expansiones de $ {! Prefix *} y $ {! Name [@]} que se describen a continuación. El signo de exclamación debe seguir inmediatamente a la llave izquierda para introducir la indirección.

Gilles Quenot
fuente
¿Qué está !haciendo exactamente delante de la varvariable? ¿Cómo funciona eso? Parecía ser la sustitución de la historia en Google, pero no podía ver cómo funcionaba aquí.
Aaron
Ver mi publicación editada
Gilles Quenot
4

Si bien puede usar el acceso indirecto como se señala en otra respuesta , otra forma (en ksh y Bash 4.3 y posteriores) sería usar namerefs. Especialmente en el caso de las matrices, esto puede ser más útil, ya que puede indexar la matriz a través de nameref y no necesita colocar el índice en la variable utilizada como referencia.

arr1=(a b c)
arr2=(x y z)
typeset -n p=arr1    # or 'declare -n' 
echo "${p[1]}"       # prints 'b'

Esto no funciona a través del acceso indirecto:

q=arr2
echo "${!q}"         # prints 'x', the same as $arr2
echo "${!q[1]}"      # doesn't work, it tries to take q[1] as a reference

Como podría decir un programador en C, ${!q[1]}aquí actúa como si qfuera una matriz de punteros, en lugar de ser un puntero a una matriz.

ilkkachu
fuente
1
Esto funciona solo en la versión bash ≥ 4.3.
G-Man dice 'Restablece a Monica' el
1
arrayA=(1 2 3)
arrayB=(a b c)

ARG="$1"

eval echo \${array${ARG}[@]}

dataget (){ 
    eval echo \${array${1}[${2:-@}]}
}
$ dataget A
1 2 3
$ dataget A 0
1
$ dataget B 1
b

nota: escap cotes en caso de espacio !

eval dostuff \"\${array${1}[${2:-@}]}\"
Jonás
fuente
1

Esto requirió mucha prueba y error, pero finalmente funcionó.

Tomé un poco de inspiración de Youness. Pero todas las otras respuestas no ayudaron en mi viejo bash (suse11sp1 [3.2.51 (1) -release])

El bucle 'for' se negó a expandir la matriz indirecta, en su lugar, debe expandirla previamente, usarla para crear otra matriz con su nuevo nombre de variable. Mi ejemplo a continuación muestra un doble bucle, ya que ese es mi uso previsto.

THEBIGLOOP=(New_FOO New_BAR)

FOOthings=(1 2 3)
BARthings=(a b c)

for j in ${THEBIGLOOP[*]}
do
    TheNewVariable=$(eval echo \${${j#New_}things[@]})

    for i in $TheNewVariable
        do
            echo  $j $i" hello"
        echo
    done
done

Estoy usando # para eliminar el "Nuevo_" de la primera entrada de la matriz, luego concatenando con "cosas", para obtener "FOOthings". \ $ {} con echo y eval, luego haga lo suyo en orden sin arrojar errores, que se envuelve en un nuevo $ () y se le asigna el nuevo nombre de la variable.

$ Test.sh

New_FOO 1 hello

New_FOO 2 hello

New_FOO 3 hello

New_BAR a hello

New_BAR b hello

New_BAR c hello
ACTUALIZACIÓN ##### 2018/06/07

Recientemente descubrí un giro más sobre este tema. La variable creada no es en realidad una matriz, sino una cadena delimitada por espacios. Para la tarea anterior, esto estaba bien, debido a cómo funciona "for", no lee la matriz, se expande y luego se repite, vea el extracto a continuación:

for VARIABLE in 1 2 3 4 5 .. N
do
    command1
    command2
    commandN
done

Pero, luego necesitaba usarlo como una matriz. Para esto necesitaba realizar un paso más. Tomé el código literalmente por Dennis Williamson . Lo he probado y funciona bien.

IFS=', ' read -r -a TheNewVariable <<< ${TheNewVariable[@]}

El "IFS = ','" es una variable que contiene su deliminador. "leer" con "-a" corta y retroalimenta la picadura en la variable de matriz. Tenga en cuenta que esto no respeta las comillas, pero hay algunas opciones en lectura para administrar esto, por ejemplo, he eliminado el indicador -r que no necesitaba. Así que ahora he combinado esta adición en la creación de variables, lo que permite que los datos sean tratados y tratados como deberían.

THEBIGLOOP=(New_FOO New_BAR)

FOOthings=(1 2 3)
BARthings=(a b c)

for j in ${THEBIGLOOP[*]}
do

    IFS=', ' read -a TheNewVariable <<< $(eval echo \${${j#New_}things[@]})

    for i in ${TheNewVariable[@]}  #Now have to wrap with {} and expand with @
        do
            echo  $j $i" hello"
            echo  ${TheNewVariable[$i]}  #This would not work in the original code
        echo
    done
done
Stripy42
fuente
-1

de ninguna manera :(

si sus matrices son así de simples, entonces use matrices asociativas

    declare -A array
    array[A]="1 2 3"
    array[B]="a b c"

desafortunadamente, si sus arreglos son más complicados (por ejemplo array=( "a b" c )), eso no funcionaría. Luego, debe pensar más en otra forma de alcanzar su objetivo.

watael
fuente
¿Cuál es la razón del voto negativo? La matriz asociativa proporciona una buena manera de agrupar todo, suponiendo que todos mis elementos no contendrán espacio.
Aaron
2
@ Aaron Suponiendo que sus elementos no contienen espacios, ese es un diseño razonable. @Watael Supongo que comenzar la respuesta con "de ninguna manera" cuando el enfoque principal de su pregunta es claramente posible no fue una buena idea.
Gilles 'SO- deja de ser malvado'
-1

Utilizar eval

arrayA=(1 2 3)
ARG=arrayA
eval echo \${$ARG[@]} # equivalent to eval echo \${arrayA[@]}
                      # note that we escape the first '$' to prevent from 
                      # its parameter expansion before passing it to echo
MS.Kim
fuente
-1

Así es como crearía una variable con nombre dinámico (versión bash <4.3).

# Dynamically named array
my_variable_name="dyn_arr_names"
eval $my_variable_name=\(\)

# Adding by index to the array eg. dyn_arr_names[0]="bob"
eval $my_variable_name[0]="bob"

# Adding by pushing onto the array eg. dyn_arr_names+=(robert)
eval $my_variable_name+=\(robert\)

# Print value stored at index indirect
echo ${!my_variable_name[0]}

# Print value stored at index
eval echo \${$my_variable_name[0]}

# Get item count
eval echo \${#$my_variable_name[@]}

A continuación se muestra un grupo de funciones que se pueden usar para administrar matrices con nombres dinámicos (versión bash <4.3).

# Dynamically create an array by name
function arr() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
     # The following line can be replaced with 'declare -ag $1=\(\)'
     # Note: For some reason when using 'declare -ag $1' without the parentheses will make 'declare -p' fail
    eval $1=\(\)
}

# Insert incrementing by incrementing index eg. array+=(data)
function arr_insert() { 
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    eval $1[\$\(\(\${#${1}[@]}\)\)]=\$2
}

# Update an index by position
function arr_set() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    eval ${1}[${2}]=\${3}
}

# Get the array content ${array[@]}
function arr_get() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    eval echo \${${1}[@]}
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_at() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    [[ ! "$2" =~ ^(0|[-]?[1-9]+[0-9]*)$ ]] && { echo "Array index must be a number" 1>&2 ; return 1 ; }
    local v=$1
    local i=$2
    local max=$(eval echo \${\#${1}[@]})
    # Array has items and index is in range
    if [[ $max -gt 0 && $i -ge 0 && $i -lt $max ]]
    then 
        eval echo \${$v[$i]}
    fi
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_count() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable " 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    local v=${1}
    eval echo \${\#${1}[@]}
}



array_names=(bob jane dick)

for name in "${array_names[@]}"
do
    arr dyn_$name
done

echo "Arrays Created"
declare -a | grep "a dyn_"

# Insert three items per array
for name in "${array_names[@]}"
do
    echo "Inserting dyn_$name abc"
    arr_insert dyn_$name "abc"
    echo "Inserting dyn_$name def"
    arr_insert dyn_$name "def"
    echo "Inserting dyn_$name ghi"
    arr_insert dyn_$name "ghi"
done

for name in "${array_names[@]}"
do
    echo "Setting dyn_$name[0]=first"
    arr_set dyn_$name 0 "first"
    echo "Setting dyn_$name[2]=third"
    arr_set dyn_$name 2 "third"
done 

declare -a | grep "a dyn_"

for name in "${array_names[@]}"
do
    arr_get dyn_$name
done


for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name by index"
    # Print by index
    for (( i=0 ; i < $(arr_count dyn_$name) ; i++ ))
    do
        echo "dyn_$name[$i]: $(arr_at dyn_$name $i)"

    done
done

for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name"
    for n in $(arr_get dyn_$name)
    do
        echo $n
    done
done

A continuación se muestra un grupo de funciones que se pueden usar para administrar matrices con nombres dinámicos (versión bash> = 4.3).

# Dynamically create an array by name
function arr() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -g -a $1=\(\)   
}

# Insert incrementing by incrementing index eg. array+=(data)
function arr_insert() { 
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1
    r[${#r[@]}]=$2
}

# Update an index by position
function arr_set() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1 
    r[$2]=$3
}

# Get the array content ${array[@]}
function arr_get() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1 
    echo ${r[@]}
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_at() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable" 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    [[ ! "$2" =~ ^(0|[-]?[1-9]+[0-9]*)$ ]] && { echo "Array index must be a number" 1>&2 ; return 1 ; }
    declare -n r=$1 
    local max=${#r[@]}
    # Array has items and index is in range
    if [[ $max -gt 0 && $i -ge 0 && $i -lt $max ]]
    then 
        echo ${r[$2]}
    fi
}

# Get the value stored at a specific index eg. ${array[0]}  
function arr_count() {
    [[ ! "$1" =~ ^[a-zA-Z_]+[a-zA-Z0-9_]*$ ]] && { echo "Invalid bash variable " 1>&2 ; return 1 ; }
    declare -p "$1" > /dev/null 2>&1
    [[ $? -eq 1 ]] && { echo "Bash variable [${1}] doesn't exist" 1>&2 ; return 1 ; }
    declare -n r=$1
    echo ${#r[@]}
}



array_names=(bob jane dick)

for name in "${array_names[@]}"
do
    arr dyn_$name
done

echo "Arrays Created"
declare -a | grep "a dyn_"

# Insert three items per array
for name in "${array_names[@]}"
do
    echo "Inserting dyn_$name abc"
    arr_insert dyn_$name "abc"
    echo "Inserting dyn_$name def"
    arr_insert dyn_$name "def"
    echo "Inserting dyn_$name ghi"
    arr_insert dyn_$name "ghi"
done

for name in "${array_names[@]}"
do
    echo "Setting dyn_$name[0]=first"
    arr_set dyn_$name 0 "first"
    echo "Setting dyn_$name[2]=third"
    arr_set dyn_$name 2 "third"
done 

declare -a | grep 'a dyn_'

for name in "${array_names[@]}"
do
    arr_get dyn_$name
done


for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name by index"
    # Print by index
    for (( i=0 ; i < $(arr_count dyn_$name) ; i++ ))
    do
        echo "dyn_$name[$i]: $(arr_at dyn_$name $i)"

    done
done

for name in "${array_names[@]}"
do
    echo "Dumping dyn_$name"
    for n in $(arr_get dyn_$name)
    do
        echo $n
    done
done

Para obtener más detalles sobre estos ejemplos, visite Getting Bashed by Dynamic Arrays de Ludvik Jerabek

NOPx90
fuente
Tengo curiosidad por qué esto se desestima. ¿Hay algo mal / peligroso con el enfoque? Me gustaría usar funciones en bash <4.3.
stephenmm