Tablice asocjacyjne w skryptach powłoki

116

Potrzebowaliśmy skryptu, który symuluje tablice asocjacyjne lub strukturę danych podobną do mapy dla skryptów powłoki.

Irfan Zulfiqar
źródło

Odpowiedzi:

20

Aby dodać do odpowiedzi Irfana , oto krótsza i szybsza wersja, get()ponieważ nie wymaga iteracji po zawartości mapy:

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

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
Jerry Penner
źródło
16
rozwidlenie podpowłoki i seda nie jest optymalne. Bash4 obsługuje to natywnie, a bash3 ma lepsze alternatywy.
lhunath
149

Inną opcją, jeśli przenośność nie jest Twoim głównym celem, jest użycie tablic asocjacyjnych wbudowanych w powłokę. Powinno to działać w bash 4.0 (dostępne teraz w większości głównych dystrybucji, ale nie na OS X, chyba że zainstalujesz go samodzielnie), ksh i zsh:

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

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

W zależności od powłoki może być konieczne wykonanie typeset -A newmapzamiast declare -A newmaplub w niektórych przypadkach może to nie być konieczne.

Brian Campbell
źródło
Dzięki za wysłanie odpowiedzi, myślę, że byłby to najlepszy sposób na zrobienie tego dla facetów, którzy używają basha 4.0 lub nowszego.
Irfan Zulfiqar
Dodałbym trochę kludge, aby upewnić się, że BASH_VERSION jest ustawiony i> = 4. I tak, BASH 4 jest naprawdę, naprawdę fajny!
Tim Post
Używam czegoś takiego. Jaki jest najlepszy sposób „wychwycenia” błędu, w którym indeks / indeks tablicy nie istnieje? Na przykład, co jeśli wziąłem indeks dolny jako opcję wiersza poleceń, a użytkownik popełnił literówkę i wprowadził „designatio”? Otrzymuję błąd „zły indeks tablicy”, ale nie wiem, jak sprawdzić poprawność danych wejściowych w czasie wyszukiwania tablicy, jeśli to możliwe?
Jer
3
@Jer Jest to dość niejasne, ale aby określić, czy zmienna jest ustawiona w powłoce, możesz użyć test -z ${variable+x}( xnie ma znaczenia, może to być dowolny ciąg). W przypadku tablicy asocjacyjnej w Bash możesz zrobić podobnie; używać test -z ${map[key]+x}.
Brian Campbell
95

Kolejny 4 sposób bez bash.

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

Możesz także wrzucić tam instrukcję if do wyszukiwania. if [[$ var = ~ / blah /]]. lub cokolwiek.

Bubnoff
źródło
2
Ta metoda jest dobra, jeśli rzeczywiście nie masz Bash 4. Ale myślę, że wiersz pobierający WARTOŚĆ byłby bezpieczniejszy w ten sposób: WARTOŚĆ = $ {zwierzę nr *:}. Mając tylko jeden znak #, dopasowywanie zatrzyma się na pierwszym znaku „:”. Dzięki temu wartości mogą również zawierać „:”.
Ced-le-pingouin
@ Ced-le-pingouin ~ Świetna uwaga! Nie rozumiem. Zmodyfikowałem swój post, aby odzwierciedlić sugerowane przez Ciebie ulepszenia.
Bubnoff
1
Jest to dość hakerska emulacja tablic asocjacyjnych wykorzystująca podstawianie parametrów BASH. „Klucz” param-sub podstawia wszystko przed dwukropkiem, a wzorzec wartości zastępuje wszystko po dwukropku. Podobny do dopasowania wieloznacznego wyrażenia regularnego. Więc NIE jest to prawdziwa tablica asocjacyjna. Niezalecane, chyba że potrzebujesz łatwego do zrozumienia sposobu wykonywania funkcji mieszania / tablic asocjacyjnych w BASH 3 lub niższym. Ale działa! Więcej tutaj: tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2
Bubnoff
1
Nie implementuje to tablicy asocjacyjnej, ponieważ nie zapewnia sposobu wyszukiwania elementu według klucza. Zapewnia jedynie sposób na znalezienie każdego klucza (i wartości) na podstawie indeksu liczbowego. (Element można znaleźć za pomocą klucza, przechodząc przez tablicę, ale nie jest to pożądane w przypadku tablicy asocjacyjnej).
Eric Postpischil,
@EricPostpischil True. To tylko hack. Pozwala to na użycie znanej składni podczas konfiguracji, ale nadal wymaga iteracji po tablicy, jak mówisz. W moim poprzednim komentarzu starałem się wyjaśnić, że zdecydowanie nie jest to tablica asocjacyjna i nawet nie polecam jej, jeśli masz alternatywy. Moim zdaniem jedyną zaletą jest to, że jest łatwy do pisania i używania dla osób znających inne języki, takie jak Python. Jeśli jesteś w punkcie, w którym faktycznie chcesz zaimplementować tablice asocjacyjne w BASH 3, być może będziesz musiał trochę odtworzyć swoje kroki.
Bubnoff,
34

