Jak definiować tabele hash w Bash?
Jaki jest odpowiednik słowników Pythona ale w Bash (powinien działać na OS X i Linux).
16 answers
Bash 4
Bash 4 natywnie obsługuje tę funkcję. Upewnij się, że hashbang twojego skryptu to #!/usr/bin/env bash
lub #!/bin/bash
lub cokolwiek innego, co odwołuje się do bash
, a nie sh
. Upewnij się, że wykonujesz skrypt i nie robisz czegoś głupiego, jak sh script
, co spowodowałoby zignorowanie twojego bash
hashbang. To jest podstawowe rzeczy, ale tak wielu wciąż zawodzi w tym, stąd ponowna iteracja.
Deklarujesz tablicę asocjacyjną wykonując:
declare -A animals
Możesz wypełnić go elementami użycie normalnego operatora przypisania tablicy:
animals=( ["moo"]="cow" ["woof"]="dog")
Lub połączyć je:
declare -A animals=( ["moo"]="cow" ["woof"]="dog")
Następnie używaj ich jak normalnych tablic. "${animals[@]}"
rozszerza wartości, "${!animals[@]}"
(zauważ, że !
) rozszerza klucze. Nie zapomnij ich zacytować:
echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done
Bash 3
Przed bash 4 nie masz tablic asocjacyjnych. nie używaj eval
do ich naśladowania . Należy unikać eval jak plaga, ponieważ to jest plaga skryptów powłoki. Najważniejsze powodem jest to, że nie chcesz traktować swoich danych jako kodu wykonywalnego (jest też wiele innych powodów).
Po pierwsze i najważniejsze: po prostu rozważ aktualizację do bash 4. Poważnie. przyszłość jest teraz , przestań żyć przeszłością i {41]} cierpieć z niej {42]} zmuszając głupie złamane i brzydkie hacki na Twój kod i każda biedna dusza utkwiła w jego utrzymaniu.
Jeśli masz jakąś głupią wymówkę, dlaczego " nie możesz uaktualnić", declare
jest o wiele bezpieczniejszą opcją. Informatyka nie ocenia danych tak, jak robi to kod bash, jak robi to eval
, i jako taki nie pozwala na dowolne wstrzyknięcie kodu tak łatwo.
Przygotujmy odpowiedź wprowadzając pojęcia:
Po pierwsze, indirection (poważnie; nigdy nie używaj tego, chyba że jesteś chory psychicznie lub masz inną złą wymówkę do pisania hacków).
$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow
Po Drugie, declare
:
$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow
Połączyć:
# Set a value:
declare "array_$index=$value"
# Get a value:
arrayGet() {
local array=$1 index=$2
local i="${array}_$index"
printf '%s' "${!i}"
}
Let ' s use it:
$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow
Uwaga: declare
nie można umieścić w funkcja. Każde użycie declare
wewnątrz funkcji bash zamienia zmienną, którą tworzy local W Zakres tej funkcji, co oznacza, że nie możemy uzyskać dostępu ani modyfikować za pomocą niej tablic globalnych. (W bash 4 możesz użyć declare-g do deklarowania zmiennych globalnych - ale w bash 4 powinieneś używać tablic asocjacyjnych, a nie tego hacka.)
Podsumowanie
Uaktualnij do bash 4 i użyj declare -A
. Jeśli nie możesz, rozważ całkowite przejście na awk
, zanim zrobisz brzydkie hacki jako opisane powyżej. I zdecydowanie trzymaj się z dala od hakerstwa.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-18 18:31:26
Istnieje podstawianie parametrów, choć może to być również un-PC ...jak indirection.
#!/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]##*:}"
Sposób BASH 4 jest oczywiście lepszy, ale jeśli potrzebujesz hack ...wystarczy włamanie. Można przeszukiwać tablicę / hash za pomocą podobnych technik.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-09 18:11:26
Tego właśnie szukałem:
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
To nie działa dla mnie z bash 4.1.5:
animals=( ["moo"]="cow" )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-23 08:36:05
Możesz dalej modyfikować interfejs hput()/hget() tak, że nazwałeś hasze w następujący sposób:
hput() {
eval "$1""$2"='$3'
}
hget() {
eval echo '${'"$1$2"'#hash}'
}
A następnie
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
To pozwala zdefiniować inne mapy, które nie są ze sobą sprzeczne (np. 'rcapitals', który przeszukuje kraj według stolicy). Ale tak czy siak, myślę, że przekonasz się, że to wszystko jest dość straszne, jeśli chodzi o wydajność.
Jeśli naprawdę chcesz szybkiego wyszukiwania hash, jest straszny, straszny hack, który naprawdę działa naprawdę dobrze. To jest to: napisz swój klucz / wartości do pliku tymczasowego, jeden-na linię, a następnie użyj 'grep" ^ $ key"', aby je wyciągnąć, używając rur z cut, awk, sed lub cokolwiek, aby pobrać wartości.
Jak już mówiłem, brzmi to okropnie i brzmi, jakby miało być wolne i robić wszelkiego rodzaju niepotrzebne IO, ale w praktyce jest bardzo szybkie (pamięć podręczna dysku jest niesamowita, prawda?), nawet dla bardzo dużych tabel hashowych. Musisz sam wyegzekwować wyjątkowość klucza itp. Nawet jeśli masz tylko kilkaset wpisów, combo plik wyjściowy / grep jest będzie trochę szybciej - z doświadczenia wiem, że kilka razy szybciej. Zużywa też mniej pamięci.
Jest jeden sposób, aby to zrobić:
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`
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-29 16:09:52
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-29 22:45:35
Rozważ rozwiązanie za pomocą wbudowanego w bash przeczytaj Jak pokazano w poniższym fragmencie kodu ze skryptu zapory ufw. To podejście ma tę zaletę, że używa się tak wielu rozdzielonych zestawów pól (nie tylko 2), Jak jest to pożądane. Wykorzystaliśmy | delimiter, ponieważ specyfikatory zakresu portów mogą wymagać dwukropka, czyli 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-15 17:16:30
Wystarczy użyć systemu plików
System plików jest strukturą drzewiastą, która może być używana jako mapa hash. Twoja tabela hash będzie katalogiem tymczasowym, Twoje klucze będą nazwami plików, a wartości będą zawartością plików. Zaletą jest to, że może obsługiwać ogromne hashmapy i nie wymaga specjalnej powłoki.
Tworzenie Hashtable
hashtable=$(mktemp -d)
Dodaj element
echo $value > $hashtable/$key
Odczytaj element
value=$(< $hashtable/$key)
Wydajność
Oczywiście, jego wolno, ale nie wolno. Testowałem go na moim komputerze, z dyskiem SSD i btrfs, i robi około 3000 elementów odczytu/zapisu na sekundę.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-06 13:21:19
Zgadzam się z @lhunath i innymi, że tablica asocjacyjna jest drogą do przejścia z Bashem 4. Jeśli utkniesz w Bash 3 (OSX, stare DISTRO, których nie możesz zaktualizować), możesz użyć również expr, które powinno być wszędzie, ciąg znaków i wyrażenia regularne. Podoba mi się szczególnie, gdy słownik nie jest zbyt duży.
- Wybierz 2 separatory, których nie będziesz używać w kluczach i wartościach (np. ',' I':')
-
Zapisz mapę jako łańcuch znaków (zwróć uwagę na separator ',' również na początku i end)
animals=",moo:cow,woof:dog,"
-
Użyj wyrażenia regularnego, aby wyodrębnić wartości
get_animal { echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")" }
-
Podziel łańcuch, aby wyświetlić listę elementów
get_animal_items { arr=$(echo "${animals:1:${#animals}-2}" | tr "," "\n") for i in $arr do value="${i##*:}" key="${i%%:*}" echo "${value} likes to $key" done }
Teraz możesz go użyć:
$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-01 15:13:10
Bardzo podobała mi się odpowiedź Ala P, Ale chciałem tanio wymusić wyjątkowość, więc poszedłem o krok dalej-skorzystałem z katalogu. Istnieją pewne oczywiste ograniczenia (limity plików katalogów, nieprawidłowe nazwy plików), ale powinno to działać w większości przypadków.
hinit() {
rm -rf /tmp/hashmap.$1
mkdir -p /tmp/hashmap.$1
}
hput() {
printf "$3" > /tmp/hashmap.$1/$2
}
hget() {
cat /tmp/hashmap.$1/$2
}
hkeys() {
ls -1 /tmp/hashmap.$1
}
hdestroy() {
rm -rf /tmp/hashmap.$1
}
hinit ids
for (( i = 0; i < 10000; i++ )); do
hput ids "key$i" "value$i"
done
for (( i = 0; i < 10000; i++ )); do
printf '%s\n' $(hget ids "key$i") > /dev/null
done
hdestroy ids
Sprawdza się też nieco lepiej w moich testach.
$ time bash hash.sh
real 0m46.500s
user 0m16.767s
sys 0m51.473s
$ time bash dirhash.sh
real 0m35.875s
user 0m8.002s
sys 0m24.666s
Pomyślałem, że się wtrącę. Zdrowie!
Edit: dodanie hdestroy ()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-14 21:19:11
Dwie rzeczy, możesz używać pamięci zamiast / tmp w dowolnym jądrze 2.6 używając /dev / shm (Redhat) inne dystrybucje mogą się różnić. Również hget może być ponownie zaimplementowany za pomocą odczytu w następujący sposób:
function hget {
while read key idx
do
if [ $key = $2 ]
then
echo $idx
return
fi
done < /dev/shm/hashmap.$1
}
Ponadto, zakładając, że wszystkie klucze są unikalne, zwraca zwarcie pętli odczytu i zapobiega konieczności odczytu wszystkich wpisów. Jeśli twoja implementacja może mieć zduplikowane klucze, po prostu pomiń zwrot. Pozwala to zaoszczędzić koszty czytania i rozwidlania zarówno grep, jak i awk. Using / dev / shm dla obu implementacji uzyskano następujące użycie time hget na hash 3 wpisów szukających ostatniego wpisu:
Grep / Awk:
hget() {
grep "^$2 " /dev/shm/hashmap.$1 | awk '{ print $2 };'
}
$ time echo $(hget FD oracle)
3
real 0m0.011s
user 0m0.002s
sys 0m0.013s
Read/echo:
$ time echo $(hget FD oracle)
3
real 0m0.004s
user 0m0.000s
sys 0m0.004s
Przy wielu inwokacjach nigdy nie widziałem mniej niż 50% poprawy.
Wszystko to można przypisać widelcowi nad głową, ze względu na użycie /dev/shm
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-15 17:44:08
Rozwiązanie Bash 3:
Czytając niektóre z odpowiedzi, stworzyłem krótką, małą funkcję, którą chciałbym wnieść z powrotem, która może pomóc innym.
# Define a hash like this
MYHASH=("firstName:Milan"
"lastName:Adamovsky")
# Function to get value by key
getHashKey()
{
declare -a hash=("${!1}")
local key
local lookup=$2
for key in "${hash[@]}" ; do
KEY=${key%%:*}
VALUE=${key#*:}
if [[ $KEY == $lookup ]]
then
echo $VALUE
fi
done
}
# Function to get a list of all keys
getHashKeys()
{
declare -a hash=("${!1}")
local KEY
local VALUE
local key
local lookup=$2
for key in "${hash[@]}" ; do
KEY=${key%%:*}
VALUE=${key#*:}
keys+="${KEY} "
done
echo $keys
}
# Here we want to get the value of 'lastName'
echo $(getHashKey MYHASH[@] "lastName")
# Here we want to get all keys
echo $(getHashKeys MYHASH[@])
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-29 15:26:17
Przed bash 4 nie ma dobrego sposobu na używanie tablic asocjacyjnych w bash. Najlepszym rozwiązaniem jest użycie języka interpretowanego, który rzeczywiście ma wsparcie dla takich rzeczy, jak awk. Z drugiej strony, bash 4 obsługuje je .
Jeśli chodzi o mniej dobrych sposobów w bash 3, Oto odniesienie, które może pomóc: http://mywiki.wooledge.org/BashFAQ/006
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-12 12:53:37
Współpracownik właśnie wspomniał o tym wątku. Niezależnie zaimplementowałem tabele hash w bash i nie jest to zależne od wersji 4. Z mojego posta na blogu z marca 2010 (przed niektórymi odpowiedziami tutaj...) o nazwie tabele Hash w bash :
# Here's the hashing function
ht() { local ht=`echo "$*" |cksum`; echo "${ht//[!0-9]}"; }
# Example:
myhash[`ht foo bar`]="a value"
myhash[`ht baz baf`]="b value"
echo ${myhash[`ht baz baf`]} # "b value"
echo ${myhash[@]} # "a value b value" though perhaps reversed
Oczywiście, wywołuje zewnętrzne wywołanie cksum i dlatego jest nieco spowolniony, ale implementacja jest bardzo czysta i użyteczna. Nie jest dwukierunkowy, a wbudowany sposób jest o wiele lepszy, ale tak naprawdę nie powinien być używany w każdym razie. Bash jest do szybkich jednorazowych, a takie rzeczy powinny dość rzadko obejmować złożoność, która może wymagać skrótów, chyba że w Twoim .bashrc i przyjaciele.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-18 00:39:57
Aby uzyskać nieco większą wydajność pamiętaj, że grep ma funkcję stop, aby zatrzymać, gdy znajdzie N-te dopasowanie w tym przypadku N będzie równe 1.
Grep --max_count=1 ... lub grep-m 1..
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-01 15:47:27
Użyłem również sposobu bash4, ale znalazłem i denerwujący błąd.
Musiałem dynamicznie zaktualizować zawartość tablicy asocjacyjnej, więc użyłem tego sposobu:
for instanceId in $instanceList
do
aws cloudwatch describe-alarms --output json --alarm-name-prefix $instanceId| jq '.["MetricAlarms"][].StateValue'| xargs | grep -E 'ALARM|INSUFFICIENT_DATA'
[ $? -eq 0 ] && statusCheck+=([$instanceId]="checkKO") || statusCheck+=([$instanceId]="allCheckOk"
done
Dowiedziałem się, że w bash 4.3.11 dołączanie do istniejącego klucza w dict skutkowało dodaniem wartości, jeśli już istnieje. Tak więc na przykład po pewnym powtórzeniu zawartość wartości była "checkKOcheckKOallCheckOK" i to nie było dobre.
Nie ma problemu z bash 4.3.39 gdzie appenging istniejącego klucza oznacza zastąp wartość actuale, jeśli już istnieje.
Rozwiązałem to po prostu czyszczenie / deklarowanie tablicy asocjacyjnej statusCheck przed cyklem:
unset statusCheck; declare -A statusCheck
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-04 06:31:09
Tworzę Hashmapy w bash 3 używając zmiennych dynamicznych. Wyjaśniłem jak to działa w mojej odpowiedzi na: tablice asocjacyjne w skryptach Powłoki
Możesz też zajrzeć do shell_map, który jest implementacją HashMap wykonaną w bash 3.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:42