Assoziative Arrays in Shell-Skripten

116

Wir benötigten ein Skript, das assoziative Arrays oder eine Map-ähnliche Datenstruktur für Shell Scripting simuliert.

Irfan Zulfiqar
quelle

Antworten:

20

Um Irfans Antwort zu ergänzen , hier eine kürzere und schnellere Version von, get()da keine Iteration über den Karteninhalt erforderlich ist:

get() {
    mapName=$1; key=$2

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
Jerry Penner
quelle
16
Gabelung einer Unterschale und Sedierung ist kaum optimal. Bash4 unterstützt dies nativ und bash3 hat bessere Alternativen.
lhunath
149

Eine andere Option, wenn Portabilität nicht Ihr Hauptanliegen ist, ist die Verwendung von assoziativen Arrays, die in die Shell integriert sind. Dies sollte in Bash 4.0 (jetzt verfügbar in den meisten wichtigen Distributionen, jedoch nicht in OS X, es sei denn, Sie installieren es selbst), ksh und zsh funktionieren:

declare -A newmap
newmap[name]="Irfan Zulfiqar"
newmap[designation]=SSE
newmap[company]="My Own Company"

echo ${newmap[company]}
echo ${newmap[name]}

Abhängig von der Shell müssen Sie möglicherweise eine typeset -A newmapanstelle von ausführen declare -A newmap, oder in einigen Fällen ist dies möglicherweise überhaupt nicht erforderlich.

Brian Campbell
quelle
Vielen Dank für Ihre Antwort. Ich denke, das wäre der beste Weg, dies für Leute zu tun, die Bash 4.0 oder höher verwenden würden.
Irfan Zulfiqar
Ich würde ein wenig kludge hinzufügen, um sicherzustellen, dass BASH_VERSION gesetzt ist und> = 4. Und ja, BASH 4 ist wirklich sehr, sehr cool!
Tim Post
Ich benutze so etwas. Was ist der beste Weg, um den Fehler zu "fangen", wenn der Array-Index / Index nicht existiert? Was wäre zum Beispiel, wenn ich den Index als Befehlszeilenoption verwenden würde und der Benutzer einen Tippfehler machen und "designatio" eingeben würde? Ich erhalte den Fehler "Bad Array Index", weiß aber nicht, wie ich die Eingabe zum Zeitpunkt der Array-Suche überprüfen soll, wenn dies möglich ist.
Jer
3
@Jer Es ist ziemlich dunkel, aber um festzustellen, ob eine Variable in der Shell festgelegt ist, können Sie verwenden test -z ${variable+x}(das xspielt keine Rolle, das kann eine beliebige Zeichenfolge sein). Für ein assoziatives Array in Bash können Sie ähnliche Aktionen ausführen. verwenden test -z ${map[key]+x}.
Brian Campbell
95

Ein weiterer Non-Bash-4-Weg.

#!/bin/bash

# A pretend Python dictionary with bash 3 
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

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

Sie können dort auch eine if-Anweisung für die Suche eingeben. if [[$ var = ~ / blah /]]. oder Wasauchimmer.

Bubnoff
quelle
2
Diese Methode ist gut, wenn Sie Bash 4 nicht haben. Aber ich denke, die Zeile, die den Wert abruft, wäre auf diese Weise sicherer: VALUE = $ {animal # *:}. Mit nur einem # -Zeichen wird der Abgleich beim ersten ":" beendet. Dadurch können Werte auch ":" enthalten.
Ced-le-Pingouin
@ Ced-le-pingouin ~ Das ist ein großartiger Punkt! Das habe ich nicht verstanden. Ich habe meinen Beitrag bearbeitet, um Ihre vorgeschlagenen Verbesserungen widerzuspiegeln.
Bubnoff
1
Es ist eine ziemlich hackige Emulation von assoziativen Arrays unter Verwendung der BASH-Parametersubstitution. Das param-sub "key" ersetzt alles vor dem Doppelpunkt und das Wertemuster ersetzt alles nach dem Doppelpunkt. Ähnlich wie bei einem Regex-Wildcard-Match. Also KEIN echtes assoziatives Array. Nicht zu empfehlen, es sei denn, Sie benötigen eine leicht verständliche Methode, um Hash- / assoziative Array-ähnliche Funktionen in BASH 3 oder niedriger auszuführen. Es funktioniert aber! Mehr hier: tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2
Bubnoff
1
Dies implementiert kein assoziatives Array, da es keine Möglichkeit bietet, ein Element anhand des Schlüssels nachzuschlagen. Es bietet nur eine Möglichkeit, jeden Schlüssel (und Wert) aus einem numerischen Index zu finden. (Ein Element könnte durch Schlüssel durch Iteration durch das Array gefunden werden, aber das ist nicht das, was für ein assoziatives Array gewünscht wird.)
Eric Postpischil
@EricPostpischil True. Es ist nur ein Hack. Es ermöglicht einer Person, die vertraute Syntax im Setup zu verwenden, erfordert jedoch weiterhin das Durchlaufen des Arrays, wie Sie sagen. Ich habe in meinem vorherigen Kommentar versucht, klar zu machen, dass es sich definitiv nicht um ein assoziatives Array handelt, und ich empfehle es nicht einmal, wenn Sie Alternativen haben. Der einzige Punkt, der meiner Meinung nach dafür spricht, ist, dass es für diejenigen, die mit anderen Sprachen wie Python vertraut sind, einfach zu schreiben und zu verwenden ist. Wenn Sie sich an einem Punkt befinden, an dem Sie tatsächlich assoziative Arrays in BASH 3 implementieren möchten, müssen Sie möglicherweise Ihre Schritte ein wenig zurückverfolgen.
Bubnoff
34

Ich denke, Sie müssen einen Schritt zurücktreten und darüber nachdenken, was eine Karte oder ein assoziatives Array wirklich ist. Es ist lediglich eine Möglichkeit, einen Wert für einen bestimmten Schlüssel zu speichern und diesen Wert schnell und effizient zurückzugewinnen. Möglicherweise möchten Sie auch in der Lage sein, die Schlüssel zu durchlaufen, um jedes Schlüsselwertpaar abzurufen, oder Schlüssel und die zugehörigen Werte zu löschen.

Stellen Sie sich nun eine Datenstruktur vor, die Sie beim Shell-Scripting ständig verwenden, und sogar nur in der Shell, ohne ein Skript zu schreiben, das diese Eigenschaften aufweist. Stumped? Es ist das Dateisystem.

Alles, was Sie für ein assoziatives Array in der Shell-Programmierung benötigen, ist ein temporäres Verzeichnis. mktemp -dist Ihr assoziativer Array-Konstruktor:

prefix=$(basename -- "$0")
map=$(mktemp -dt ${prefix})
echo >${map}/key somevalue
value=$(cat ${map}/key)

Wenn Sie keine Lust haben, echound zu verwenden cat, können Sie immer ein paar kleine Wrapper schreiben. Diese sind von Irfans modelliert, obwohl sie nur den Wert ausgeben, anstatt beliebige Variablen wie $value:

#!/bin/sh

prefix=$(basename -- "$0")
mapdir=$(mktemp -dt ${prefix})
trap 'rm -r ${mapdir}' EXIT

put() {
  [ "$#" != 3 ] && exit 1
  mapname=$1; key=$2; value=$3
  [ -d "${mapdir}/${mapname}" ] || mkdir "${mapdir}/${mapname}"
  echo $value >"${mapdir}/${mapname}/${key}"
}

get() {
  [ "$#" != 2 ] && exit 1
  mapname=$1; key=$2
  cat "${mapdir}/${mapname}/${key}"
}

put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"

value=$(get "newMap" "company")
echo $value

value=$(get "newMap" "name")
echo $value

edit : Dieser Ansatz ist tatsächlich ziemlich viel schneller als die vom Fragesteller vorgeschlagene lineare Suche mit sed und robuster (er ermöglicht, dass Schlüssel und Werte -, =, Leerzeichen, qnd ": SP:" enthalten). Die Tatsache, dass es das Dateisystem verwendet, macht es nicht langsam; Es wird nie garantiert, dass diese Dateien auf die Festplatte geschrieben werden, es sei denn, Sie rufen an sync. Bei temporären Dateien wie diesen mit kurzer Lebensdauer ist es nicht unwahrscheinlich, dass viele von ihnen niemals auf die Festplatte geschrieben werden.

Ich habe einige Benchmarks für Irfans Code, Jerrys Modifikation von Irfans Code und meinen Code mit dem folgenden Treiberprogramm durchgeführt:

#!/bin/sh

mapimpl=$1
numkeys=$2
numvals=$3

. ./${mapimpl}.sh    #/ <- fix broken stack overflow syntax highlighting

for (( i = 0 ; $i < $numkeys ; i += 1 ))
do
    for (( j = 0 ; $j < $numvals ; j += 1 ))
    do
        put "newMap" "key$i" "value$j"
        get "newMap" "key$i"
    done
done

Die Ergebnisse:

    $ time ./driver.sh irfan 10 5

    echte 0m0.975s
    Benutzer 0m0.280s
    sys 0m0.691s

    $ time ./driver.sh brian 10 5

    echte 0m0.226s
    Benutzer 0m0.057s
    sys 0m0.123s

    $ time ./driver.sh jerry 10 5

    echte 0m0.706s
    Benutzer 0m0.228s
    sys 0m0.530s

    $ time ./driver.sh irfan 100 5

    echte 0m10.633s
    Benutzer 0m4.366s
    sys 0m7.127s

    $ time ./driver.sh brian 100 5

    echte 0m1.682s
    Benutzer 0m0.546s
    sys 0m1.082s

    $ time ./driver.sh jerry 100 5

    echte 0m9.315s
    Benutzer 0m4.565s
    sys 0m5.446s

    $ time ./driver.sh irfan 10 500

    echte 1m46.197s
    Benutzer 0m44.869s
    sys 1m12.282s

    $ time ./driver.sh brian 10 500

    echte 0m16.003s
    Benutzer 0m5.135s
    sys 0m10.396s

    $ time ./driver.sh jerry 10 500

    echte 1m24.414s
    Benutzer 0m39.696s
    sys 0m54.834s

    $ time ./driver.sh irfan 1000 5

    echte 4m25.145s
    Benutzer 3m17.286s
    sys 1m21.490s

    $ time ./driver.sh brian 1000 5

    echte 0m19.442s
    Benutzer 0m5.287s
    sys 0m10.751s

    $ time ./driver.sh jerry 1000 5

    echte 5m29.136s
    Benutzer 4m48.926s
    sys 0m59.336s

Brian Campbell
quelle
1
Ich denke nicht, dass Sie ein Dateisystem für Karten verwenden sollten, das im Grunde genommen E / A für etwas verwendet, das Sie im Speicher ziemlich schnell erledigen können.
Irfan Zulfiqar
9
Die Dateien werden nicht unbedingt jemals auf die Festplatte geschrieben. Wenn Sie nicht die Synchronisierung aufrufen, belässt das Betriebssystem sie möglicherweise nur im Speicher. Ihr Code ruft sed auf und führt mehrere lineare Suchvorgänge durch, die alle sehr langsam sind. Ich habe einige schnelle Benchmarks durchgeführt und meine Version ist 5-35-mal schneller.
Brian Campbell
Auf der anderen Seite sind die nativen Arrays von bash4 ein wesentlich besserer Ansatz, und in bash3 können Sie immer noch alles von der Festplatte fernhalten, ohne es durch Deklarieren und Indirektion zu verzweigen.
lhunath
7
"schnell" und "Shell" passen sowieso nicht wirklich zusammen: sicherlich nicht für die Geschwindigkeitsprobleme, über die wir auf der Ebene "Vermeiden Sie winzige E / A" sprechen. Sie können nach / dev / shm suchen und verwenden, um keine E / A zu garantieren.
jmtd
2
Diese Lösung hat mich überrascht und ist einfach fantastisch. Dies gilt auch für 2016. Es sollte wirklich die akzeptierte Antwort sein.
Gordon
7

Bash4 unterstützt dies nativ. Verwenden Sie nicht grepoder eval, sie sind die hässlichsten Hacks.

Eine ausführliche Antwort mit Beispielcode finden Sie unter: /programming/3467959

lhunath
quelle
7
####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
    alias "${1}$2"="$3"
}

# map_get map_name key
# @return value
#
function map_get
{
    alias "${1}$2" | awk -F"'" '{ print $2; }'
}

# map_keys map_name 
# @return map keys
#
function map_keys
{
    alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}

Beispiel:

mapName=$(basename $0)_map_
map_put $mapName "name" "Irfan Zulfiqar"
map_put $mapName "designation" "SSE"

for key in $(map_keys $mapName)
do
    echo "$key = $(map_get $mapName $key)
done
Vadim
quelle
4

Beantworten Sie nun diese Frage.

Die folgenden Skripte simulieren assoziative Arrays in Shell-Skripten. Es ist einfach und sehr leicht zu verstehen.

Map ist nichts anderes als eine nie endende Zeichenfolge, in der keyValuePair als --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company gespeichert ist

Leerzeichen werden für Werte durch ': SP:' ersetzt

put() {
    if [ "$#" != 3 ]; then exit 1; fi
    mapName=$1; key=$2; value=`echo $3 | sed -e "s/ /:SP:/g"`
    eval map="\"\$$mapName\""
    map="`echo "$map" | sed -e "s/--$key=[^ ]*//g"` --$key=$value"
    eval $mapName="\"$map\""
}

get() {
    mapName=$1; key=$2; valueFound="false"

    eval map=\$$mapName

    for keyValuePair in ${map};
    do
        case "$keyValuePair" in
            --$key=*) value=`echo "$keyValuePair" | sed -e 's/^[^=]*=//'`
                      valueFound="true"
        esac
        if [ "$valueFound" == "true" ]; then break; fi
    done
    value=`echo $value | sed -e "s/:SP:/ /g"`
}

