Qual é o caminho certo para classificar uma matriz associada no bash ou zsh?

7

Eu estou querendo saber como devo classificar o array associado no bash? Eu tentei o manual, mas parece nada relacionado a ordenar.

A solução atual ecoa tudo e usa um programa externo, por exemplo, key value | sort -k2

Isso parece ineficiente para mim.

Um exemplo de matriz foi:

A['192.168.2.2']=5
A['192.168.3.2']=1
A['192.168.1.1']=9

E eu vou estar procurando o top 2 usado endereço IP, que é 192.168.1.1 e 192.168.2.2, ou seja, eu preciso classificar essa matriz por seu valor.

    
por daisy 16.10.2012 / 16:57

4 respostas

3

O Zsh tem uma maneira interna de ordenar listas. No entanto, não acho que haja uma maneira de classificar os valores, mantendo a correlação com as chaves usando sinalizadores de expansão de parâmetro e sinalizadores de subscrito , o que significa que um sinal explícito loop é necessário. Supondo que seus valores não contenham um caractere nulo, você pode construir uma matriz contendo os valores e chaves concatenados com um caractere nulo no meio e ordenar isso.

keys=("${(@k)A}")
values=("${(@v)A}")
combined=()
for ((i=1; i <= $#values; i++)) { combined[i]=($values[i]$'
combined=()
for k v ("${(@kv)A}") combined+=($k$'
keys=("${!A[@]}")
combined=()
for ((i=0; i <= ${#keys}; i++)); do combined[i]=(${A[${keys[$i]}]}$'\n'${keys[$i]}); done
set -A sorted -s "${combined[@]}"
top_combined=${sorted[${#sorted[@]}-1]}  # -2 for the next-to-largest, etc.
top_key=${top_combined#*$'\n'}
'$v)
'$keys[i]); } keys_sorted_by_decreasing_value=("${${(@On)combined}#*$'
IFS=$'\n'; set -f
keys_sorted_by_decreasing_value=($(
    for k in "${!A[@]}"; do printf '%s\t%s\n' "${A[$k]}" "$k"; done |
    sort | sed $'s/\t.*//'
  ))
'}") keys_of_the_top_two_values=("${(@)keys_sorted_by_decreasing_value[1,2]}")

EDIT by @sch: as primeiras 4 linhas podem ser simplificadas para

keys=("${(@k)A}")
values=("${(@v)A}")
combined=()
for ((i=1; i <= $#values; i++)) { combined[i]=($values[i]$'
combined=()
for k v ("${(@kv)A}") combined+=($k$'
keys=("${!A[@]}")
combined=()
for ((i=0; i <= ${#keys}; i++)); do combined[i]=(${A[${keys[$i]}]}$'\n'${keys[$i]}); done
set -A sorted -s "${combined[@]}"
top_combined=${sorted[${#sorted[@]}-1]}  # -2 for the next-to-largest, etc.
top_key=${top_combined#*$'\n'}
'$v)
'$keys[i]); } keys_sorted_by_decreasing_value=("${${(@On)combined}#*$'
IFS=$'\n'; set -f
keys_sorted_by_decreasing_value=($(
    for k in "${!A[@]}"; do printf '%s\t%s\n' "${A[$k]}" "$k"; done |
    sort | sed $'s/\t.*//'
  ))
'}") keys_of_the_top_two_values=("${(@)keys_sorted_by_decreasing_value[1,2]}")

As variáveis keys e values contêm as chaves e valores de A em uma ordem arbitrária, mas consistente. Você pode escrever keys=(${(k)A}) se não houver chaves vazias e, da mesma forma, para valores. keys_sorted_by_decreasing_value classifica as chaves lexicograficamente, adiciona o n para ordenar numericamente ( 9 antes de 10 ) e remove O se você quiser ordenar em ordem crescente (caso em que os dois primeiros valores podem ser obtidos com o subscrito [-2,-1] ).

O Ksh93 tem uma maneira de classificar apenas os parâmetros posicionais, com set -s ; isto também existe em zsh mas não em bash 4.2. Presumindo que seus valores não contêm novas linhas ou caracteres de controle que classificam antes de novas linhas:

%pre%

Isso tudo é muito complexo, então você também pode optar pelo tipo externo, que é muito mais fácil de escrever. Assumindo que nem chaves nem valores contenham caracteres de controle, em ksh ou bash:

%pre%     
por 17.10.2012 / 14:56
2

Um shell é antes de tudo uma ferramenta para executar outras ferramentas. Parece-me que você está atrás de uma linguagem de programação como perl, ruby, python ...

Dito isto, aqui está alguma solução possível para o zsh.

Em zsh, você pode obter uma lista classificada das chaves de uma matriz associada ( ${(kOn)A} ) ou dos valores ( ${(On)A} ), mas não diretamente uma lista de chaves da lista de valores classificada (AFAIK), mas você pode fazer coisas como:

typeset -A A B
A=(
  192.168.2.2 5
  192.168.3.2 1
  192.168.1.1 9
  192.168.8.1 9
)

for v ("${(@nO)A}") B+=("${(@kv)A[(eR)$v]}")

Ou seja, ordene ( O ) a lista de valores ( $A ) numericamente ( n ) e for each v alue, adicione os pares de correspondência k ey / v o valor $v ( e para correspondência exata, R para obter a lista inversa com base no valor, não chave) e adicione isso à matriz associativa B .

Em seguida, você obterá a lista classificada em B :

$ printf '%s => %s\n' "${(@kv)B}"
192.168.8.1 => 9
192.168.1.1 => 9
192.168.2.2 => 5
192.168.3.2 => 1

E você pode selecionar as 2 primeiras chaves com

$ print -rl -- ${${(k)B}[1,2]}
192.168.8.1
192.168.1.1
    
por 17.10.2012 / 12:06
0

"Matriz associativa" geralmente significa que os dados na matriz têm significado no mundo real, que é o seu caso. A ordenação externa de unix é ideal para esta tarefa, e poucos programadores C podem superar a classificação unix. Especialmente para big data você pode customizar, fatiar, garfo, trazer poder total de unix e shell. É por isso que tantas plataformas shell e awk não se incomodam com isso.

    
por 19.10.2012 / 08:48
0

A melhor maneira de classificar uma matriz associativa bash por KEY é NÃO classificá-la.

Em vez disso, obtenha a lista de KEYS, classifique essa lista como uma variável e faça uma iteração na lista. Exemplo: suponha que você tenha uma matriz de endereços IP (chaves) e nomes de host (valores):

Alternativa: Crie uma nova lista a partir de KEYs, converta em linhas, ordene-a, converta-a novamente em lista e use-a para percorrer o array.

declare -A ADDR
ADDR[192.168.1.1]="host1"
ADDR[192.168.1.2]="host2"
etc...

KEYS='echo ${!ADDR[@]} | tr ' ' '2' | sort | tr '2' ' ''
for KEY in $KEYS; do
  VAL=${ADDR[$KEY]}
  echo "KEY=[$KEY] VAL=[$VAL]"
done
    
por 06.02.2019 / 19:24

Tags