Ассоциативные массивы в сценариях оболочки

116

Нам нужен сценарий, который имитирует ассоциативные массивы или структуру данных типа Map для сценариев оболочки, любое тело?

Ирфан Зульфикар
источник

Ответы:

20

Чтобы добавить к ответу Ирфана , вот более короткая и быстрая версия, get()поскольку она не требует итерации по содержимому карты:

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

    map=${!mapName}
    value="$(echo $map |sed -e "s/.*--${key}=\([^ ]*\).*/\1/" -e 's/:SP:/ /g' )"
}
Джерри Пеннер
источник
16
разветвление подоболочки и sed вряд ли оптимально. Bash4 поддерживает это изначально, а bash3 имеет лучшие альтернативы.
lhunath
149

Другой вариант, если переносимость не является вашей основной задачей, - использовать ассоциативные массивы, встроенные в оболочку. Это должно работать в 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]}

В зависимости от оболочки вам может потребоваться выполнить a typeset -A newmapвместо declare -A newmapили в некоторых случаях это может не понадобиться вовсе.

Брайан Кэмпбелл
источник
Спасибо, что отправили ответ, я думаю, что это лучший способ сделать это для парней, которые будут использовать bash 4.0 или выше.
Irfan Zulfiqar
Я бы добавил немного клуджа, чтобы убедиться, что BASH_VERSION установлен и> = 4. И да, BASH 4 действительно, действительно крутой!
Тим Пост
Я использую что-то вроде этого. Как лучше всего «поймать» ошибку, когда индекс / индекс массива не существует? Например, что, если я использовал нижний индекс как параметр командной строки, а пользователь сделал опечатку и ввел «designatio»? Я получаю ошибку «неверный индекс массива», но не знаю, как проверить ввод во время поиска в массиве, если это возможно?
Джер
3
@Jer Это довольно непонятно, но чтобы определить, установлена ​​ли переменная в оболочке, вы можете использовать test -z ${variable+x}( xне имеет значения, это может быть любая строка). Для ассоциативного массива в Bash вы можете сделать то же самое; использовать test -z ${map[key]+x}.
Брайан Кэмпбелл,
95

Другой способ, не связанный с bash 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 /]]. или что угодно.

