Was ist das Äquivalent zu Python-Wörterbüchern, aber in Bash (sollte unter OS X und Linux funktionieren).
bash
dictionary
hashtable
associative-array
Sridhar Ratnakumar
quelle
quelle
Antworten:
Bash 4
Bash 4 unterstützt diese Funktion nativ. Stellen Sie sicher , dass Ihr Skript Hash - Bang ist
#!/usr/bin/env bash
oder#!/bin/bash
so dass Sie nicht am Ende mitsh
. Stellen Sie sicher , entweder sind Ausführung Skript direkt, oder führen Siescript
mitbash script
. (Nicht ausgeführt wird tatsächlich ein Bash - Skript mit Bash nicht passieren, und wird wirklich verwirrend!)Sie deklarieren ein assoziatives Array, indem Sie Folgendes tun:
Sie können es mit dem normalen Array-Zuweisungsoperator mit Elementen füllen. Wenn Sie beispielsweise eine Karte haben möchten von
animal[sound(key)] = animal(value)
:Oder führen Sie sie zusammen:
Verwenden Sie sie dann wie normale Arrays. Verwenden
animals['key']='value'
Wert setzen"${animals[@]}"
um die Werte zu erweitern"${!animals[@]}"
(beachten Sie die!
), um die Tasten zu erweiternVergiss nicht, sie zu zitieren:
Bash 3
Vor Bash 4 haben Sie keine assoziativen Arrays. Nicht verwenden
eval
, um sie zu emulieren . Vermeiden Sieeval
wie die Pest, denn es ist die Pest von Shell Scripting. Der wichtigste Grund ist, dasseval
Ihre Daten als ausführbarer Code behandelt werden (es gibt auch viele andere Gründe).In erster Linie : Erwägen Sie ein Upgrade auf Bash 4. Dies erleichtert Ihnen den gesamten Vorgang erheblich.
Wenn es einen Grund gibt, warum Sie kein Upgrade durchführen können,
declare
ist dies eine weitaus sicherere Option. Es wertet Daten nicht wie Bash-Code auseval
und erlaubt daher nicht so einfach das Einfügen von willkürlichem Code.Bereiten wir die Antwort vor, indem wir die Konzepte einführen:
Erstens die Indirektion.
Zweitens
declare
:Bring sie zusammen:
Lass es uns benutzen:
Hinweis:
declare
Kann nicht in eine Funktion eingefügt werden. Bei jeder Verwendungdeclare
innerhalb einer Bash-Funktion wird die von ihr lokal erstellte Variable in den Bereich dieser Funktion verschoben, was bedeutet, dass wir damit nicht auf globale Arrays zugreifen oder diese ändern können. (In Bash 4 können Sie deklarieren -g verwenden, um globale Variablen zu deklarieren. In Bash 4 können Sie jedoch zunächst assoziative Arrays verwenden, um diese Problemumgehung zu vermeiden.)Zusammenfassung:
declare -A
für assoziative Arrays.declare
Option, wenn Sie kein Upgrade durchführen können.awk
stattdessen und vermeiden Sie das Problem insgesamt.quelle
4.x
und nichty
.sudo port install bash
für diejenigen (mit Bedacht IMHO), die nicht bereit sind, Verzeichnisse im PATH für alle Benutzer ohne explizite prozessbezogene Eskalation von Berechtigungen beschreibbar zu machen.Es gibt eine Parametersubstitution, obwohl es auch ein Un-PC sein kann ... wie Indirektion.
Der BASH 4-Weg ist natürlich besser, aber wenn Sie einen Hack brauchen ... reicht nur ein Hack. Sie können das Array / den Hash mit ähnlichen Techniken durchsuchen.
quelle
VALUE=${animal#*:}
um den Fall zu schützen, in demARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
Das habe ich hier gesucht:
Dies hat bei Bash 4.1.5 bei mir nicht funktioniert:
quelle
Sie können die Schnittstelle hput () / hget () weiter ändern, sodass Sie Hashes wie folgt benannt haben:
und dann
Auf diese Weise können Sie andere Karten definieren, die nicht in Konflikt stehen (z. B. "Hauptstädte", die die Ländersuche nach Hauptstadt durchführen). Aber ich denke, Sie werden feststellen, dass dies alles ziemlich schrecklich ist, was die Leistung betrifft.
Wenn Sie wirklich eine schnelle Hash-Suche wünschen, gibt es einen schrecklichen, schrecklichen Hack, der wirklich gut funktioniert. Es ist dies: Schreiben Sie Ihre Schlüssel / Werte in eine temporäre Datei, eine pro Zeile, und verwenden Sie dann 'grep "^ $ key"', um sie herauszuholen. Verwenden Sie dazu Pipes mit cut oder awk oder sed oder was auch immer, um die Werte abzurufen.
Wie ich schon sagte, es klingt schrecklich und es klingt so, als ob es langsam sein und alle möglichen unnötigen E / A-Vorgänge ausführen sollte, aber in der Praxis ist es sehr schnell (der Festplatten-Cache ist fantastisch, nicht wahr?), Selbst für sehr großen Hash Tabellen. Sie müssen die Eindeutigkeit der Schlüssel selbst erzwingen usw. Selbst wenn Sie nur einige hundert Einträge haben, wird die Kombination aus Ausgabedatei und grep erheblich schneller sein - meiner Erfahrung nach um ein Vielfaches schneller. Es frisst auch weniger Speicher.
Hier ist eine Möglichkeit, dies zu tun:
quelle
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 bestimmte Shell benötigt.
Hashtable-Erstellung
hashtable=$(mktemp -d)
Fügen Sie ein Element hinzu
echo $value > $hashtable/$key
Lesen Sie ein Element
value=$(< $hashtable/$key)
Performance
Natürlich ist es langsam, aber nicht so langsam. Ich habe es auf meinem Computer mit einer SSD und btrfs getestet und es liest / schreibt ungefähr 3000 Elemente pro Sekunde .
quelle
mkdir -d
? (Nicht 4.3, auf Ubuntu 14. Ich würde zurückgreifenmkdir /run/shm/foo
, oder wenn das RAM füllen würdemkdir /tmp/foo
.)mktemp -d
war stattdessen gemeint?$value=$(< $hashtable/$key)
undvalue=$(< $hashtable/$key)
? Vielen Dank!quelle
${var#start}
entfernt den Text Start von Anfang an dem in der Variablen gespeicherten Wert var .Stellen Sie sich eine Lösung vor, bei der das eingebaute Bash-Read verwendet wird , wie im Code-Snippet eines folgenden ufw-Firewall-Skripts dargestellt. Dieser Ansatz hat den Vorteil, dass beliebig viele begrenzte Feldsätze (nicht nur 2) verwendet werden. Wir haben die | verwendet Trennzeichen, da für Portbereichsspezifizierer möglicherweise ein Doppelpunkt erforderlich ist, z. B. 6001: 6010 .
quelle
IFS=$'|' read -r first rest <<< "$fields"
Ich stimme @lhunath und anderen zu, dass das assoziative Array der richtige Weg für Bash 4 ist. Wenn Sie an Bash 3 festhalten (OSX, alte Distributionen, die Sie nicht aktualisieren können), können Sie auch expr verwenden, was überall eine Zeichenfolge sein sollte und reguläre Ausdrücke. Ich mag es besonders, wenn das Wörterbuch nicht zu groß ist.
Schreiben Sie Ihre Karte als Zeichenfolge (beachten Sie das Trennzeichen '' auch am Anfang und am Ende).
Verwenden Sie einen regulären Ausdruck, um die Werte zu extrahieren
Teilen Sie die Zeichenfolge, um die Elemente aufzulisten
Jetzt können Sie es verwenden:
quelle
Ich mochte die Antwort von Al P sehr, wollte aber, dass die Eindeutigkeit billig durchgesetzt wird, also ging ich noch einen Schritt weiter - benutze ein Verzeichnis. Es gibt einige offensichtliche Einschränkungen (Verzeichnisdateilimits, ungültige Dateinamen), aber es sollte in den meisten Fällen funktionieren.
Es ist auch ein bisschen besser in meinen Tests.
Ich dachte nur, ich würde mitmachen. Prost!
Bearbeiten: Hinzufügen von hdestroy ()
quelle
Zwei Dinge, Sie können Speicher anstelle von / tmp in jedem Kernel 2.6 verwenden, indem Sie / dev / shm (Redhat) verwenden. Andere Distributionen können variieren. Außerdem kann hget mit read wie folgt neu implementiert werden:
Durch die Annahme, dass alle Schlüssel eindeutig sind, schließt die Rückgabe die Leseschleife kurz und verhindert, dass alle Einträge durchgelesen werden müssen. Wenn Ihre Implementierung doppelte Schlüssel haben kann, lassen Sie die Rückgabe einfach weg. Dies spart die Kosten für das Lesen und Gabeln von grep und awk. Die Verwendung von / dev / shm für beide Implementierungen ergab Folgendes unter Verwendung von time hget für einen Hash mit 3 Einträgen, der nach dem letzten Eintrag suchte:
Grep / Awk:
Lesen / Echo:
Bei mehreren Aufrufen habe ich nie weniger als 50% Verbesserung gesehen. Dies alles kann aufgrund der Verwendung von auf Gabel über Kopf zurückgeführt werden
/dev/shm
.quelle
Ein Mitarbeiter hat gerade diesen Thread erwähnt. Ich habe Hash-Tabellen unabhängig in Bash implementiert und es ist nicht abhängig von Version 4. Aus einem meiner Blog-Posts im März 2010 (vor einigen Antworten hier ...) mit dem Titel Hash-Tabellen in Bash :
Ich früher verwendet ,
cksum
um Hash aber seit übersetzt Java - String hashCode zu nativem bash / zsh.Es ist nicht bidirektional und die eingebaute Methode ist viel besser, sollte aber auch nicht wirklich verwendet werden. Bash ist für schnelle Unikate gedacht, und solche Dinge sollten ziemlich selten mit Komplexität verbunden sein, die Hashes erfordern könnte, außer vielleicht bei Ihnen
~/.bashrc
und Ihren Freunden.quelle
Vor Bash 4 gibt es keine gute Möglichkeit, assoziative Arrays in Bash zu verwenden. Am besten verwenden Sie eine interpretierte Sprache, die solche Dinge wie awk tatsächlich unterstützt. Auf der anderen Seite tut Bash 4 sie unterstützen.
In Bezug auf weniger gute Möglichkeiten in Bash 3 finden Sie hier eine Referenz, die möglicherweise hilfreich ist: http://mywiki.wooledge.org/BashFAQ/006
quelle
Bash 3-Lösung:
Beim Lesen einiger Antworten habe ich eine kurze kleine Funktion zusammengestellt, die ich zurückgeben möchte, um anderen zu helfen.
quelle
Ich habe auch den bash4-Weg benutzt, aber ich finde einen nervigen Fehler.
Ich musste den assoziativen Array-Inhalt dynamisch aktualisieren, also habe ich Folgendes verwendet:
Ich finde heraus, dass das Anhängen von bash 4.3.11 an einen vorhandenen Schlüssel im Diktat dazu führte, dass der Wert angehängt wurde, falls er bereits vorhanden war. Nach einiger Wiederholung war der Inhalt des Werts beispielsweise "checkKOcheckKOallCheckOK" und dies war nicht gut.
Kein Problem mit Bash 4.3.39, bei dem das Anhängen eines vorhandenen Schlüssels bedeutet, den tatsächlichen Wert zu ersetzen, wenn er bereits vorhanden ist.
Ich habe dieses Problem gelöst, indem ich das assoziative Array statusCheck vor dem Cicle bereinigt / deklariert habe:
quelle
Ich erstelle HashMaps in Bash 3 mit dynamischen Variablen. Wie das funktioniert, erklärte ich in meiner Antwort auf: Assoziative Arrays in Shell-Skripten erklärt
Sie können auch einen Blick auf shell_map werfen , eine HashMap-Implementierung aus Bash 3.
quelle