put "newMap" "name" "Irfan Zulfiqar"
put "newMap" "designation" "SSE"
put "newMap" "company" "My Own Company"

get "newMap" "company"
echo $value

get "newMap" "name"
echo $value

Bearbeiten: Fügte gerade eine andere Methode hinzu, um alle Schlüssel abzurufen.

getKeySet() {
    if [ "$#" != 1 ]; 
    then 
        exit 1; 
    fi

    mapName=$1; 

    eval map="\"\$$mapName\""

    keySet=`
           echo $map | 
           sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
          `
}
Irfan Zulfiqar
quelle
1
Sie geben evalDaten so ein, als wäre es Bash-Code, und außerdem zitieren Sie sie nicht richtig. Beide verursachen eine Vielzahl von Fehlern und eine willkürliche Code-Injektion.
lhunath
3

Für Bash 3 gibt es einen speziellen Fall, der eine schöne und einfache Lösung bietet:

Wenn Sie nicht viele Variablen verarbeiten möchten oder Schlüssel einfach ungültige Variablenkennungen sind und Ihr Array garantiert weniger als 256 Elemente enthält , können Sie Funktionsrückgabewerte missbrauchen. Diese Lösung erfordert weder eine Unterschale, da der Wert als Variable verfügbar ist, noch eine Iteration, sodass die Leistung schreit. Außerdem ist es sehr gut lesbar, fast wie die Bash 4-Version.