Myślę, że musisz cofnąć się i pomyśleć o tym, czym naprawdę jest mapa lub tablica asocjacyjna. Wszystko to jest sposobem na przechowywanie wartości dla danego klucza i szybkie i wydajne odzyskanie tej wartości. Możesz również chcieć mieć możliwość iteracji po kluczach, aby pobrać każdą parę klucz-wartość lub usunąć klucze i powiązane z nimi wartości.

Teraz pomyśl o strukturze danych, której używasz cały czas w skryptach powłoki, a nawet tylko w powłoce bez pisania skryptu, który ma takie właściwości. Zaskoczony? To system plików.

Naprawdę, wszystko, czego potrzebujesz, aby mieć tablicę asocjacyjną w programowaniu powłoki, to katalog tymczasowy. mktemp -djest konstruktorem tablicy asocjacyjnej:

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

Jeśli nie masz ochoty używać echoi cat, zawsze możesz napisać kilka małych opakowań; te są wzorowane na Irfan, chociaż po prostu wyświetlają wartość, a nie ustawiają dowolne zmienne, takie jak $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

edycja : To podejście jest właściwie trochę szybsze niż wyszukiwanie liniowe przy użyciu seda sugerowanego przez pytającego, a także bardziej niezawodne (pozwala kluczom i wartościom zawierać -, =, spację, qnd ": SP:"). Fakt, że używa systemu plików, nie spowalnia go; w rzeczywistości nigdy nie ma gwarancji, że te pliki zostaną zapisane na dysku, chyba że zadzwonisz sync; w przypadku takich plików tymczasowych o krótkim czasie życia nie jest nieprawdopodobne, że wiele z nich nigdy nie zostanie zapisanych na dysku.

Zrobiłem kilka testów porównawczych kodu Irfana, modyfikacji kodu Irfana przez Jerry'ego i mojego kodu, używając następującego programu sterownika:

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

Wyniki:

    $ czas ./driver.sh irfan 10 5

    real 0m0,975s
    użytkownik 0m0,280s
    sys 0m0,691s

    $ czas ./driver.sh brian 10 5

    real 0m0,226s
    użytkownik 0m0,057s
    sys 0m0,123s

    $ czas ./driver.sh jerry 10 5

    real 0m0.706s
    użytkownik 0m0,228s
    sys 0m0,530s

    $ czas ./driver.sh irfan 100 5

    prawdziwe 0m10.633s
    użytkownik 0m4,366s
    sys 0m7,127s

    $ czas ./driver.sh brian 100 5

    real 0m1,682s
    użytkownik 0m0,546s
    sys 0m1.082s

    $ czas ./driver.sh jerry 100 5

    real 0m9,315s
    użytkownik 0m4,565s
    sys 0m5.446s

    $ time ./driver.sh irfan 10500

    prawdziwy 1m46,197s
    użytkownik 0m44.869s
    sys 1m12.282s

    $ time ./driver.sh brian 10500

    prawdziwe 0m16,003s
    użytkownik 0m5,135s
    sys 0m10.396s

    $ czas ./driver.sh jerry 10500

    prawdziwe 1m24,414s
    użytkownik 0m39.696s
    sys 0m54.834s

    $ czas ./driver.sh irfan 1000 5

    prawdziwe 4m25,145s
    użytkownik 3m17.286s
    sys 1m21.490s

    $ czas ./driver.sh brian 1000 5

    real 0m19,442s
    użytkownik 0m5,287s
    sys 0m10.751s

    $ czas ./driver.sh jerry 1000 5

    prawdziwe 5m29,136s
    użytkownik 4m48.926s
    sys 0m59.336s

