Como usar uma variável como parte de um nome de matriz

11

Eu tenho duas matrizes:

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

e quero imprimir um deles usando um argumento de linha de comando, ou seja, sem nenhum if else.

Eu tentei algumas variações na sintaxe sem sucesso. Eu estou querendo fazer algo assim:

ARG="$1"

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

mas recebo um erro de "substituição incorreta". Como posso conseguir isso?

Aaron
fonte
Isso não é enfaticamente uma festa idiomática. Por favor, não faça isso.
Curinga

Respostas:

22

Tente fazer isso:

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

NOTA

  • from man bash(expansão de parâmetro):
    ${parameter}
           The value of parameter is substituted.
 The braces are required when parameter is a positional parameter with
  more than one

dígito ou quando o parâmetro é seguido por um caractere que não deve ser interpretado como parte de seu nome.
* Se o primeiro caractere do parâmetro for um ponto de exclamação (!), É introduzido um nível de indireção variável. Bash usa o valor da variável formada a partir do restante do parâmetro como o nome da variável; essa variável é expandida e esse valor é usado no restante da substituição, em vez do valor do próprio parâmetro. Isso é conhecido como expansão indireta. * As exceções a isso são as expansões de $ {! Prefix *} e $ {! Name [@]} descritas abaixo. O ponto de exclamação deve seguir imediatamente a chave esquerda para introduzir a indireção.

Gilles Quenot
fonte
O que exatamente está !fazendo na frente da varvariável? Como isso funciona, parecia substituir a história do Google, mas eu não conseguia ver como isso funcionava aqui.
Aaron
Veja meu post editado
Gilles Quenot,
4

Embora você possa usar o acesso indireto, conforme indicado em outra resposta , outra maneira (no ksh e no Bash 4.3 e mais recente) seria usar o namerefs. Especialmente no caso de matrizes, isso pode ser mais útil, pois você pode indexar a matriz através do nameref e não precisa colocar o índice na variável usada como referência.

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

Isso não funciona através do acesso indireto:

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 um programador C pode colocar, ${!q[1]}aqui age como se houvesse quma matriz de ponteiros, em vez de ser um ponteiro para uma matriz.

ilkkachu
fonte
1
Isso funciona apenas na versão bash ≥ 4.3.
G-Man diz 'Reinstate Monica'
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: escape cotes em caso de espaço !

eval dostuff \"\${array${1}[${2:-@}]}\"
Jonah
fonte
1

Foram necessárias muitas tentativas e erros, mas eventualmente funcionaram.

Tomei alguma inspiração de Youness. Mas todas as outras respostas não ajudaram na minha antiga festança (suse11sp1 [lançamento do 3.2.51 (1)])

O loop 'for' recusou-se a expandir a matriz indireta; em vez disso, você precisa pré-expandi-la. Use isso para criar outra matriz com seu novo nome de variável. Meu exemplo abaixo mostra um loop duplo, pois esse é o meu uso pretendido.

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

Estou usando # para excluir o "New_" da primeira entrada da matriz e concatenar com "things" para obter "FOOthings". \ $ {} com echo e eval, faça suas tarefas em ordem sem gerar erros, que são agrupados em um novo $ () e atribuídos ao novo nome da variável.

$ 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
ATUALIZAÇÃO ##### 2018/06/07

Eu descobri recentemente mais uma rodada sobre esse problema. A variável criada não é realmente uma matriz, mas uma sequência delimitada por espaço. Para a tarefa acima, isso foi bom, por causa de como "for" funciona, ele não lê a matriz, é expandido e depois percorre o loop, veja o extrato abaixo:

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

Mas, então eu precisava usá-lo como uma matriz. Para isso, eu precisava executar mais uma etapa. Eu peguei o código literalmente por Dennis Williamson . Eu testei e funciona bem.

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

O "IFS = ','" é uma variável que contém seu deliminador. "read" com "-a" corta e alimenta a picada de volta na variável da matriz. Observe que isso não respeita as aspas, mas existem algumas opções na leitura para gerenciar isso; por exemplo, removi o sinalizador -r do qual não precisava. Portanto, agora combinei essa adição na criação de variáveis, que permite que os dados sejam tratados e endereçados como deveriam.

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
fonte
-1

de jeito nenhum :(

se suas matrizes forem tão simples, use matrizes associativas

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

infelizmente, se suas matrizes forem mais complicadas (por exemplo array=( "a b" c )), isso não funcionaria. Então, você precisa pensar mais sobre outra maneira de alcançar seu objetivo.

watael
fonte
Qual é a razão do voto negativo? A matriz associativa fornece uma boa maneira de agrupar tudo, assumindo que todos os meus elementos não terão espaço.
Aaron
2
@ Aaron Supondo que seus elementos não contenham espaços, esse é um design razoável. Atawatael Eu acho que começar a resposta com “de jeito nenhum” quando o foco principal da sua pergunta é claramente possível não era uma boa ideia.
Gilles 'SO- stop be evil' (
-1

Usar 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
fonte
-1

É assim que você criaria uma variável nomeada dinamicamente (versão 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[@]}

Abaixo está um grupo de funções que podem ser usadas para gerenciar matrizes nomeadas dinamicamente (versão 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

Abaixo está um grupo de funções que podem ser usadas para gerenciar matrizes nomeadas dinamicamente (versão 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 obter mais detalhes sobre esses exemplos, visite Getting Bashed by Dynamic Arrays de Ludvik Jerabek

NOPx90
fonte
Estou curioso para saber por que isso é prejudicado. Existe algo errado / perigoso com a abordagem. Gostaria de usar funções no bash <4.3.
stephenmm