impressão de matriz associativa BASH

13

Existe uma maneira de imprimir uma matriz inteira ([chave] = valor) sem fazer um loop sobre todos os elementos?

Suponha que criei um array com alguns elementos:

declare -A array
array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)

Eu posso imprimir todo o array com

for i in "${!array[@]}"
do
echo "${i}=${array[$i]}"
done

No entanto, parece que o bash já sabe como obter todos os elementos do array em um "go" - ambas as chaves ${!array[@]} e valores ${array[@]} .

Existe uma maneira de fazer bash imprimir essa informação sem o loop?

Edit:
typeset -p array faz isso!
No entanto, não consigo remover o prefixo e o sufixo em uma única substituição:

a="$(typeset -p array)"
b="${a##*(}"
c="${b%% )*}"

Existe uma maneira mais limpa de obter / imprimir somente a parte key = value da saída?

    
por Dani_l 22.05.2017 / 18:13

5 respostas

13

Eu acho que você está perguntando duas coisas diferentes lá.

Is there a way to make bash print this info without the loop?

Sim, mas eles não são tão bons quanto usar o loop.

Is there a cleaner way to get/print only the key=value portion of the output?

Sim, o loop for . Ele tem as vantagens de não exigir programas externos, é simples e facilita bastante o controle do formato de saída exato sem surpresas.

Qualquer solução que tente manipular a saída de declare -p ( typeset -p ) tem que lidar com a) a possibilidade de as variáveis conterem parênteses ou colchetes, b) as citações que declare -p tem que adicionar para fazer sua saída válida para o shell.

Por exemplo, sua expansão b="${a##*(}" come alguns dos valores, se qualquer chave / valor contiver um parêntese de abertura. Isso porque você usou ## , que remove o prefixo maior . O mesmo para c="${b%% )*}" . Embora você possa, é claro, combinar exatamente com o clichê impresso por declare , você ainda teria dificuldades se não quisesse todas as citações.

Isso não parece muito bom, a menos que você precise.

$ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
$ declare -p array
declare -A array='([def]="\"foo bar\"" [abc]="'\''foobar'\''" )'

Com o for loop, é mais fácil escolher o formato de saída como você gosta:

# without quoting
$ for x in "${!array[@]}"; do printf "[%s]=%s\n" "$x" "${array[$x]}" ; done
[def]="foo bar"
[abc]='foobar'

# with quoting
$ for x in "${!array[@]}"; do printf "[%q]=%q\n" "$x" "${array[$x]}" ; done
[def]=\"foo\ bar\"
[abc]=\'foobar\'

A partir daí, também é simples alterar o formato de saída (remova os colchetes ao redor da chave, coloque todos os pares chave / valor em uma única linha ...). Se você precisar de citar algo diferente do próprio shell, ainda precisará fazer isso sozinho, mas pelo menos terá os dados brutos para trabalhar. (Se você tiver novas linhas nas chaves ou valores, provavelmente precisará de algumas citações.)

Com um Bash atual (4.4, eu acho), você também pode usar printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}" em vez de printf "%q=%q" . Ele produz um formato um pouco mais agradável, mas é claro que é um pouco mais trabalhoso lembrar de escrever. (e cita o caso de canto de @ como chave de matriz, que %q não cita.)

Se o loop for parecer muito cansado para escrever, salve-o em alguma parte (sem citar aqui):

printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ;  }  

E então use isso:

$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)

Funciona com matrizes indexadas também:

$ b=(abba acdc)
$ printarr b
0=abba
1=acdc
    
por 23.05.2017 / 00:18
9
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'

2 garfo

Talvez isso:

printf "%s\n" "${!array[@]}"
a2
a1
f50
zz
b1

printf "%s\n" "${array[@]}"
2
1
abcd
Hello World
bbb

printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t
a2                              2
a1                              1
f50                             abcd
zz                              Hello World
b1                              bbb

3 garfos

ou isto:

paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}")
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Sem garfo

para ser comparado com

for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb

Comparação de tempos de execução

