17 ответов:
добавить ответ Ирфана, вот более короткая и быстрая версия
get()так как он не требует итерации по содержимому карты:get() { mapName=; key= map=${!mapName} value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*//" -e 's/:SP:/ /g' )" }
другой вариант, если переносимость не является вашей главной заботой, заключается в использовании ассоциативных массивов, встроенных в оболочку. Это должно работать в bash 4.0 (доступно теперь на большинстве основных дистрибутивов, хотя и не на OS X, если вы не установите его самостоятельно), ksh и zsh:
declare -A newmap newmap[name]="Irfan Zulfiqar" newmap[designation]=SSE newmap[company]="My Own Company" echo ${newmap[company]} echo ${newmap[name]}В зависимости от оболочки, вам может понадобиться сделать
typeset -A newmapвместоdeclare -A newmap, или в некоторых это может вообще не понадобиться.
еще один способ не Баш 4.
#!/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"вы также можете бросить оператор if для поиска там. если [[$var = ~ / blah/ ]]. или еще что-нибудь.
Я думаю, что вам нужно сделать шаг назад и подумать о том, что такое карта, или ассоциативный массив, на самом деле. Все это является способом хранения значения для данного ключа, и получить это значение обратно быстро и эффективно. Вы также можете иметь возможность перебирать ключи для получения каждой пары значений ключей или удалять ключи и связанные с ними значения.
теперь подумайте о структуре данных, которую вы используете все время в сценариях оболочки, и даже просто в оболочке без написания сценария, который имеет это свойство. В тупике? Это файловая система.
действительно, все, что вам нужно иметь ассоциативный массив в программировании оболочки-это временный каталог.
mktemp -dэто ваш конструктор ассоциативного массива:prefix=$(basename -- "") map=$(mktemp -dt ${prefix}) echo >${map}/key somevalue value=$(cat ${map}/key)если вы не хотите использовать
echoиcat, вы всегда можете написать некоторые маленькие обертки; эти смоделированы с Irfan, хотя они просто выводят значение, а не устанавливают произвольные переменные, такие как$value:#!/bin/sh prefix=$(basename -- "") mapdir=$(mktemp -dt ${prefix}) trap 'rm -r ${mapdir}' EXIT put() { [ "$#" != 3 ] && exit 1 mapname=; key=; value= [ -d "${mapdir}/${mapname}" ] || mkdir "${mapdir}/${mapname}" echo $value >"${mapdir}/${mapname}/${key}" } get() { [ "$#" != 2 ] && exit 1 mapname=; key= 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 $valueedit: этот подход на самом деле немного быстрее, чем линейный поиск с использованием sed, предложенный вопрошающим, а также более надежный (он позволяет ключам и значениям содержать -,=, space, qnd ":SP:"). Тот факт, что он использует файловую систему не делает его медленным; эти файлы на самом деле никогда не гарантируется запись на диск, если вы не вызываете
sync; для временных файлов, как это с коротким сроком службы, это не маловероятно, что многие из них никогда не будет записан на диск.Я сделал несколько тестов кода Ирфана, модификацию Джерри кода Ирфана и мой код, используя следующую программу драйвера:
#!/bin/sh mapimpl= numkeys= numvals= . ./${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результаты:
$ 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""='' } hget () { eval echo '${hash'""'#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
#################################################################### # 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 ""="" } # map_get map_name key # @return value # function map_get { alias "" | awk -F"'" '{ print ; }' } # map_keys map_name # @return map keys # function map_keys { alias -p | grep | cut -d'=' -f1 | awk -F"" '{print ; }' }пример:
mapName=$(basename )_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
Bash4 поддерживает Это изначально. Не используйте
grepилиeval, они самые уродливые из хаков.подробный ответ с примером кода см. В разделе: https://stackoverflow.com/questions/3467959
теперь ответить на этот вопрос.
следующие скрипты имитируют ассоциативные массивы в скриптах оболочки. Его просто и очень легко понять.
карта-это не что иное, как бесконечная строка, в которой keyValuePair сохранена как --имя=Ирфан --обозначение=ГСП --компании=Мой:СП:собственный:СП:компания
пробелы заменяются на': SP: 'для значений
put() { if [ "$#" != 3 ]; then exit 1; fi mapName=; key=; value=`echo | sed -e "s/ /:SP:/g"` eval map="\"$$mapName\"" map="`echo "$map" | sed -e "s/--$key=[^ ]*//g"` --$key=$value" eval $mapName="\"$map\"" } get() { mapName=; key=; 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 $valueedit: просто добавлен другой метод, чтобы получить все ключи.
getKeySet() { if [ "$#" != 1 ]; then exit 1; fi mapName=; eval map="\"$$mapName\"" keySet=` echo $map | sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--//g" ` }
для Bash 3, есть частный случай, который имеет хорошее и простое решение:
если вы не хотите обрабатывать много переменных, или ключи просто недопустимые идентификаторы переменных,и Ваш массив гарантированно имеет менее 256 элементов, вы можете злоупотреблять возвращаемыми значениями функции. Это решение не требует какой-либо подобласти, поскольку значение легко доступно в качестве переменной, а также любой итерации, чтобы производительность кричала. Также это очень читабельно, почти как версия Bash 4.
вот самая основная версия:
hash_index() { case 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[$?]}помните, используйте одинарные кавычки в
case, то он подлежит глоббинг. Действительно полезно для статических / замороженных хэшей с самого начала, но можно написать генератор индексов изhash_keys=()массив.осторожно, по умолчанию он первый, поэтому вы можете выделить нулевой элемент:
hash_index() { case 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предостережение: длина теперь неверна.
кроме того, если вы хотите сохранить индексацию на основе нуля, вы можете зарезервировать другое значение индекса и защитить его от несуществующего ключа, но он менее читаем:
hash_index() { case 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[$?]}или, чтобы сохранить длину правильно, смещение индекса на один:
hash_index() { case 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))]}
вы можете использовать динамические имена переменных и пусть имена переменных работают как ключи hashmap.
например, если у вас есть входной файл с двумя столбцами, имя, кредит, как пример ниже, и вы хотите суммировать доход каждого пользователя:
Mary 100 John 200 Mary 50 John 300 Paul 100 Paul 400 David 100команда ниже будет суммировать все, используя динамические переменные в качестве ключей, в виде map_${person}:
while read -r person money; ((map_$person+=$money)); done < <(cat INCOME_REPORT.log)читать результаты:
set | grep mapвыход будет:
map_David=100 map_John=500 map_Mary=150 map_Paul=500разрабатывая эти методы, я разрабатываю на GitHub функцию, которая работает так же, как Объект HashMap,shell_map.
для того, чтобы создать "экземпляры HashMap " the функции shell_map умеет создавать копии себя под разными именами. Каждая новая копия функции будет иметь другую переменную $FUNCNAME. Затем $FUNCNAME используется для создания пространства имен для каждого экземпляра карты.
ключи карты являются глобальными переменными в виде $FUNCNAME_DATA_$KEY, где $KEY-это ключ, добавленный к карте. Эти переменные динамические переменные.
ниже я поставлю упрощенную версию этого, так что вы можете использовать в качестве примера.
#!/bin/bash shell_map () { local METHOD="" case $METHOD in new) local NEW_MAP="" # 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/}" ;; put) local KEY="" local VALUE="" # declares a variable in the global scope eval ${FUNCNAME}_DATA_${KEY}='$VALUE' ;; get) local KEY="" local VALUE="${FUNCNAME}_DATA_${KEY}" echo "${!VALUE}" ;; keys) declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))" ;; name) echo $FUNCNAME ;; contains_key) local KEY="" 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="" unset ${FUNCNAME}_DATA_${KEY} ;; size) compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l ;; *) echo "unsupported operation ''." return 1 ;; esac }использование:
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!"
Я нашел это верно, как уже упоминалось, что лучший способ выполнения-записать ключ/vals в файл, а затем использовать grep/awk для их извлечения. Это звучит как всевозможные ненужные IO, но дисковый кэш срабатывает и делает его чрезвычайно эффективным-гораздо быстрее, чем пытаться хранить их в памяти с помощью одного из вышеперечисленных методов (как показывают тесты).
вот быстрый, чистый метод, который мне нравится:
hinit() { rm -f /tmp/hashmap. } hput() { echo " " >> /tmp/hashmap. } hget() { grep "^ " /tmp/hashmap. | awk '{ print };' } 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`Если вы хотите применить одно значение для каждого ключа, вы также можете сделать небольшое действие grep/sed в hput().
Как жаль, что я не видел вопрос раньше-я написал библиотеку shell-framework который содержит среди прочего карты (ассоциативные массивы). Последняя версия его можно найти здесь.
пример:
#!/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")"
оболочка не имеет встроенной карты, такой как структура данных, я использую необработанную строку для описания таких элементов:
ARRAY=( "item_A|attr1|attr2|attr3" "item_B|attr1|attr2|attr3" "..." )при извлечении элементов и его атрибутов:
for item in "${ARRAY[@]}" do item_name=$(echo "${item}"|awk -F "|" '{print }') item_attr1=$(echo "${item}"|awk -F "|" '{print }') item_attr2=$(echo "${item}"|awk -F "|" '{print }') echo "${item_name}" echo "${item_attr1}" echo "${item_attr2}" doneЭто кажется не умнее, чем ответ других людей, но легко понять для новых людей, чтобы оболочка.
Я изменил решение Вадима следующим образом:
#################################################################### # 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 ""="" } # map_get map_name key # @return value # function map_get { if type -p "" then alias "" | awk -F "'" '{ print ; }'; fi } # map_keys map_name # @return map keys # function map_keys { alias -p | grep | cut -d'=' -f1 | awk -F"" '{print ; }' }изменение заключается в map_get, чтобы предотвратить его возврат ошибок, если вы запросите ключ, который не существует, хотя побочный эффект заключается в том, что он также будет молча игнорировать отсутствующие карты, но он лучше подходит для моего использования, так как я просто хотел проверить ключ, чтобы пропустить элементы в цикле.
несколько лет назад я написал библиотеку сценариев для bash, которая поддерживала ассоциативные массивы среди других функций (ведение журнала, файлы конфигурации, расширенная поддержка аргумента командной строки, генерация справки, модульное тестирование и т. д.). Библиотека содержит оболочку для ассоциативных массивов и автоматически переключается на соответствующую модель (внутреннюю для bash4 и эмуляцию для предыдущих версий). Он назывался shell-framework и размещался по адресу origo.ethz.ch но сегодня ресурс закрыт. Если кто-то еще нуждается я могу поделиться им с вами.
поздний ответ, но рассмотрите возможность решения проблемы таким образом, используя встроенный 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
добавление другой опции, если jq доступен:
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 '"'
Comments