Bubnoff
источник
2
Этот метод хорош, когда у вас действительно нет Bash 4. Но я думаю, что строка, которая извлекает ЗНАЧЕНИЕ, будет безопаснее таким образом: ЗНАЧЕНИЕ = $ {животное # *:}. При наличии только одного символа # сопоставление остановится на первом «:». Это также позволяет значениям содержать ":".
Ced-le-pingouin
@ Ced-le-pingouin ~ Отличный момент! Я этого не уловил. Я отредактировал свой пост, чтобы отразить предложенные вами улучшения.
Bubnoff
1
Это довольно хакерская эмуляция ассоциативных массивов с использованием подстановки параметров BASH. Параметр "key" заменяет все, что находится до двоеточия, а шаблон значения заменяет все, что находится после двоеточия. Подобно совпадению с подстановочными знаками регулярного выражения. Так что НЕ настоящий ассоциативный массив. Не рекомендуется, если вам не нужен простой для понимания способ реализации функций, подобных хеш-функциям / ассоциативным массивам, в BASH 3 или ниже. Но это работает! Подробнее здесь: tldp.org/LDP/abs/html/parameter-substitution.html#PSOREX2
Bubnoff
1
Это не реализует ассоциативный массив, потому что он не дает возможности искать элемент по ключу. Он предоставляет только способ найти каждый ключ (и значение) по числовому индексу. (Элемент можно найти по ключу путем итерации по массиву, но это не то, что требуется для ассоциативного массива.)
Эрик Постпишил,
@EricPostpischil Верно. Это всего лишь взлом. Это позволяет человеку использовать знакомый синтаксис в настройке, но все же требует итерации по массиву, как вы говорите. В своем предыдущем комментарии я попытался прояснить, что это определенно не ассоциативный массив, и я даже не рекомендую его, если у вас есть альтернативы. На мой взгляд, единственный аргумент в его пользу заключается в том, что его легко писать и использовать для тех, кто знаком с другими языками, такими как Python. Если вы находитесь в точке, где вы действительно хотите реализовать ассоциативные массивы в BASH 3, вам может потребоваться немного повторить свои шаги.
Bubnoff
34

Я думаю, что вам нужно сделать шаг назад и подумать о том, что на самом деле представляет собой карта или ассоциативный массив. Все это способ сохранить значение для данного ключа и быстро и эффективно вернуть это значение. Вы также можете захотеть перебрать ключи для получения каждой пары ключ-значение или удалить ключи и связанные с ними значения.

Теперь подумайте о структуре данных, которую вы все время используете в сценариях оболочки и даже просто в оболочке без написания сценария, обладающего этими свойствами. Тупик? Это файловая система.

На самом деле, все, что вам нужно, чтобы иметь ассоциативный массив при программировании оболочки, - это временный каталог. mktemp -dваш конструктор ассоциативного массива:

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

Если вам не хочется использовать echoи cat, вы всегда можете написать несколько маленьких оберток; эти модели созданы по образцу Irfan, хотя они просто выводят значение, а не устанавливают произвольные переменные, например $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

edit : этот подход на самом деле немного быстрее, чем линейный поиск с использованием sed, предложенный задающим вопрос, а также более надежен (он позволяет ключам и значениям содержать -, =, пробел, qnd ": SP:"). Тот факт, что он использует файловую систему, не замедляет его; эти файлы никогда не гарантированно будут записаны на диск, если вы не позвоните sync; для подобных временных файлов с коротким сроком жизни не исключено, что многие из них никогда не будут записаны на диск.

Я провел несколько тестов кода Ирфана, модификации кода Ирфана Джерри и своего кода, используя следующую программу-драйвер:

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

Результаты:

    $ time ./driver.sh irfan 10 5

    реальный 0m0.975s
    пользователь 0m0.280s
    sys 0m0.691s

    $ time ./driver.sh брайан 10 5

    реальный 0m0.226s
    пользователь 0m0.057s
    sys 0m0.123s

    $ time ./driver.sh jerry 10 5

    реальный 0m0,706s
    пользователь 0m0.228s
    sys 0m0.530s

    $ time ./driver.sh irfan 100 5

    реальный 0m10.633s
    пользователь 0m4.366s
    sys 0m7.127s

    $ time ./driver.sh брайан 100 5

    реальный 0m1.682s
    пользователь 0m0.546s
    sys 0m1.082s

    $ time ./driver.sh jerry 100 5

    реальный 0 мин. 9,315 сек.
    пользователь 0m4.565s
    sys 0m5.446s

    $ time ./driver.sh irfan 10 500

    реальный 1 мин. 46,197 сек.
    пользователь 0m44.869s
    sys 1m12.282s

    $ time ./driver.sh брайан 10 500

    реальный 0 мин. 16.003 сек.
    пользователь 0m5.135s
    sys 0m10.396s

    $ time ./driver.sh jerry 10 500

    реальный 1 мин. 24,414 сек.
    пользователь 0m39.696s
    sys 0m54.834s

    $ time ./driver.sh irfan 1000 5

    реальный 4 мин. 25,145 сек.
    пользователь 3m17.286s
    sys 1m21.490s

    $ time ./driver.sh брайан 1000 5

    реальный 0m19.442s
    пользователь 0m5.287s
    sys 0m10.751s

    $ time ./driver.sh jerry 1000 5

    реальные 5 мин. 29,136 сек.
    пользователь 4m48.926s
    sys 0m59.336s

Брайан Кэмпбелл
источник
1
Я не думаю, что вам следует использовать файловую систему для карт, которая в основном использует ввод-вывод для чего-то, что вы можете сделать довольно быстро в памяти.
Irfan Zulfiqar
9
Файлы не обязательно когда-либо будут записаны на диск; если вы не вызовете синхронизацию, операционная система может просто оставить их в памяти. Ваш код обращается к sed и выполняет несколько линейных поисков, которые очень медленные. Я провел несколько быстрых тестов, и моя версия в 5-35 раз быстрее.
Брайан Кэмпбелл,
с другой стороны, собственные массивы bash4 - это значительно лучший подход, и в bash3 вы по-прежнему можете хранить все с диска без разветвления, используя объявление и косвенное обращение.
lhunath
7
«быстрый» и «оболочка» в любом случае не идут вместе: уж точно не для тех проблем со скоростью, о которых мы говорим на уровне «избегать минимального ввода-вывода». Вы можете искать и использовать / dev / shm, чтобы гарантировать отсутствие ввода-вывода.
jmtd 01
2
Это решение меня поразило, и оно просто потрясающее. По-прежнему верно в 2016 году. Это действительно должен быть принятый ответ.
Гордон
7

Bash4 изначально поддерживает это. Не используйте grepили eval, это самый уродливый из хаков.

Подробный, подробный ответ с примером кода см .: /programming/3467959

lhunath
источник
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; }'
}

Пример:

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
Вадим
источник
4

Теперь отвечу на этот вопрос.

Следующие сценарии моделируют ассоциативные массивы в сценариях оболочки. Это просто и очень легко понять.

Карта - это не что иное, как бесконечная строка, в которой keyValuePair сохранен как --name = Irfan --designation = SSE --company = My: SP: Own: SP: Company

пробелы заменяются на ': SP:' для значений

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

изменить: просто добавил еще один метод для получения всех ключей.

getKeySet() {
    if [ "$#" != 1 ]; 
    then 
        exit 1; 
    fi

    mapName=$1; 

    eval map="\"\$$mapName\""

    keySet=`
           echo $map | 
           sed -e "s/=[^ ]*//g" -e "s/\([ ]*\)--/\1/g"
          `
}
Ирфан Зульфикар
источник
1
Вы evalвводите данные, как если бы это был код на bash, и более того: вы неправильно их цитируете. Оба вызывают массу ошибок и произвольное внедрение кода.
lhunath
3