Hier ist die grundlegendste Version:

hash_index() {
    case $1 in
        'foo') return 0;;
        'bar') return 1;;
        'baz') return 2;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo"
echo ${hash_vals[$?]}

Denken Sie daran, verwenden Sie einfache Anführungszeichen case, da dies sonst zu Globbing führen kann. Wirklich nützlich für statische / eingefrorene Hashes von Anfang an, aber man könnte einen Indexgenerator aus einem hash_keys=()Array schreiben .

Beachten Sie, dass standardmäßig das erste Element verwendet wird. Sie können also das nullte Element beiseite legen:

hash_index() {
    case $1 in
        'foo') return 1;;
        'bar') return 2;;
        'baz') return 3;;
    esac
}

hash_vals=("",           # sort of like returning null/nil for a non existent key
           "foo_val"
           "bar_val"
           "baz_val");

hash_index "foo" || echo ${hash_vals[$?]}  # It can't get more readable than this

Vorsichtsmaßnahme: Die Länge ist jetzt falsch.

Wenn Sie die auf Null basierende Indizierung beibehalten möchten, können Sie alternativ einen anderen Indexwert reservieren und sich vor einem nicht vorhandenen Schlüssel schützen, der jedoch weniger lesbar ist:

hash_index() {
    case $1 in
        'foo') return 0;;
        'bar') return 1;;
        'baz') return 2;;
        *)   return 255;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo"
