Що є еквівалентом словників Python, але в Bash (має працювати в OS X і Linux).
Що є еквівалентом словників Python, але в Bash (має працювати в OS X і Linux).
Відповіді:
Bash 4 підтримує цю функцію. Переконайтеся , що hashbang вашого сценарію є #!/usr/bin/env bash
або #!/bin/bash
так що ви не будете використовувати sh
. Переконайтеся, що ви виконуєте сценарій безпосередньо або виконуєте за script
допомогою bash script
. (Насправді виконання сценарію Bash з Bash не відбувається, і це буде дійсно заплутано!)
Ви оголошуєте асоціативний масив, виконуючи:
declare -A animals
Ви можете заповнити його елементами, використовуючи звичайний оператор призначення масиву. Наприклад, якщо ви хочете мати карту animal[sound(key)] = animal(value)
:
animals=( ["moo"]="cow" ["woof"]="dog")
Або об'єднайте їх:
declare -A animals=( ["moo"]="cow" ["woof"]="dog")
Тоді використовуйте їх так само, як звичайні масиви. Використовуйте
animals['key']='value'
встановити значення
"${animals[@]}"
для розширення значень
"${!animals[@]}"
(помітити !
), щоб розгорнути клавіші
Не забудьте процитувати їх:
echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done
До bash 4 у вас немає асоціативних масивів. Не використовуйте eval
для імітації . Уникайте , eval
як чуми, тому що це чума сценаріїв оболонки. Найважливішою причиною є те, що eval
трактує ваші дані як виконуваний код (є й багато інших причин).
Перше і головне : Подумайте про модернізацію до гри 4. Це полегшить вам весь процес.
Якщо ви не можете оновити, declare
це набагато безпечніший варіант. Він не оцінює дані як bash-код, як eval
це робиться, і як такий не дозволяє довільно вводити код досить легко.
Давайте підготуємо відповідь, ввівши поняття:
По-перше, непрямість.
$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow
По-друге declare
:
$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow
Об’єднайте їх:
# Set a value:
declare "array_$index=$value"
# Get a value:
arrayGet() {
local array=$1 index=$2
local i="${array}_$index"
printf '%s' "${!i}"
}
Давайте скористаємося цим:
$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow
Примітка: declare
не можна ставити функцію. Будь-яке використання declare
внутрішньої функції bash перетворює змінну, яку вона створює локальну, на область цієї функції, тобто ми не можемо отримати доступ до неї чи змінити глобальні масиви. (У bash 4 ви можете використовувати объявить -g для оголошення глобальних змінних, але в bash 4 ви можете використовувати асоціативні масиви в першу чергу, уникаючи цього вирішення.)
Підсумок:
declare -A
для асоціативних масивів.declare
опцію, якщо ви не можете оновити.awk
а не уникайте проблеми взагалі.4.x
а не y
.
sudo port install bash
для тих (з розумом, IMHO), які не бажають робити каталоги в PATH для всіх користувачів, що підлягають запису, без явного ескалації привілеїв під час процесу.
Існує підміна параметрів, хоча це може бути і без ПК ... як непряма.
#!/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]##*:}"
Шлях BASH 4 краще, звичайно, але якщо вам потрібен злом ... зробить лише хак. Ви можете шукати масив / хеш за аналогічними методами.
VALUE=${animal#*:}
захистити випадок, колиARRAY[$x]="caesar:come:see:conquer"
for animal in "${ARRAY[@]}"; do
Ось що я шукав тут:
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
Для мене це не спрацювало з bash 4.1.5:
animals=( ["moo"]="cow" )
Ви можете додатково змінити інтерфейс hput () / hget (), щоб ви назвали хеші таким чином:
hput() {
eval "$1""$2"='$3'
}
hget() {
eval echo '${'"$1$2"'#hash}'
}
і потім
hput capitals France Paris
hput capitals Netherlands Amsterdam
hput capitals Spain Madrid
echo `hget capitals France` and `hget capitals Netherlands` and `hget capitals Spain`
Це дозволяє визначити інші карти, які не конфліктують (наприклад, "rcapitals", який здійснює пошук по столиці). Але в будь-якому випадку, я думаю, ви побачите, що все це дуже жахливо, ефективні.
Якщо ви дійсно хочете швидкого пошуку хешу, є жахливий, жахливий злом, який насправді працює дуже добре. Це так: запишіть свій ключ / значення у тимчасовий файл, один на рядок, а потім скористайтеся 'grep' ^ $ key '', щоб отримати їх, використовуючи труби з вирізанням або awk або sed або будь-яким іншим для отримання значень.
Як я вже говорив, це звучить жахливо, і це здається, що це повинно бути повільним і робити всілякі непотрібні введення-виведення, але на практиці це дуже швидко (дисковий кеш-клас є дивним, чи не так?), Навіть для дуже великого хешу столи. Ви повинні самостійно застосувати унікальність ключів і т. Д. Навіть якщо у вас є лише кілька сотень записів, вихідний файл / grep комбо буде досить швидким - на мій досвід, у кілька разів швидшим. Він також їсть менше пам'яті.
Ось один із способів зробити це:
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`
Файлова система - це структура дерева, яку можна використовувати як хеш-карту. Ваша хеш-таблиця буде тимчасовим каталогом, вашими ключами будуть імена файлів, а ваші значення - вміст файлу. Перевага полягає в тому, що він може обробляти величезні хешмапи і не потребує конкретної оболонки.
hashtable=$(mktemp -d)
echo $value > $hashtable/$key
value=$(< $hashtable/$key)
Звичайно, його повільно, але не , що повільно. Я перевірив це на своїй машині, з SSD та btrfs , і він робить близько 3000 елементів читання / запису за секунду .
mkdir -d
? (Не 4.3, на Ubuntu 14. Я б mkdir /run/shm/foo
mkdir /tmp/foo
mktemp -d
малося на увазі замість цього?
$value=$(< $hashtable/$key)
і value=$(< $hashtable/$key)
? Дякую!
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}
видаляє текст старт з початку значення , що зберігається в змінної Var .
Розглянемо рішення, використовуючи bash вбудований зчитування, як показано в фрагменті коду із сценарію брандмауера ufw, який випливає нижче. Цей підхід має перевагу у використанні стільки розмежованих наборів полів (не лише 2), скільки бажано. Ми використали | роздільник, оскільки специфікатори діапазону портів можуть вимагати двокрапки, тобто 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"
Я погоджуюся з @lhunath та іншими людьми, що асоціативний масив - це шлях до Bash 4. Якщо ви дотримуєтеся Bash 3 (OSX, старі дистрибутиви, які ви не можете оновити), ви можете також використовувати expr, який повинен бути скрізь, рядок і регулярні вирази. Мені це подобається особливо, коли словник не надто великий.
Запишіть свою карту у вигляді рядка (відзначте роздільник ',' також на початку та в кінці)
animals=",moo:cow,woof:dog,"
Використовуйте регулярний вираз для вилучення значень
get_animal {
echo "$(expr "$animals" : ".*,$1:\([^,]*\),.*")"
}
Розділіть рядок для переліку елементів
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
}
Тепер ви можете використовувати його:
$ animal = get_animal "moo"
cow
$ get_animal_items
cow likes to moo
dog likes to woof
Мені дуже сподобалась відповідь Аль П, але я хотів, щоб унікальність набула дешевого способу, тому я зробив це на крок далі - скористайтеся каталогом. Існують деякі очевидні обмеження (обмеження файлів каталогів, недійсні імена файлів), але це повинно працювати в більшості випадків.
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
Він також працює трохи краще, ніж у моїх тестах.
$ 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
Тільки думав, що я заїду. Привіт!
Редагувати: Додавання hdestroy ()
Дві речі, ви можете використовувати пам'ять замість / tmp в будь-якому ядрі 2.6, використовуючи / dev / shm (Redhat) інші дистрибутиви, можуть відрізнятися. Також hget можна повторно доповнити, використовуючи читання наступним чином:
function hget {
while read key idx
do
if [ $key = $2 ]
then
echo $idx
return
fi
done < /dev/shm/hashmap.$1
}
Крім того, припускаючи, що всі клавіші унікальні, зворотне коротке замикання циклу зчитування і запобігає необхідності прочитати всі записи. Якщо у вашій реалізації можуть бути повторювані ключі, просто не залишайте повернення. Це заощаджує витрати на читання та розгортання як grep, так і awk. Використання / dev / shm для обох реалізацій дало наступне використання hget часу на 3 хеші запису для пошуку останнього запису:
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
Читання / відлуння:
$ time echo $(hget FD oracle)
3
real 0m0.004s
user 0m0.000s
sys 0m0.004s
на кількох викликах я ніколи не бачив менше, ніж 50% покращення. Це все можна віднести до вилки над головою, завдяки використанню /dev/shm
.
Співробітник щойно згадував цю нитку. Я самостійно реалізував хеш-таблиці в межах bash, і це не залежить від версії 4. З моєї публікації в блозі в березні 2010 року (до деяких відповідей тут ...) під назвою Hash таблиці in bash :
Раніше я використовував cksum
хеш, але з тих пір переклав рядок hashCode Java в рідний 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)
Це не двонаправлене, а вбудований спосіб набагато кращий, але ні в якому разі його реально не використовувати. Bash призначений для швидких разових дій, і такі речі повинні досить рідко включати складність, яка може зажадати хеш, за винятком, можливо, у ваших ~/.bashrc
та друзів.
До bash 4 не існує хорошого способу використання асоціативних масивів у bash. Ваша найкраща ставка - використовувати інтерпретовану мову, яка насправді підтримує такі речі, як awk. З іншого боку, Баш 4 робить їх підтримки.
Що стосується менш хороших способів в bash 3, тут є посилання, ніж це може допомогти: http://mywiki.wooledge.org/BashFAQ/006
Рішення Bash 3:
Читаючи деякі відповіді, я зібрав невелику функцію, яку я хотів би зробити, щоб допомогти іншим.
# 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[@])
Я також використовував bash4 спосіб, але я знаходжу і дратує помилку.
Мені потрібно було динамічно оновлювати вміст асоціативного масиву, тому я використовував такий спосіб:
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
Я з’ясовую, що з bash 4.3.11 додавання до існуючого ключа в диктаті призвело до додавання значення, якщо воно вже є. Так, наприклад, після деякого повторення зміст значення було "checkKOcheckKOallCheckOK", і це було не добре.
Немає проблеми з bash 4.3.39, коли зміна існуючого ключа означає підстановку актуального значення, якщо воно вже є.
Я вирішив це лише очищення / оголошення асоціативного масиву statusCheck перед циклом:
unset statusCheck; declare -A statusCheck
Я створюю HashMaps в bash 3, використовуючи динамічні змінні. Я пояснив, як це працює у моїй відповіді на: Асоціативні масиви в сценаріях Shell
Також ви можете подивитися в shell_map , що є реалізацією HashMap, зробленою в bash 3.