Was ist der richtige Weg, um ein zugeordnetes Array in bash oder zsh zu sortieren?

8

Ich frage mich, wie ich das zugehörige Array in Bash sortieren soll. Ich habe das Handbuch ausprobiert, aber es scheint nichts mit Sortieren zu tun zu haben.

Die aktuelle Lösung besteht darin, alles wiederzugeben und ein externes Programm zu verwenden, d. H. key value | sort -k2

Das sieht für mich ineffizient aus.

Ein Beispiel für ein Array war:

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

Und ich werde nach den Top 2 der verwendeten IP-Adressen suchen, nämlich 192.168.1.1 und 192.168.2.2, dh ich muss dieses Array nach seinem Wert sortieren.

Gänseblümchen
quelle
Es klingt so, als ob das, was Sie erreichen möchten, zu komplex ist, um bashes einfach zu tun. Was versuchst du zu machen?
jw013
Und nach dem "richtigen" Weg zu fragen, heißt nur nach Ärger zu fragen. ;)
Lynxlynxlynx
Ist ein Wechsel zu zsh eine Option? Andernfalls ist es bei der Shell-Programmierung üblich, sich auf externe Tools zu verlassen.
Gilles 'SO - hör auf böse zu sein'
1
Wenn Sie diese Daten aus dem Parsen erhalten und Gawk zur Verfügung haben, neige ich dazu, alles in awk zu erledigen.
Jordan

Antworten:

4

Zsh verfügt über eine integrierte Methode zum Sortieren von Listen. Ich glaube jedoch nicht, dass es eine Möglichkeit gibt, die Werte zu sortieren, während die Korrelation mit den Schlüsseln mithilfe von Parametererweiterungs- und Indexflags beibehalten wird , was bedeutet, dass eine explizite Schleife erforderlich ist. Angenommen, Ihre Werte enthalten kein Nullzeichen, können Sie ein Array erstellen, das die Werte und Schlüssel enthält, die mit einem Nullzeichen dazwischen verknüpft sind, und dieses sortieren.

keys=("${(@k)A}")
values=("${(@v)A}")
combined=()
for ((i=1; i <= $#values; i++)) { combined[i]=($values[i]$'\0'$keys[i]); }
keys_sorted_by_decreasing_value=("${${(@On)combined}#*$'\0'}")
keys_of_the_top_two_values=("${(@)keys_sorted_by_decreasing_value[1,2]}")

EDIT by @sch: Die ersten 4 Zeilen können vereinfacht werden

combined=()
for k v ("${(@kv)A}") combined+=($k$'\0'$v)

Die Variablen keysund valuesenthalten die Schlüssel und Werte von Ain einer beliebigen, aber konsistenten Reihenfolge. Sie können schreiben, keys=(${(k)A})wenn keine leeren Schlüssel vorhanden sind, und dies gilt auch für Werte. keys_sorted_by_decreasing_valuesortiert Schlüssel lexikographisch, fügt das nFlag hinzu, um numerisch ( 9vorher 10) zu sortieren, und entfernt es, Owenn Sie in aufsteigender Reihenfolge sortieren möchten (in diesem Fall können die beiden obersten Werte mit dem Index erhalten werden [-2,-1]).

Ksh93 bietet die Möglichkeit, nur die Positionsparameter mit zu sortieren set -s. Dies existiert auch in zsh, aber nicht in bash 4.2. Angenommen, Ihre Werte enthalten keine Zeilenumbrüche oder Steuerzeichen, die vor Zeilenumbrüchen sortiert werden:

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'}

Das ist alles ziemlich komplex, also können Sie sich auch für die externe Sortierung entscheiden, die viel einfacher zu schreiben ist. Angenommen, weder Schlüssel noch Werte enthalten Steuerzeichen in ksh oder bash:

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.*//'
  ))
Gilles 'SO - hör auf böse zu sein'
quelle
3

Eine Shell ist vor allem ein Werkzeug, um andere Werkzeuge auszuführen. Es klingt für mich nach einer Programmiersprache wie Perl, Ruby, Python ...

Trotzdem gibt es hier eine mögliche Lösung für zsh.

In zsh können Sie eine sortierte Liste der Schlüssel eines zugeordneten Arrays ( ${(kOn)A}) oder der Werte ( ${(On)A}) abrufen, jedoch nicht direkt eine Liste der Schlüssel aus der sortierten Werteliste (AFAIK). Sie können jedoch Folgendes tun:

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]}")

Das heißt, ordne ( O) die Liste der Werte ( $A) numerisch ( n) und forjeden Wert v, addiere die kEy / vAlue-Paare, die mit dem Wert übereinstimmen $v( efür eine genaue Übereinstimmung, Rum die umgekehrte Liste basierend auf dem Wert und nicht dem Schlüssel zu erhalten) und füge diese dem hinzu Bassoziatives Array.

Dann erhalten Sie die sortierte Liste in 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

Und Sie können die ersten 2 Tasten mit auswählen

$ print -rl -- ${${(k)B}[1,2]}
192.168.8.1
192.168.1.1
Stéphane Chazelas
quelle
1

Der beste Weg, ein assoziatives Bash-Array nach KEY zu sortieren, besteht darin, es NICHT zu sortieren.

Rufen Sie stattdessen die Liste der SCHLÜSSEL ab, sortieren Sie diese Liste als Variable und durchlaufen Sie die Liste. Beispiel: Angenommen, Sie haben ein Array von IP-Adressen (Schlüsseln) und Hostnamen (Werten):

Alternative: Erstellen Sie eine neue Liste aus KEYs, konvertieren Sie sie in Zeilen, sortieren Sie sie, konvertieren Sie sie zurück in eine Liste und verwenden Sie sie, um das Array zu durchlaufen.

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

KEYS=`echo ${!ADDR[@]} | tr ' ' '\012' | sort | tr '\012' ' '`
for KEY in $KEYS; do
  VAL=${ADDR[$KEY]}
  echo "KEY=[$KEY] VAL=[$VAL]"
done
mz4wheeler
quelle
0

"Assoziatives Array" bedeutet häufig, dass die Daten im Array eine reale Bedeutung haben, was in Ihrem Fall der Fall ist. Die externe Unix-Sortierung ist ideal für diese Aufgabe, und nur wenige C-Programmierer können die Unix-Sortierung übertreffen. Speziell für Big Data können Sie Unix und Shell anpassen, in Scheiben schneiden, teilen und die volle Leistung von Unix und Shell erzielen. Aus diesem Grund kümmern sich so viele Shell- und Awk-Plattformen dort nicht um eine Sorte.

MeaCulpa
quelle