[[ $? -ne 255 ]] && echo ${hash_vals[$?]}

Um die Länge korrekt zu halten, versetzen Sie den Index um eins:

hash_index() {
    case $1 in
        'foo') return 1;;
        'bar') return 2;;
        'baz') return 3;;
    esac
}

hash_vals=("foo_val"
           "bar_val"
           "baz_val");

hash_index "foo" || echo ${hash_vals[$(($? - 1))]}
Lloeki
quelle
2

Sie können dynamische Variablennamen verwenden und die Variablennamen wie die Schlüssel einer Hashmap arbeiten lassen.

Wenn Sie beispielsweise eine Eingabedatei mit zwei Spalten haben, Name, Gutschrift als Beispiel unten, und Sie das Einkommen jedes Benutzers summieren möchten:

Mary 100
John 200
Mary 50
John 300
Paul 100
Paul 400
David 100

Der folgende Befehl summiert alles unter Verwendung dynamischer Variablen als Schlüssel in Form von map _ $ {person} :

while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)

So lesen Sie die Ergebnisse:

set | grep map

Die Ausgabe wird sein:

map_David=100
map_John=500
map_Mary=150
map_Paul=500

Ich arbeite an diesen Techniken und entwickle auf GitHub eine Funktion, die genau wie ein HashMap-Objekt funktioniert , shell_map .

