Matrizes associativas em scripts de shell

10

Eu vi um truque para implementar matrizes associativas em um script de shell. Por exemplo, print array["apples"]pode ser script como echo \$array$keyonde key = maçãs.

No entanto, não houve menção de como gerar as chaves para iterar sobre a matriz. A única maneira de pensar era em armazenar as chaves em uma variável delimitada por espaços, para que eu pudesse usar um loop for para iterar sobre a matriz.

Então, existe alguma outra maneira de armazenar as chaves para uso posterior?

EggHead
fonte
5
Se você está tentando usar arrays associativos em um shell script que poderia ser possível que seu projeto é muito complexo para um shell script :)
Martin von Wittich
@MartinvonWittich por quê? Eu tenho um script de shell que executa um script SQL em um dos três possíveis esquemas de banco de dados. O esquema necessário está incluído no nome do arquivo com uma abreviação. Eu preciso de um mapeamento entre essa abreviação e o nome do esquema real. Que melhor maneira do que uma matriz associativa, considerando-se os nomes de esquemas reais (não a abreviatura) podem diferir entre os ambientes, de modo que uma variável de matriz (cujos valores podem ser definidos apenas uma vez) é perfeito
Slav
2
@Slav Não estou argumentando contra matrizes associativas, apenas contra scripts de shell em que essa complexidade é necessária. Mas essa é apenas a minha preferência pessoal; Costumo começar a escrever um script de shell e, em seguida, reescrevê-lo imediatamente em Perl quando percebo que estou excedendo um certo limite de complexidade.
Martin von Wittich

Respostas:

19

Conchas com matrizes associativas

Alguns shells modernos fornecem matrizes associativas: ksh93, bash ≥4, zsh. No ksh93 e no bash, se afor uma matriz associativa, "${!a[@]}"é a matriz de suas chaves:

for k in "${!a[@]}"; do
  echo "$k -> ${a[$k]}"
done

No zsh, essa sintaxe funciona apenas no modo de emulação ksh. Caso contrário, você precisará usar a sintaxe nativa do zsh:

for k in "${(@k)a}"; do
  echo "$k -> $a[$k]"
done

${(k)a}também funciona se anão tiver uma chave vazia.

No zsh, você também pode fazer o loop em keys e values ​​ao mesmo tempo:

for k v ("${(@kv)a}") echo "$k -> $v"

Cascas sem matrizes associativas

Emular matrizes associativas em conchas que não as possuem é muito mais trabalhoso. Se você precisar de matrizes associativas, provavelmente é hora de trazer uma ferramenta maior, como ksh93 ou Perl.

Se você precisar de matrizes associativas em um mero shell POSIX, aqui está uma maneira de simulá-las, quando as chaves são restritas para conter apenas os caracteres 0-9A-Z_a-z(dígitos ASCII, letras e sublinhado). Sob essa suposição, as chaves podem ser usadas como parte dos nomes de variáveis. As funções abaixo atuam em uma matriz identificada por um prefixo de nomenclatura, o "tronco", que não deve conter dois sublinhados consecutivos.

## ainit STEM
## Declare an empty associative array named STEM.
ainit () {
  eval "__aa__${1}=' '"
}
## akeys STEM
## List the keys in the associatve array named STEM.
akeys () {
  eval "echo \"\$__aa__${1}\""
}
## aget STEM KEY VAR
## Set VAR to the value of KEY in the associative array named STEM.
## If KEY is not present, unset VAR.
aget () {
  eval "unset $3
        case \$__aa__${1} in
          *\" $2 \"*) $3=\$__aa__${1}__$2;;
        esac"
}
## aset STEM KEY VALUE
## Set KEY to VALUE in the associative array named STEM.
aset () {
  eval "__aa__${1}__${2}=\$3
        case \$__aa__${1} in
          *\" $2 \"*) :;;
          *) __aa__${1}=\"\${__aa__${1}}$2 \";;
        esac"
}
## aunset STEM KEY
## Remove KEY from the associative array named STEM.
aunset () {
  eval "unset __aa__${1}__${2}
        case \$__aa__${1} in
          *\" $2 \"*) __aa__${1}=\"\${__aa__${1}%%* $2 } \${__aa__${1}#* $2 }\";;
        esac"
}

(Aviso, código não testado. A detecção de erros para hastes e chaves sintaticamente inválidas não é fornecida.)

Gilles 'SO- parar de ser mau'
fonte
5

Não sei ao certo o que você quer dizer com loja, mas é possível iterar nas chaves usando a ${!array[@]}sintaxe:

$ typeset -A foo=([key1]=bar [key2]=baz);
$ echo "${!foo[@]}" 
key2 key1

Então, para iterar:

$ for key in "${!foo[@]}"; do echo "$key : ${foo[$key]}"; done
key2 : baz
key1 : bar

Encontrei um tutorial breve e agradável sobre isso aqui .


Conforme apontado nos comentários abaixo, matrizes associativas foram adicionadas na bashversão 4. Veja aqui um artigo de jornal do Linux sobre o assunto.

terdon
fonte
1
(bash version 4 only)Isso é uma coisa importante a ser observada. Tradicionalmente, as bashmatrizes são apenas numéricas.
Ricky feixe
1
Você pode usar em typesetvez de declarenos seus exemplos. Isso os tornaria portáveis ​​entre o bash 4 e o ksh93, que primeiro implementou matrizes associativas de shell.
Jlliagre
0

Cascas sem matrizes associativas

Não é tão difícil quando as teclas são restritas a [0-9A-Za-z_](números, letras, sublinhado).

O truque é, em vez de armazenar na matriz [ $ key ], armazenar nas variáveis ​​array_ $ key .

Conjunto:

eval "array_$key='$value'"

Obter:

value=`eval echo '$'array_$key`

Nota: Os valores não podem conter '(aspas simples).

Marián Černý
fonte
-1

isso funciona no bash

cert="first"
web="second"
declare -A assoc_array=(["cert"]="${cert}" ["web"]="${web}")
echo "first is" ${assoc_array[cert]}
echo "second is" ${assoc_array[web]}

OU

#loop
for i in "${assoc_array[@]}"
do
   echo "$i"
done

Não há necessidade de usar eval afaik

JamesD
fonte
Eu acredito que você perdeu o objetivo da pergunta.
G-Man diz 'Reinstate Monica'