Frage

Wir benötigten ein Skript, das assoziative Arrays oder Karte wie Datenstruktur für Shell Scripting, jeder Körper?

simuliert
War es hilfreich?

Lösung

Irfan Antwort hinzuzufügen, hier ist eine kürzere und schnellere Version von get() da sie keine Iteration über den Karteninhalt erfordern:

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

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}

Andere Tipps

Eine weitere Option, wenn Portabilität nicht Ihr Hauptanliegen ist, ist assoziative Arrays zu verwenden, die in die Schale eingebaut sind. Dies sollte in bash 4.0 arbeiten (jetzt auf den meisten großen Distributionen zur Verfügung, wenn auch nicht auf OS X, wenn Sie es selbst installieren), KSH und zsh:

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

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

In Abhängigkeit von der Schale, müssen Sie möglicherweise ein typeset -A newmap statt declare -A newmap tun, oder in einigen kann es gar nicht nötig sein.

Ein weiterer nicht-bash 4 Art und Weise.

#!/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önnten werfen eine if-Anweisung für auch dort zu suchen. if [[$ var = ~ / blah /]]. oder was auch immer.

Ich denke, dass Sie Schritt müssen zurück und darüber nachdenken, was eine Karte oder ein assoziatives Array, wirklich ist. es ist alles ist ein Weg, um einen Wert für einen bestimmten Schlüssel zu speichern, und diesen Wert wieder schnell und effizient. Sie können auch jeden Schlüsselwert Paar abzurufen über die Tasten zu durchlaufen können, wollen oder Tasten und ihre zugehörigen Werte löschen.

Nun denken über eine Datenstruktur, die Sie die ganze Zeit in Shell-Scripting verwenden, und auch nur in der Schale ohne einen Skript zu schreiben, das diese Eigenschaften hat. Stapfte? Es ist das Dateisystem.

Wirklich, alles, was Sie ein assoziatives Array in der Shell-Programmierung haben müssen, ist ein temporäres Verzeichnis. mktemp -d ist das assoziative Array Konstruktor:

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

Wenn Sie mit echo und cat fühlen sich nicht, können Sie immer ein paar kleine Wrapper schreiben; ist diese hier aus der Irfan des modelliert, obwohl sie den Wert eher als die Einstellung beliebige Variablen wie $value gerade Ausgabe:

#!/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

Bearbeiten : Dieser Ansatz ist eigentlich ziemlich viel schneller als die lineare Suche durch die Fragesteller vorgeschlagen mit sed sowie robustere (es kann Schlüssel und Werte enthalten -, =, Raum, qnd ": SP:"). Die Tatsache, dass es das Dateisystem verwendet es nicht machen verlangsamen; Diese Dateien sind eigentlich nie auf die Festplatte geschrieben werden garantiert, wenn Sie rufen sync; für temporäre Dateien wie diese mit einer kurzen Lebensdauer, ist es nicht unwahrscheinlich, dass viele von ihnen werden nie auf der Festplatte geschrieben werden.

Ich habe ein paar Benchmarks von Irfan Code, Jerrys Modifikation von Irfan des Code, und meinem Code, das folgende Treiber-Programm mit:

#!/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

    real    0m0.975s
    user    0m0.280s
    sys     0m0.691s

    $ time ./driver.sh brian 10 5

    real    0m0.226s
    user    0m0.057s
    sys     0m0.123s

    $ time ./driver.sh jerry 10 5

    real    0m0.706s
    user    0m0.228s
    sys     0m0.530s

    $ time ./driver.sh irfan 100 5

    real    0m10.633s
    user    0m4.366s
    sys     0m7.127s

    $ time ./driver.sh brian 100 5

    real    0m1.682s
    user    0m0.546s
    sys     0m1.082s

    $ time ./driver.sh jerry 100 5

    real    0m9.315s
    user    0m4.565s
    sys     0m5.446s

    $ time ./driver.sh irfan 10 500

    real    1m46.197s
    user    0m44.869s
    sys     1m12.282s

    $ time ./driver.sh brian 10 500

    real    0m16.003s
    user    0m5.135s
    sys     0m10.396s

    $ time ./driver.sh jerry 10 500

    real    1m24.414s
    user    0m39.696s
    sys     0m54.834s

    $ time ./driver.sh irfan 1000 5

    real    4m25.145s
    user    3m17.286s
    sys     1m21.490s

    $ time ./driver.sh brian 1000 5

    real    0m19.442s
    user    0m5.287s
    sys     0m10.751s

    $ time ./driver.sh jerry 1000 5

    real    5m29.136s
    user    4m48.926s
    sys     0m59.336s

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

hget () {
  eval echo '${hash'"$1"'#hash}'
}
hput France Paris
hput Netherlands Amsterdam
hput Spain Madrid
echo `hget France` and `hget Netherlands` and `hget Spain`

$ sh hash.sh
Paris and Amsterdam and Madrid

Bash4 nativ unterstützt dies. Verwenden Sie keine grep oder eval, sie sind die hässlichsten Hacks.

Für eine ausführliche, detaillierte Antwort mit Beispielcode siehe: https://stackoverflow.com/questions/3467959

####################################################################
# 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

Nun diese Frage zu beantworten.

Nach Skripte simuliert assoziative Arrays in Shell-Skripten. Seine einfache und sehr einfach zu verstehen.

Karte ist nichts anderes als eine nie endende Zeichenfolge, die KeyValuePair gespeichert hat als --name = Irfan --designation = SSE --company = My: SP: Eigene: SP: Firma