Um " HashMap-Instanzen " zu erstellen, kann die Funktion shell_map Kopien von sich selbst unter verschiedenen Namen erstellen. Jede neue Funktionskopie hat eine andere Variable $ FUNCNAME. $ FUNCNAME wird dann verwendet, um einen Namespace für jede Map-Instanz zu erstellen.

Die Kartenschlüssel sind globale Variablen in der Form $ FUNCNAME_DATA_ $ KEY, wobei $ KEY der der Karte hinzugefügte Schlüssel ist. Diese Variablen sind dynamische Variablen .

Unten werde ich eine vereinfachte Version davon einfügen, damit Sie als Beispiel verwenden können.

#!/bin/bash

shell_map () {
    local METHOD="$1"

    case $METHOD in
    new)
        local NEW_MAP="$2"

        # loads shell_map function declaration
        test -n "$(declare -f shell_map)" || return

        # declares in the Global Scope a copy of shell_map, under a new name.
        eval "${_/shell_map/$2}"
    ;;
    put)
        local KEY="$2"  
        local VALUE="$3"

        # declares a variable in the global scope
        eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
    ;;
    get)
        local KEY="$2"
        local VALUE="${FUNCNAME}_DATA_${KEY}"
        echo "${!VALUE}"
    ;;
    keys)
        declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))"
    ;;
    name)
        echo $FUNCNAME
    ;;
    contains_key)
        local KEY="$2"
        compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
    ;;
    clear_all)
        while read var; do  
            unset $var
        done < <(compgen -v ${FUNCNAME}_DATA_)
    ;;
    remove)
        local KEY="$2"
        unset ${FUNCNAME}_DATA_${KEY}
    ;;
    size)
        compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
    ;;
    *)
        echo "unsupported operation '$1'."
        return 1
    ;;
    esac
}