Brian Campbell
źródło
1
Nie sądzę, abyś używał systemu plików dla map, który zasadniczo używa IO do czegoś, co możesz zrobić dość szybko w pamięci.
Irfan Zulfiqar
9
Pliki nie zawsze muszą zostać zapisane na dysku; jeśli nie wywołasz synchronizacji, system operacyjny może po prostu pozostawić je w pamięci. Twój kod wywołuje sed i wykonuje kilka liniowych wyszukiwań, które są bardzo powolne. Zrobiłem kilka szybkich testów porównawczych i moja wersja jest 5-35 razy szybsza.
Brian Campbell
z drugiej strony, natywne tablice bash4 są znacznie lepszym podejściem, aw bash3 nadal możesz trzymać wszystko z dysku bez rozwidlania, używając deklaracji i pośrednictwa.
lhunath
7
„Szybka” i „powłoka” tak naprawdę nie idą w parze: na pewno nie z powodu tego rodzaju problemów z szybkością, o których mówimy na poziomie „unikania drobnych IO”. Możesz wyszukać i użyć / dev / shm, aby zagwarantować brak IO.
jmtd
2
To rozwiązanie mnie zachwyciło i jest po prostu niesamowite. Nadal obowiązuje w 2016 r. To naprawdę powinna być akceptowana odpowiedź.
Gordon,
7

Bash4 obsługuje to natywnie. Nie używaj greplub eval, są najbrzydszymi hackami.

Pełną, szczegółową odpowiedź z przykładowym kodem można znaleźć pod adresem : /programming/3467959

lhunath
źródło
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; }'
}

Przykład:

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
źródło
4

Teraz odpowiadam na to pytanie.

Poniższe skrypty symulują tablice asocjacyjne w skryptach powłoki. Jego proste i bardzo łatwe do zrozumienia.

Mapa to nic innego jak niekończący się ciąg, który ma keyValuePair zapisany jako --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company

spacje są zastępowane przez „: SP:” dla wartości

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

edycja: Właśnie dodano kolejną metodę pobierania wszystkich kluczy.

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
źródło
1
Jesteś eval„ing dane tak, jakby jego kod bash, a co więcej: nie uda się go cytować poprawnie. Obie powodują masę błędów i wstrzyknięcie dowolnego kodu.
lhunath
3

W przypadku Bash 3 istnieje szczególny przypadek, który ma ładne i proste rozwiązanie:

Jeśli nie chcesz obsługiwać wielu zmiennych lub klucze są po prostu nieprawidłowymi identyfikatorami zmiennych, a Twoja tablica ma mniej niż 256 pozycji , możesz nadużywać wartości zwracanych przez funkcję. To rozwiązanie nie wymaga żadnej podpowłoki, ponieważ wartość jest łatwo dostępna jako zmienna, ani żadnej iteracji, aby wydajność krzyczała. Jest również bardzo czytelny, prawie jak wersja Bash 4.

Oto najbardziej podstawowa wersja:

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

Pamiętaj, używaj pojedynczych cudzysłowów w case, w przeciwnym razie podlega globalizacji. Naprawdę przydatne do statycznych / zamrożonych skrótów od samego początku, ale można by napisać generator indeksów z hash_keys=()tablicy.

Uważaj, domyślnie jest to pierwszy, więc możesz odłożyć na bok element zerowy:

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

Uwaga: długość jest teraz nieprawidłowa.

Alternatywnie, jeśli chcesz zachować indeksowanie od zera, możesz zarezerwować inną wartość indeksu i zabezpieczyć się przed nieistniejącym kluczem, ale jest mniej czytelny:

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

Lub, aby zachować poprawną długość, przesuń indeks o jeden:

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
źródło
2

Możesz używać dynamicznych nazw zmiennych i pozwolić im działać jak klucze w tablicy mieszającej.

Na przykład, jeśli masz plik wejściowy z dwiema kolumnami, imieniem i kredytem, ​​jak w przykładzie poniżej, i chcesz zsumować dochód każdego użytkownika:

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

Polecenie poniżej zsumuje wszystko, używając zmiennych dynamicznych jako kluczy, w postaci mapy _ $ {person} :

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

Aby przeczytać wyniki:

set | grep map

Wynik będzie:

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