Для Bash 3 есть частный случай, у которого есть хорошее и простое решение:

Если вы не хотите обрабатывать большое количество переменных или ключи являются просто недопустимыми идентификаторами переменных, и ваш массив гарантированно содержит менее 256 элементов , вы можете злоупотреблять возвращаемыми значениями функции. Это решение не требует какой-либо подоболочки, поскольку значение легко доступно в виде переменной, а также каких-либо итераций, чтобы производительность кричала. Также он очень удобочитаемый, почти как версия Bash 4.

Вот самая простая версия:

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

Помните, используйте одинарные кавычки case, иначе это может быть подстановкой. Действительно полезно для статических / замороженных хэшей с самого начала, но можно написать генератор индекса из hash_keys=()массива.

Остерегайтесь, по умолчанию используется первый, поэтому вы можете отложить нулевой элемент:

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

Предупреждение: длина теперь неверная.

В качестве альтернативы, если вы хотите сохранить индексирование с нуля, вы можете зарезервировать другое значение индекса и защититься от несуществующего ключа, но он менее читабелен:

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

Или, чтобы сохранить правильную длину, сместите индекс на единицу:

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
источник
2

Вы можете использовать имена динамических переменных и позволить именам переменных работать как ключи хэш-карты.

Например, если у вас есть входной файл с двумя столбцами, имя, кредит, как в примере ниже, и вы хотите просуммировать доход каждого пользователя:

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 » функция shell_map может создавать свои копии под разными именами. Каждая новая копия функции будет иметь другую переменную $ FUNCNAME. Затем $ FUNCNAME используется для создания пространства имен для каждого экземпляра карты.

Ключи карты - это глобальные переменные в форме $ FUNCNAME_DATA_ $ KEY, где $ KEY - это ключ, добавленный в карту. Эти переменные являются динамическими переменными .

Ниже я помещу его упрощенную версию, чтобы вы могли использовать ее в качестве примера.

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

Использование:

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!"
Бруно Неграу Зика
источник
2

Еще один способ, отличный от bash-4 (т. Е. 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

Печать:

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

Функция с caseэлементами действует как ассоциативный массив. К сожалению, он не может использовать return, поэтому он должен echoвыводить его, но это не проблема, если вы не пурист, который избегает разветвления подоболочки.

Уолтер Тросс
источник
1

Как жаль, что я не видел этого вопроса раньше - я написал библиотеку- оболочку, которая содержит среди прочего карты (ассоциативные массивы). Последнюю версию можно найти здесь .

Пример:

#!/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
источник
1

Добавляем еще один вариант, если доступен 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 '"' 
critium
источник
0

Как уже упоминалось, я обнаружил, что наиболее эффективный метод - это записать key / vals в файл, а затем использовать grep / awk для их получения. Это звучит как всевозможные ненужные операции ввода-вывода, но дисковый кеш срабатывает и делает его чрезвычайно эффективным - намного быстрее, чем пытаться сохранить их в памяти с помощью одного из вышеперечисленных методов (как показывают тесты).

Вот быстрый и чистый метод, который мне нравится:

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`

Если вы хотите применить одно значение для каждого ключа, вы также можете выполнить небольшое действие grep / sed в hput ().

Аль П.
источник
0

Несколько лет назад я написал библиотеку сценариев для bash, которая среди других функций поддерживала ассоциативные массивы (ведение журнала, файлы конфигурации, расширенная поддержка аргументов командной строки, создание справки, модульное тестирование и т. д.). Библиотека содержит оболочку для ассоциативных массивов и автоматически переключается на соответствующую модель (внутреннюю для bash4 и эмулируемую для предыдущих версий). Он назывался shell-framework и размещался на сайте origo.ethz.ch, но сегодня ресурс закрыт. Если кому-то это все еще нужно, могу поделиться с вами.

Beggy
источник
Может, стоит выложить это на github
Марк К. Коуэн
0

В оболочке нет встроенной карты, такой как структура данных, я использую необработанную строку для описания таких элементов:

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

при извлечении предметов и их атрибутов:

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

Кажется, что это не умнее, чем ответ других людей, но легко понять, что новички могут пустить пыль в глаза.

coanor
источник
-1

Я модифицировал решение Вадима следующим образом:

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

Изменение заключается в map_get, чтобы он не возвращал ошибки, если вы запрашиваете ключ, который не существует, хотя побочным эффектом является то, что он также будет молча игнорировать отсутствующие карты, но это лучше подходит для моего варианта использования, поскольку я просто хотел проверить наличие ключа, чтобы пропустить элементы в цикле.

Haravikk
источник
-1

Поздний ответ, но рассмотрите возможность решения проблемы таким образом, используя встроенную функцию 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
AsymLabs
источник