Como a última sintaxe não usa fork, eles podem ser mais rápidos:

time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
      5      11      76
real    0m0.005s
user    0m0.000s
sys     0m0.000s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
      5       6      41
real    0m0.008s
user    0m0.000s
sys     0m0.000s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
      5       6      41
real    0m0.002s
user    0m0.000s
sys     0m0.001s

Mas essa afirmação não permanece verdadeira se a matriz se tornar grande; Se a redução de garfos for eficiente para pequenos processos, o uso de ferramentas dedicadas é mais eficiente para processos maiores.

for i in {a..z}{a..z}{a..z};do array[$i]=$RANDOM;done


time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
  17581   35163  292941
real    0m0.150s
user    0m0.124s
sys     0m0.036s

time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
  17581   17582  169875
real    0m0.140s
user    0m0.000s
sys     0m0.004s

time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
  17581   17582  169875
real    0m0.312s
user    0m0.268s
sys     0m0.076s

Comentário

Como ambas as soluções ( bifurcadas ) usam o alinhamento , nenhuma delas funcionará se qualquer variável contiver uma nova linha . Nesse caso, a única maneira é um loop for .

    
por 22.05.2017 / 18:30
2

Se você estiver procurando por um shell com melhor suporte à matriz associativa, tente zsh .

Em zsh (onde matrizes associativas foram adicionadas em 1998, comparado a 1993 para ksh93 e 2009 para bash), $var ou ${(v)var} expande para os valores (não vazios) do hash, ${(k)var} para as chaves (não vazias) (na mesma ordem) e ${(kv)var} para as chaves e valores.

Para preservar os valores vazios, como para arrays, é necessário citar e usar o sinalizador @ .

Então, para imprimir as chaves e os valores, é só uma questão de

printf '%s => %s\n' "${(@kv)var}"

Embora seja responsável por um hash possivelmente vazio, você deve fazer:

(($#var)) &&  printf '%s => %s\n' "${(@kv)var}"

Observe também que o zsh usa uma sintaxe de definição de array muito mais sensível e útil do que a ksh93 (copiada por bash ):

typeset -A var
var=(k1 v1 k2 v2 '' empty '*' star)

O que torna muito mais fácil copiar ou mesclar matrizes associativas:

var2=("${(@kv)var1}")
var3+=("${(@kv)var2}")
var4=("${@kv)var4}" "${(@kv)var5}")

(não é possível copiar facilmente um hash sem um loop com bash e observe que bash atualmente não suporta chaves vazias ou chaves / valores com bytes NUL).

Veja também os recursos de compactação de matriz zsh , que você normalmente precisará para trabalhar com matrizes associativas:

keys=($(<keys.txt)) values=($(<values.txt))
hash=(${keys:^values})
    
por 23.05.2017 / 08:51
1

Como o typeset faz o que você deseja, por que não apenas editar sua saída?

typeset -p array | sed s/^.*\(// | tr -d ")\'\""  | tr "[" "\n" | sed s/]=/' = '/

a2 = 2  
a1 = 1  
b1 = bbb 

Onde

array='([a2]="2" [a1]="1" [b1]="bbb" )'

Detalhável, mas é muito fácil ver como a formatação funciona: basta executar o pipeline com progressivamente mais comandos sed e tr . Modifique-os para se adequar aos gostos de impressão.

    
por 22.05.2017 / 21:36
1

Mais uma opção é listar todas as variáveis e grep para aquela que você quer.

set | grep -e '^aa='

Eu uso isso para depuração. Eu duvido que seja muito produtivo, pois lista todas as variáveis.

Se você estivesse fazendo isso com frequência, poderia fazer uma função como esta:

aap() { set | grep -e "^$1="; }

Infelizmente, quando verificamos o desempenho usando o tempo:

$ time aap aa aa=([0]="abc") . real 0m0.014s user 0m0.003s sys 0m0.006s

Portanto, se você estava fazendo isso com muita frequência, você desejaria a versão NO FORKS do @Fauri porque é muito mais rápida.

    
por 12.10.2018 / 18:26