823 Stimmen

Wie definiert man Hash-Tabellen in der Bash?

Was ist das Äquivalent von Python-Wörterbücher sondern in Bash (sollte unter OS X und Linux funktionieren).

6 Stimmen

Bash soll ein Python/Perl-Skript ausführen... Das ist so viel flexibler!

1 Stimmen

1373voto

lhunath Punkte 111281

Bash 4

Bash 4 unterstützt diese Funktion von Haus aus. Stellen Sie sicher, dass der Hashbang Ihres Skripts #!/usr/bin/env bash o #!/bin/bash damit Sie nicht am Ende mit sh . Stellen Sie sicher, dass Sie Ihr Skript entweder direkt ausführen, oder führen Sie script con bash script . (Nicht die tatsächliche Ausführung eines Bash-Skripts mit Bash する geschehen, und werden wirklich verwirrend!)

Sie deklarieren ein assoziatives Array, indem Sie dies tun:

declare -A animals

Sie können es mit dem normalen Array-Zuweisungsoperator mit Elementen auffüllen. Wenn Sie zum Beispiel eine Map mit folgenden Elementen haben wollen animal[sound(key)] = animal(value) :

animals=( ["moo"]="cow" ["woof"]="dog")

Oder deklarieren und instanziieren Sie in einer Zeile:

declare -A animals=( ["moo"]="cow" ["woof"]="dog")

Dann verwenden Sie sie wie normale Arrays. verwenden

  • animals['key']='value' zum Einstellen des Wertes

  • "${animals[@]}" um die Werte zu erweitern

  • "${!animals[@]}" (Beachten Sie die ! ), um die Tasten zu erweitern

Vergessen Sie nicht, sie zu zitieren:

echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done

Bash 3

Vor Bash 4 haben Sie keine assoziativen Arrays. Nicht verwenden eval ihnen nachzueifern . Vermeiden Sie eval wie die Pest, denn sie ist die Plage des Shell-Scripting. Der wichtigste Grund ist, dass eval Ihre Daten als ausführbaren Code behandelt (es gibt noch viele andere Gründe).

Zuallererst : Erwägen Sie ein Upgrade auf bash 4. Das wird den ganzen Prozess für Sie viel einfacher machen.

Wenn es einen Grund gibt, warum Sie nicht aufrüsten können, declare ist eine weitaus sicherere Option. Sie wertet Daten nicht als Bash-Code aus, wie eval und erlaubt daher nicht so leicht die Einspeisung von beliebigem Code.

Bereiten wir die Antwort vor, indem wir die Begriffe einführen:

Erstens, die Umleitung.

$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow

Zweitens, declare :

$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow

Bringen Sie sie zusammen:

# Set a value:
declare "array_$index=$value"

# Get a value:
arrayGet() { 
    local array=$1 index=$2
    local i="${array}_$index"
    printf '%s' "${!i}"
}

Benutzen wir es:

$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow

Anmerkung: declare kann nicht in eine Funktion eingefügt werden. Jede Verwendung von declare innerhalb einer Bash-Funktion macht die von ihr erzeugte Variable . auf den Geltungsbereich dieser Funktion, was bedeutet, dass wir damit nicht auf globale Arrays zugreifen oder sie verändern können. (In Bash 4 können Sie mit declare -g um globale Variablen zu deklarieren - aber in der Bash 4 können Sie von vornherein assoziative Arrays verwenden und so diesen Workaround vermeiden).

Zusammenfassung:

  • Aktualisieren Sie auf bash 4 und verwenden Sie declare -A für assoziative Arrays.
  • Verwenden Sie die declare Option, wenn Sie nicht aktualisieren können.
  • Erwägen Sie die Verwendung von awk und das Thema ganz zu vermeiden.

0 Stimmen

+1 für declare -A Ich kann nicht glauben, dass ich es noch nie benutzt habe! Ich habe 10 Jahre lang Bash programmiert.

1 Stimmen

Ich verwende Bash 4.2, aber declare -A beschwert sich -A ist keine gültige Option... irgendeine Idee warum? linux distr ist SUSE...

1 Stimmen

@Richard: Vermutlich benutzen Sie nicht wirklich Bash. Ist Ihr Hashbang sh anstelle von bash, oder rufen Sie Ihren Code sonst mit sh auf? Versuchen Sie, dies direkt vor Ihr declare zu setzen: echo "$BASH_VERSION $POSIXLY_CORRECT", es sollte Folgendes ausgeben 4.x y no y .

160voto

Bubnoff Punkte 3647

Es gibt die Parametersubstitution, die allerdings auch un-PC sein kann ... wie die Indirektion.

#!/bin/bash

# Array pretending to be a Pythonic dictionary
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

for animal in "${ARRAY[@]}" ; do
    KEY="${animal%%:*}"
    VALUE="${animal##*:}"
    printf "%s likes to %s.\n" "$KEY" "$VALUE"
done

printf "%s is an extinct animal which likes to %s\n" "${ARRAY[1]%%:*}" "${ARRAY[1]##*:}"

Der BASH-4-Weg ist natürlich besser, aber wenn man einen Hack braucht ... dann reicht ein Hack. Sie könnten das Array/Hash mit ähnlichen Techniken durchsuchen.

6 Stimmen

