Co jest odpowiednikiem słowników Python, ale w Bash (powinno działać w systemach OS X i Linux).
Co jest odpowiednikiem słowników Python, ale w Bash (powinno działać w systemach OS X i Linux).
Odpowiedzi:
Bash 4 natywnie obsługuje tę funkcję. Upewnij się hashbang skrypcie jest #!/usr/bin/env bash
albo #!/bin/bash
więc nie kończy się przy użyciu sh
. Upewnij się, że albo wykonujesz skrypt bezpośrednio, albo wykonujesz script
go bash script
. (Nie faktycznie wykonujący skrypt Bash z Basha nie stało, i będzie bardzo mylące!)
Deklarujesz tablicę asocjacyjną, wykonując:
declare -A animals
Możesz wypełnić go elementami za pomocą normalnego operatora przypisania tablicy. Na przykład, jeśli chcesz mieć mapę animal[sound(key)] = animal(value)
:
animals=( ["moo"]="cow" ["woof"]="dog")
Lub scal je:
declare -A animals=( ["moo"]="cow" ["woof"]="dog")
Następnie używaj ich tak jak normalnych tablic. Posługiwać się
animals['key']='value'
ustawić wartość
"${animals[@]}"
aby rozwinąć wartości
"${!animals[@]}"
(zauważ !
), aby rozwinąć klucze
Nie zapomnij ich zacytować:
echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done
Przed bash 4 nie masz tablic asocjacyjnych. Nie używaj ich eval
do emulacji . Unikaj eval
jak zarazy, ponieważ jest to plaga skryptów powłoki. Najważniejszym powodem jest to, że eval
traktuje twoje dane jako kod wykonywalny (istnieje również wiele innych powodów).
Przede wszystkim : rozważ aktualizację do wersji bash 4. Ułatwi to cały proces.
Jeśli istnieje powód, dla którego nie można dokonać aktualizacji, declare
jest to znacznie bezpieczniejsza opcja. Nie ocenia danych jako kodu bashowegoeval
robi i jako taki nie pozwala na tak łatwe wprowadzanie dowolnego kodu.
Przygotujmy odpowiedź, wprowadzając pojęcia:
Po pierwsze, pośredniość.
$ 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łącz je razem:
# Set a value:
declare "array_$index=$value"
# Get a value:
arrayGet() {
local array=$1 index=$2
local i="${array}_$index"
printf '%s' "${!i}"
}
Użyjmy tego:
$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow
Uwaga: declare
nie można wprowadzić funkcji. Każde użycie declare
funkcji bash zmienia lokalną zmienną, którą tworzy się w zakres tej funkcji, co oznacza, że nie możemy uzyskać do niej dostępu ani modyfikować tablic globalnych. (W bash 4 możesz użyć deklaruj -g, aby deklarować zmienne globalne - ale w bash 4 możesz w pierwszej kolejności używać tablic asocjacyjnych, unikając tego obejścia).
Podsumowanie:
declare -A
dla tablic asocjacyjnych.declare
opcji, jeśli nie możesz zaktualizować.awk
zamiast tego i całkowicie unikaj problemu.4.x
a nie y
.
sudo port install bash
dla tych (mądrze, IMHO), którzy nie chcą, aby katalogi w PATH były zapisywane dla wszystkich użytkowników bez wyraźnej eskalacji uprawnień dla poszczególnych procesów.
Istnieje podstawianie parametrów, chociaż może to być również nie na PC ... jak pośrednie.
#!/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]##*:}"
Oczywiście sposób BASH 4 jest lepszy, ale jeśli potrzebujesz hacka ... wystarczy hack. Możesz przeszukiwać tablicę / skrót za pomocą podobnych technik.
VALUE=${animal#*:}
ARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
Oto, czego szukałem tutaj:
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
Nie działało to dla mnie w wersji bash 4.1.5:
animals=( ["moo"]="cow" )
Możesz dalej zmodyfikować interfejs hput () / hget (), tak aby nazwać skróty w następujący sposób:
hput() {
eval "$1""$2"='$3'
}
hget() {
eval echo '${'"$1$2"'#hash}'
}
i wtedy
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
Pozwala to zdefiniować inne mapy, które nie powodują konfliktów (np. „Rcapitals”, które wyszukują dane według stolic). Ale tak czy inaczej, myślę, że przekonasz się, że to wszystko jest okropne, pod względem wydajności.
Jeśli naprawdę chcesz szybkiego wyszukiwania skrótów, istnieje straszny, straszny hack, który naprawdę działa naprawdę dobrze. Jest to: zapisz klucz / wartości do pliku tymczasowego, jeden na linię, a następnie użyj „grep” ^ $ klucz ”, aby je wyciągnąć, używając rur z cięciem, awk, sed lub cokolwiek innego, aby odzyskać wartości.
Tak jak powiedziałem, brzmi to okropnie i wygląda na to, że powinien być powolny i robić wszelkiego rodzaju niepotrzebne operacje wejścia / wyjścia, ale w praktyce jest bardzo szybki (pamięć podręczna dysku jest niesamowita, prawda?), Nawet w przypadku bardzo dużego skrótu stoły Sam musisz wymusić unikalność klucza itp. Nawet jeśli masz tylko kilkaset wpisów, kombinacja pliku wyjściowego / grep będzie nieco szybsza - z mojego doświadczenia kilka razy szybsza. Zjada także mniej pamięci.
Oto jeden ze sposobów, 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`
System plików to struktura drzewa, która może być używana jako mapa mieszania. Tabela skrótów będzie katalogiem tymczasowym, klucze będą nazwami plików, a wartości będą zawartością plików. Zaletą jest to, że może obsługiwać ogromne mapy skrótów i nie wymaga konkretnej powłoki.
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
Oczywiście jest wolny, ale nie taki wolny. Przetestowałem to na moim komputerze, z dyskiem SSD i btrfs , i robi około 3000 elementów odczytu / zapisu na sekundę .
mkdir -d
? (Nie 4.3, na Ubuntu 14. Chciałbym skorzystać z mkdir /run/shm/foo
, lub jeśli to zapełniłoby RAM mkdir /tmp/foo
mktemp -d
zamiast tego miał na myśli?
$value=$(< $hashtable/$key)
i value=$(< $hashtable/$key)
? Dzięki!
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
${var#start}
usuwa tekst zaczynający się od początku wartości przechowywanej w zmiennej var .
Rozważ rozwiązanie wykorzystujące wbudowany bash odczytany jak pokazano we fragmencie kodu z następującego skryptu zapory ufw. Zaletą tego podejścia jest wykorzystanie dowolnej liczby zestawów pól rozdzielanych (nie tylko 2). Użyliśmy | ogranicznik, ponieważ specyfikatory zakresu portów mogą wymagać dwukropka, tj. 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
IFS=$'|' read -r first rest <<< "$fields"
Zgadzam się z @lhunath i innymi, że tablica asocjacyjna jest najlepszym rozwiązaniem dla Bash 4. Jeśli utkniesz w Bash 3 (OSX, stare dystrybucje, których nie możesz zaktualizować), możesz również użyć wyrażenia, które powinno być wszędzie, ciąg i wyrażenia regularne. Podoba mi się to szczególnie, gdy słownik nie jest zbyt duży.
Napisz swoją mapę jako ciąg znaków (zwróć uwagę na separator „,” również na początku i na końcu)
animals=",moo:cow,woof:dog,"
Użyj wyrażenia regularnego, aby wyodrębnić wartości
get_animal {
echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
}
Podziel ciąg, 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
Naprawdę podobała mi się odpowiedź Al P, ale chciałem tanio egzekwować tanio, więc posunąłem się o krok dalej - skorzystaj z katalogu. Istnieją pewne oczywiste ograniczenia (limity plików katalogu, 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
W moich testach działa też odrobinę lepiej.
$ 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ę.
Edycja: dodawanie hdestroy ()
Dwie rzeczy, możesz użyć 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żna ponownie zaimplementować, używając następującego odczytu:
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, powrót powoduje 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. Oszczędza to kosztów czytania i tworzenia zarówno grep, jak i awk. Użycie / dev / shm dla obu implementacji dało następujące użycie hgetu czasowego w haszu z 3 pozycjami w poszukiwaniu 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
Odczyt / 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. Można to wszystko przypisać widelcowi ponad głową, ze względu na użycie /dev/shm
.
Współpracownik właśnie wspomniał o tym wątku. Niezależnie zaimplementowałem tabele skrótów w bash i nie jest to zależne od wersji 4. Z mojego posta na blogu w marcu 2010 roku (przed niektórymi odpowiedziami tutaj ...) zatytułowanym Tabele skrótów w bash :
Ja poprzednio używany cksum
do hash ale ponieważ tłumaczone ciąg hashCode Javy do rodzimej bash / zsh.
# Here's the hashing function
ht() {
local h=0 i
for (( i=0; i < ${#1}; i++ )); do
let "h=( (h<<5) - h ) + $(printf %d \'${1:$i:1})"
let "h |= h"
done
printf "$h"
}
# 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
echo ${#myhash[@]} # "2" - there are two values (note, zsh doesn't count right)
Nie jest dwukierunkowy, a wbudowany sposób jest o wiele lepszy, ale i tak nie należy go używać. Bash jest przeznaczony do szybkich działań jednorazowych i takie rzeczy rzadko powinny wiązać się ze złożonością, która może wymagać skrótów, chyba że u ciebie ~/.bashrc
i twoich przyjaciół.
Przed bash 4 nie ma dobrego sposobu na użycie tablic asocjacyjnych w bash. Najlepiej jest użyć przetłumaczonego języka, który faktycznie obsługuje takie rzeczy, jak awk. Z drugiej strony bash 4 tak wspierać je.
Jeśli chodzi o mniej dobre sposoby w bash 3, oto odniesienie, które może pomóc: http://mywiki.wooledge.org/BashFAQ/006
Rozwiązanie Bash 3:
Czytając niektóre odpowiedzi, przygotowałem krótką funkcję, którą chciałbym wesprzeć, która mogłaby 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[@])
Użyłem również sposobu bash4, ale znajduję irytujący błąd.
Musiałem dynamicznie aktualizować zawartość tablicy asocjacyjnej, więc skorzystałem z 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
Przekonałem się, że dodanie bash 4.3.11 do istniejącego klucza w dykcie spowodowało dodanie wartości, jeśli już jest obecna. Na przykład po pewnym powtórzeniu treść wartości brzmiała „checkKOcheckKOallCheckOK”, co nie było dobre.
Nie ma problemu z wersją bash 4.3.39, gdzie dołączenie istniejącego klucza oznacza zastąpienie wartości aktualnej, jeśli jest już obecna.
Rozwiązałem to tylko czyszczenie / deklarowanie tablicy asocjacyjnej statusCheck przed cyklem:
unset statusCheck; declare -A statusCheck
HashMapy tworzę w bash 3 przy użyciu zmiennych dynamicznych. Wyjaśniłem, jak to działa w mojej odpowiedzi na: Tablice asocjacyjne w skryptach Shell
Możesz także rzucić okiem na shell_map , czyli implementację HashMap wykonaną w bash 3.