Rozwijając te techniki, rozwijam na GitHub funkcję, która działa podobnie jak obiekt HashMap , shell_map .

W celu tworzenia " instancji HashMap " funkcja shell_map może tworzyć swoje kopie pod różnymi nazwami. Każda nowa kopia funkcji będzie miała inną zmienną $ FUNCNAME. Następnie $ FUNCNAME jest używany do tworzenia przestrzeni nazw dla każdej instancji Map.

Klucze mapy są zmiennymi globalnymi w postaci $ FUNCNAME_DATA_ $ KEY, gdzie $ KEY jest kluczem dodanym do mapy. Te zmienne są zmiennymi dynamicznymi .

Poniżej zamieszczę uproszczoną wersję, abyś mógł użyć jako przykładu.

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

Stosowanie:

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
źródło
2

Jeszcze inny sposób niezgodny z bash-4 (tj. Bash 3, Mac):

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

Wydruki:

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

Funkcja z caseatrybutami działa jak tablica asocjacyjna. Niestety nie może go używać return, więc ma echoswoje wyjście, ale nie stanowi to problemu, chyba że jesteś purystą, który stroni od rozwidlających podpowłok.

Walter Tross
źródło
1

Szkoda, że ​​wcześniej nie widziałem tego pytania - napisałem bibliotekę shell-framework, która zawiera między innymi mapy (tablice asocjacyjne). Ostatnią wersję można znaleźć tutaj .

Przykład:

#!/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")"
Beggy
źródło
1

Dodanie kolejnej opcji, jeśli jq jest dostępne:

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 '"' 
krytyka
źródło
0

Jak już wspomniałem, prawdą jest, że najlepszą metodą jest zapisanie klucza / wartości do pliku, a następnie użycie grep / awk do ich odzyskania. Brzmi jak wszelkiego rodzaju niepotrzebne IO, ale pamięć podręczna dysku włącza się i sprawia, że ​​jest niezwykle wydajna - znacznie szybsza niż próba przechowywania ich w pamięci za pomocą jednej z powyższych metod (jak pokazują testy porównawcze).

Oto szybka, czysta metoda, którą lubię:

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`

Jeśli chcesz wymusić pojedynczą wartość na klucz, możesz również wykonać małą akcję grep / sed w hput ().

Al P.
źródło
0

kilka lat temu napisałem bibliotekę skryptów dla basha, która wspierała między innymi tablice asocjacyjne (logowanie, pliki konfiguracyjne, rozszerzone wsparcie dla argumentów wiersza poleceń, generowanie pomocy, testowanie jednostkowe itp.). Biblioteka zawiera opakowanie dla tablic asocjacyjnych i automatycznie przełącza się na odpowiedni model (wewnętrzny dla bash4 i emulowany dla poprzednich wersji). Nazywał się shell-framework i był hostowany na origo.ethz.ch, ale dziś zasób jest zamknięty. Jeśli ktoś nadal tego potrzebuje, mogę się tym z tobą podzielić.

Beggy
źródło
Może warto umieścić to na githubie
Mark K Cowan
0

Shell nie ma wbudowanej mapy, takiej jak struktura danych, używam nieprzetworzonego ciągu do opisywania takich elementów:

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

podczas wyodrębniania przedmiotów i ich atrybutów:

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

Wydaje się, że nie jest to mądre niż odpowiedź innych ludzi, ale łatwe do zrozumienia dla nowych ludzi.

coanor
źródło
-1

Zmodyfikowałem rozwiązanie Vadima w następujący sposób:

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

Zmiana polega na map_get, aby zapobiec zwracaniu błędów, jeśli zażądasz klucza, który nie istnieje, chociaż efektem ubocznym jest to, że po cichu ignoruje brakujące mapy, ale lepiej pasuje do mojego przypadku użycia, ponieważ po prostu chciałem sprawdzić klucz, aby pominąć elementy w pętli.

Haravikk
źródło
-1

Późna odpowiedź, ale rozważ rozwiązanie problemu w ten sposób, używając wbudowanego bash odczytanego, jak zilustrowano we fragmencie kodu z poniższego skryptu firewall ufw. Takie podejście ma tę zaletę, że wykorzystuje dowolną liczbę rozdzielonych zestawów pól (nie tylko 2). Użyliśmy | separator, ponieważ specyfikatory zakresu portów mogą wymagać dwukropka, np. 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
źródło