Verwendung:

shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do 
    value=`credit get $customer`       
    echo "customer $customer has $value"
done
credit contains_key "Mary" && echo "Mary has credit!"
Bruno Negrão Zica
quelle
2

Noch eine andere nicht-Bash-4-Methode (dh Bash-3, Mac-kompatibel):

val_of_key() {
    case $1 in
        'A1') echo 'aaa';;
        'B2') echo 'bbb';;
        'C3') echo 'ccc';;
        *) echo 'zzz';;
    esac
}

for x in 'A1' 'B2' 'C3' 'D4'; do
    y=$(val_of_key "$x")
    echo "$x => $y"
done

Drucke:

A1 => aaa
B2 => bbb
C3 => ccc
D4 => zzz

Die Funktion mit casewirkt wie ein assoziatives Array. Leider kann es nicht verwendet werden return, daher muss es echoausgegeben werden, aber dies ist kein Problem, es sei denn, Sie sind ein Purist, der das Gabeln von Unterschalen meidet.

Walter Tross
quelle
1

Wie schade, dass ich die Frage vorher nicht gesehen habe - ich habe ein Library- Shell-Framework geschrieben, das unter anderem die Maps (Assoziative Arrays) enthält. Die letzte Version finden Sie hier .

Beispiel:

#!/bin/bash 
#include map library
shF_PATH_TO_LIB="/usr/lib/shell-framework"
source "${shF_PATH_TO_LIB}/map"

#simple example get/put
putMapValue "mapName" "mapKey1" "map Value 2"
echo "mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#redefine old value to new
putMapValue "mapName" "mapKey1" "map Value 1"
echo "after change mapName[mapKey1]: $(getMapValue "mapName" "mapKey1")"

#add two new pairs key/values and print all keys
putMapValue "mapName" "mapKey2" "map Value 2"
putMapValue "mapName" "mapKey3" "map Value 3"
echo -e "mapName keys are \n$(getMapKeys "mapName")"

#create new map
putMapValue "subMapName" "subMapKey1" "sub map Value 1"
putMapValue "subMapName" "subMapKey2" "sub map Value 2"

#and put it in mapName under key "mapKey4"
putMapValue "mapName" "mapKey4" "subMapName"

#check if under two key were placed maps
echo "is map mapName[mapKey3]? - $(if isMap "$(getMapValue "mapName" "mapKey3")" ; then echo Yes; else echo No; fi)"
echo "is map mapName[mapKey4]? - $(if isMap "$(getMapValue "mapName" "mapKey4")" ; then echo Yes; else echo No; fi)"

#print map with sub maps
printf "%s\n" "$(mapToString "mapName")"
Betteln
quelle
1

Hinzufügen einer weiteren Option, wenn jq verfügbar ist:

export NAMES="{
  \"Mary\":\"100\",
  \"John\":\"200\",
  \"Mary\":\"50\",
  \"John\":\"300\",
  \"Paul\":\"100\",
  \"Paul\":\"400\",
  \"David\":\"100\"
}"
export NAME=David
echo $NAMES | jq --arg v "$NAME" '.[$v]' | tr -d '"' 
kritium
quelle
0

Ich habe festgestellt, dass es, wie bereits erwähnt, am besten ist, Schlüssel / Werte in eine Datei zu schreiben und sie dann mit grep / awk abzurufen. Es klingt nach allen möglichen unnötigen E / A-Vorgängen, aber der Festplatten-Cache wird aktiviert und ist äußerst effizient - viel schneller als der Versuch, sie mit einer der oben genannten Methoden im Speicher zu speichern (wie die Benchmarks zeigen).

Hier ist eine schnelle, saubere Methode, die mir gefällt:

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

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

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

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

echo `hget capitols France` and `hget capitols Netherlands` and `hget capitols Spain`

