![2 garfos](https://rvso.com/image/112013/2%20garfos.png)
Existe uma maneira de imprimir um array inteiro ([key]=value) sem fazer um loop em todos os elementos?
Suponha que eu criei um array com alguns elementos:
declare -A array
array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)
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 de uma só vez - tanto chaves ${!array[@]}
quanto valores ${array[@]}
.
Existe uma maneira de fazer o bash imprimir essas informações sem o loop?
Editar:
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 apenas a parte chave=valor da saída?
Responder1
Acho que você está perguntando duas coisas diferentes aí.
Existe uma maneira de fazer o bash imprimir essas informações sem o loop?
Sim, mas não são tão bons quanto usar o loop.
Existe uma maneira mais limpa de obter/imprimir apenas a parte chave=valor da saída?
Sim, o for
circuito. Tem a vantagem de não requer programas externos, é simples e torna bastante fácil controlar o formato exato de saída sem surpresas.
Qualquer solução que tente lidar com a saída de declare -p
( typeset -p
) tem que lidar com a) a possibilidade das próprias variáveis conterem parênteses ou colchetes, b) as aspas que declare -p
devem ser adicionadas para tornar sua saída uma entrada válida para o shell.
Por exemplo, sua expansão b="${a##*(}"
consome alguns dos valores, se alguma chave/valor contiver um parêntese de abertura. Isso ocorre porque você usou ##
, que remove omais longoprefixo. O mesmo para c="${b%% )*}"
. Embora você possa, é claro, corresponder ao padrão impresso com declare
mais exatidão, ainda seria difícil se não quisesse todas as citações que ele faz.
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 que desejar:
# 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 ésimplespara alterar o formato de saída (remova os colchetes ao redor da chave, coloque todos os pares chave/valor em uma única linha...). Se precisar de cotação para algo diferente do próprio shell, você 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 de citação um pouco melhor, mas é claro que é um pouco mais trabalhoso lembrar de escrever. (E cita o caso de canto @
como chave de matriz, que %q
não cita.)
Se o loop for parecer muito cansativo para escrever, salve-o como uma função em algum lugar (sem citar aqui):
printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ; }
E então é só usar isso:
$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)
Também funciona com matrizes indexadas:
$ b=(abba acdc)
$ printarr b
0=abba
1=acdc
Responder2
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'
2 garfos
Talvez isto:
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
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 poderiam 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 esta afirmação não permanece verdadeira se o array se tornar grande; se a redução de garfos é eficiente para processos pequenos, 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
Observação
Como ambos (bifurcado) soluções usamalinhamento, nenhum deles funcionará se alguma variável contiver umnova linha. Neste caso, a única maneira é um for
loop.
Resposta mais robusta e detalhada no StackOverflow
Responder3
Bash 5.1 permite uma maneira muito direta de exibir matrizes associativas usando o K
valor como em ${arr[@]@K}
:
$ declare -A arr
$ arr=(k1 v1 k2 v2)
$ printf "%s\n" "${arr[@]@K}"
k1 "v1" k2 "v2"
DeDocumento de descrição do Bash 5.1:
ah. Nova transformação de parâmetro `K' para exibir matrizes associativas como pares de valores-chave.
Está bem explicado noManual de referência do Bash → 3.5.3 Expansão de parâmetros do shell:
${parameter@operator}
K
Produz uma versão possivelmente citada do valor do parâmetro, exceto que imprime os valores de matrizes indexadas e associativas como uma sequência de pares de valores-chave entre aspas (consulte Matrizes).
Responder4
Desdeformatadofaz o que você quer, por que não apenas editar sua saída?
typeset -p array | sed s/^.*\(// | tr -d ")\'\"" | tr "[" "\n" | sed s/]=/' = '/
dá
a2 = 2
a1 = 1
b1 = bbb
Onde
array='([a2]="2" [a1]="1" [b1]="bbb" )'
Detalhado, mas é muito fácil ver como a formatação funciona: basta executar o pipeline com progressivamente mais dosedetrcomandos. Modifique-os para se adequarem aos gostos de impressão.