Leerzeichen ersetzt mit ': SP:' für Werte

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 hinzugefügt einfach eine andere Methode, alle Schlüssel zu holen

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

    mapName=$1; 

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

    keySet=`
           echo $map | 
           sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
          `
}

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

Wenn Sie nicht über eine Menge von Variablen handhaben wollen, oder Tasten sind einfach ungültig Variablenbezeichner, und Ihr Array garantiert haben weniger als 256 Einzelteile , Sie können Funktion Rückgabewerte missbrauchen. Diese Lösung erfordert keine Subshell als der Wert leicht verfügbar als eine Variable ist, noch eine Iteration, so dass die Leistung schreit. Auch es ist sehr gut lesbar, fast wie die Bash-4-Version.

Hier ist die einfachste 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 in case, sonst ist es unter Globbing. Wirklich nützlich für statischen / gefriert Hashes von Anfang an, aber man kann einen Index-Generator von einem hash_keys=() Array schreiben.

Watch out, wird standardmäßig die erste, so können Sie beiseite nullte Element festlegen möchten:

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

Caveat:. Die Länge ist jetzt falsch

Wenn Sie alternativ nullbasierte Indizierung behalten möchten, können Sie einen anderen Indexwert und Schutz vor einer nicht vorhandenen Schlüssel behalten, aber es ist weniger lesbar:

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[$?]}

Oder halten Sie die Länge richtig, Offset-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))]}

Sie können dynamische Variablennamen verwenden und die Variablennamen wie die Tasten eines hashmap arbeiten lassen.

Zum Beispiel, wenn Sie eine Eingabedatei mit zwei Spalten, Namen, Kredit, wie das Beispiel unten, und Sie wollen das Einkommen der einzelnen Benutzer summieren:

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

Der Befehl unten wird alles zusammenzufassen, mit dynamischen Variablen als Schlüssel in Form von Karte _ $ {Person} :

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

Um die Ergebnisse zu lesen:

set | grep map

Der Ausgang wird sein:

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

auf diesen Techniken Ausarbeiten, entwickle ich auf GitHub eine Funktion, die wie ein HashMap Object , shell_map .

Um zu schaffen " HashMap Instanzen " die shell_map Funktion der Lage ist, Kopien von sich selbst unter verschiedenen Namen erstellen. Jede neue Funktion Kopie wird einen anderen $ FUNCNAME Variable hat. $ FUNCNAME dann verwendet, um einen Namespace für jede Map-Instanz zu erstellen.

Die Mapkeys globale Variablen sind, in der Form $ FUNCNAME_DATA_ $ KEY, wo $ KEY der Schlüssel zur Karte hinzugefügt wird. Diese Variablen sind Variablen .

Bellow Ich werde eine vereinfachte Version davon setzen, so dass 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
}

Verbrauch:

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!"

Wie schade, sah ich die Frage nicht vor - ich habe Bibliothek Shell-Framework geschrieben , die unter anderem enthält die Karten (assoziative Arrays). Die letzte Version davon kann 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")"

Ich habe es richtig gefunden, wie bereits erwähnt, dass die beste Ergebnis erzielt Methode ist auf einen Dateischlüssel / vals zu schreiben, und dann grep / awk verwenden, um sie zurückzuholen. Es klingt wie alle Arten von unnötigen IO, aber Disk-Cache schlägt in und macht es extrem effizient -. Viel schneller als zu versuchen, sie im Speicher speichern eine der oben genannten Methoden (wie das Benchmarks zeigen)

Hier ist eine schnelle, saubere Methode Ich mag:

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 Single-Wert pro Schlüssel erzwingen wollen, könnten Sie auch ein wenig grep / sed Aktion in hput tun ().

vor einigen Jahren schrieb ich Skriptbibliothek für bash, die unter anderen Merkmalen assoziative Arrays unterstützt (Logging, Konfigurationsdateien, erweiterte Unterstützung für Befehlszeilenargument, erzeugen Hilfe, Unit-Tests, etc.). Die Bibliothek enthält eine Hülle für assoziative Arrays und schaltet automatisch in den entsprechenden Modell (intern für bash4 und emulieren die für frühere Versionen). Es wurde bei origo.ethz.ch Shell-Rahmen und gehostete genannt, aber heute ist die Ressource geschlossen ist. Wenn jemand es noch braucht, kann ich es mit Ihnen teilen.

Shell haben keine eingebaute Karte wie Datenstruktur, verwende ich Roh Schnur Gegenstände zu beschreiben, wie folgt aus:

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

, wenn Extrakt Elemente und ihre Attribute:

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 als andere Menschen der Antwort, aber leicht, neue Leute zu verstehen, zu schälen.

Hinzufügen eine weitere Option, wenn jq steht zur Verfügung:

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

I modifizierte Vadims Lösung mit dem folgenden:

####################################################################
# 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 ist, um map_get es Fehler von der Rückkehr zu verhindern, wenn Sie einen Schlüssel anfordern, die nicht existiert, obwohl der Nebeneffekt ist, dass es auch fehlenden Karten leise ignorieren, aber es ist meinen Anwendungsfall besser geeignet da ich gerade für einen Schlüssel, um zu prüfen, wollte Element in einer Schleife zu überspringen.

Späte Antwort, betrachten aber das Problem auf diese Weise Adressierung mit der bash gebautet lesen , wie von einem ufw Firewall Skript innerhalb des Code-Snippet veranschaulicht, die folgt. Dieser Ansatz hat den Vorteil, da viele begrenzten Feldsätze verwenden (und nicht nur 2), wie erwünscht. Wir verwendet haben, die | Begrenzer, da Portbereich Planer einen Doppelpunkt erfordern, dh 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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top