Ich würde das ändern in VALUE=${animal#*:} um den Fall zu schützen, dass ARRAY[$x]="caesar:come:see:conquer"

4 Stimmen

Es ist auch nützlich, doppelte Anführungszeichen um ${ARRAY[@]} zu setzen, falls Leerzeichen in den Schlüsseln oder Werten vorkommen, wie in for animal in "${ARRAY[@]}"; do

1 Stimmen

Aber ist die Effizienz nicht ziemlich schlecht? Ich denke an O(n*m), wenn man mit einer anderen Liste von Schlüsseln vergleichen will, statt an O(n) mit richtigen Hashmaps (konstante Suchzeit, O(1) für einen einzelnen Schlüssel).

132voto

aktivb Punkte 1742

Das ist es, was ich hier gesucht habe:

declare -A hashmap
hashmap["key"]="value"
hashmap["key2"]="value2"
echo "${hashmap["key"]}"
for key in ${!hashmap[@]}; do echo $key; done
for value in ${hashmap[@]}; do echo $value; done
echo hashmap has ${#hashmap[@]} elements

Dies funktionierte bei mir nicht mit Bash 4.1.5:

animals=( ["moo"]="cow" )

2 Stimmen

Beachten Sie, dass der Wert keine Leerzeichen enthalten darf, sonst fügen Sie mehrere Elemente auf einmal hinzu

16 Stimmen

Upvote für die Syntax hashmap["key"]="value", die auch ich in der ansonsten fantastisch akzeptierten Antwort vermisst habe.

1 Stimmen

@rubo77 Schlüssel auch nicht, es werden mehrere Schlüssel hinzugefügt. Gibt es eine Möglichkeit, dies zu umgehen?

44voto

lovasoa Punkte 5768

Verwenden Sie einfach das Dateisystem

Das Dateisystem ist eine Baumstruktur, die als Hash-Map verwendet werden kann. Ihre Hash-Tabelle ist ein temporäres Verzeichnis, Ihre Schlüssel sind Dateinamen und Ihre Werte sind Dateiinhalte. Der Vorteil ist, dass es große Hashmaps verarbeiten kann und keine spezielle Shell benötigt.

Erstellung einer Hashtabelle

hashtable=$(mktemp -d)

Ein Element hinzufügen

echo $value > "$hashtable/$key"

Ein Element lesen

value=$(< "$hashtable/$key")

Leistung

Natürlich ist es langsam, aber nicht dass langsam. Ich habe es auf meinem Rechner getestet, mit einer SSD und btrfs und zwar um 3000 Elemente lesen/schreiben pro Sekunde .

1 Stimmen

Welche Version der Bash unterstützt mkdir -d ? (Nicht 4.3, auf Ubuntu 14. Ich würde auf mkdir /run/shm/foo oder ob dadurch der Arbeitsspeicher gefüllt wird, mkdir /tmp/foo .)

3 Stimmen

Vielleicht mktemp -d stattdessen gemeint war?

2 Stimmen

Was ist der Unterschied zwischen $value=$(< $hashtable/$key) y value=$(< $hashtable/$key) ? Danke!

30voto

Al P. Punkte 595

Sie können die hput()/hget()-Schnittstelle weiter modifizieren, so dass Sie wie folgt benannte Hashes haben:

hput() {
    eval "$1""$2"='$3'
}

hget() {
    eval echo '${'"$1$2"'#hash}'
}

und dann

hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`

So können Sie andere Karten definieren, die nicht in Konflikt stehen (z.B. 'rcapitals', das Länder nach Hauptstädten sucht). Aber wie auch immer, ich denke, Sie werden feststellen, dass dies alles ziemlich schrecklich ist, was die Leistung angeht.

Wenn Sie wirklich schnell Hash Lookup wollen, gibt es eine schreckliche, schreckliche hack, die tatsächlich funktioniert wirklich gut. Es ist dies: Schreiben Sie Ihre Schlüssel/Werte in eine temporäre Datei, eine pro Zeile, dann verwenden Sie 'grep "^$key"', um sie herauszuholen, und verwenden Sie Pipes mit cut oder awk oder sed oder was auch immer, um die Werte abzurufen.

Wie gesagt, es hört sich schrecklich an, und es hört sich an, als müsste es langsam sein und alle möglichen unnötigen IO ausführen, aber in der Praxis ist es sehr schnell (Festplattencache ist genial, nicht wahr?), sogar für sehr große Hash-Tabellen. Sie müssen die Einzigartigkeit der Schlüssel selbst erzwingen, usw. Selbst wenn Sie nur ein paar hundert Einträge haben, wird die Ausgabedatei/Grep-Kombination um einiges schneller sein - meiner Erfahrung nach um ein Vielfaches. Außerdem verbraucht sie weniger Speicher.

Hier ist eine Möglichkeit, dies zu tun:

hinit() {
    rm -f /tmp/hashmap.$1
}

hput() {
    echo "$2 $3" >> /tmp/hashmap.$1
}

hget() {
    grep "^$2 " /tmp/hashmap.$1 | awk '{ print $2 };'
}

hinit capitals
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid

echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`

2 Stimmen

Großartig! Sie können es sogar iterieren: for i in $(compgen -A variable capitols); do hget "$i" "" done

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X