Wenn Sie einen Einzelwert pro Schlüssel erzwingen möchten, können Sie auch eine kleine grep / sed-Aktion in hput () ausführen.

Al P.
quelle
0

Vor einigen Jahren schrieb ich eine Skriptbibliothek für Bash, die unter anderem assoziative Arrays unterstützte (Protokollierung, Konfigurationsdateien, erweiterte Unterstützung für Befehlszeilenargumente, Hilfe generieren, Komponententests usw.). Die Bibliothek enthält einen Wrapper für assoziative Arrays und wechselt automatisch zum entsprechenden Modell (intern für bash4 und emuliert für frühere Versionen). Es hieß Shell-Framework und wurde auf origo.ethz.ch gehostet, aber heute ist die Ressource geschlossen. Wenn jemand es noch braucht, kann ich es mit Ihnen teilen.

Betteln
quelle
Könnte es wert sein, es auf Github zu kleben
Mark K Cowan
0

Shell hat keine eingebaute Karte wie Datenstruktur, ich benutze rohe Zeichenfolge, um Elemente wie diese zu beschreiben:

ARRAY=(
    "item_A|attr1|attr2|attr3"
    "item_B|attr1|attr2|attr3"
    "..."
)

beim Extrahieren von Elementen und ihren Attributen:

for item in "${ARRAY[@]}"
do
    item_name=$(echo "${item}"|awk -F "|" '{print $1}')
    item_attr1=$(echo "${item}"|awk -F "|" '{print $2}')
    item_attr2=$(echo "${item}"|awk -F "|" '{print $3}')

    echo "${item_name}"
    echo "${item_attr1}"
    echo "${item_attr2}"
done

Dies scheint nicht klug zu sein als die Antwort anderer, aber für neue Leute leicht zu verstehen.

coanor
quelle
-1

Ich habe die Lösung von Vadim wie folgt geändert:

####################################################################
# Bash v3 does not support associative arrays
# and we cannot use ksh since all generic scripts are on bash
# Usage: map_put map_name key value
#
function map_put
{
    alias "${1}$2"="$3"
}

# map_get map_name key
# @return value
#
function map_get {
    if type -p "${1}$2"
        then
            alias "${1}$2" | awk -F "'" '{ print $2; }';
    fi
}

# map_keys map_name 
# @return map keys
#
function map_keys
{
    alias -p | grep $1 | cut -d'=' -f1 | awk -F"$1" '{print $2; }'
}

Die Änderung betrifft map_get, um zu verhindern, dass Fehler zurückgegeben werden, wenn Sie einen Schlüssel anfordern, der nicht vorhanden ist. Der Nebeneffekt ist jedoch, dass fehlende Karten ebenfalls stillschweigend ignoriert werden, aber sie passen besser zu meinem Anwendungsfall, da ich gerade wollte nach einem Schlüssel suchen, um Elemente in einer Schleife zu überspringen.

Haravikk
quelle
-1

Verspätete Antwort, aber erwägen Sie, das Problem auf diese Weise zu beheben, indem Sie die eingebaute Bash verwenden , die im Code-Snippet eines folgenden ufw-Firewall-Skripts dargestellt ist. 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 .

#!/usr/bin/env bash

readonly connections=(       
                            '192.168.1.4/24|tcp|22'
                            '192.168.1.4/24|tcp|53'
                            '192.168.1.4/24|tcp|80'
                            '192.168.1.4/24|tcp|139'
                            '192.168.1.4/24|tcp|443'
                            '192.168.1.4/24|tcp|445'
                            '192.168.1.4/24|tcp|631'
                            '192.168.1.4/24|tcp|5901'
                            '192.168.1.4/24|tcp|6566'
)

function set_connections(){
    local range proto port
    for fields in ${connections[@]}
    do
            IFS=$'|' read -r range proto port <<< "$fields"
            ufw allow from "$range" proto "$proto" to any port "$port"
    done
}

set_connections
AsymLabs
quelle