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

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


person Irfan Zulfiqar    schedule 27.03.2009    source источник


Ответы (17)


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

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

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

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

person Brian Campbell    schedule 27.03.2009
comment
Спасибо, что отправили ответ, я думаю, что это лучший способ сделать это для парней, которые будут использовать bash 4.0 или выше. - person Irfan Zulfiqar; 02.04.2009
comment
Я бы добавил немного клуджа, чтобы убедиться, что BASH_VERSION установлен и ›= 4. И да, BASH 4 действительно, действительно крутой! - person Tim Post♦; 14.04.2009
comment
Я использую что-то вроде этого. Как лучше всего обнаружить ошибку, когда индекс / индекс массива не существует? Например, что, если я использовал индекс в качестве параметра командной строки, а пользователь допустил опечатку и ввел обозначение? Я получаю неверную ошибку индекса массива, но не знаю, как проверить ввод во время поиска в массиве, если это возможно? - person Jer; 05.02.2014
comment
@Jer Это довольно непонятно, но чтобы определить, установлена ​​ли переменная в оболочке, вы можете использовать test -z ${variable+x} (x не имеет значения, это может быть любая строка). Для ассоциативного массива в Bash вы можете сделать то же самое; используйте test -z ${map[key]+x}. - person Brian Campbell; 06.02.2014

Еще один способ без использования 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 /]]. или что-то еще.

person Bubnoff    schedule 14.12.2010
comment
Этот метод хорош, когда у вас действительно нет Bash 4. Но я думаю, что строка, которая извлекает ЗНАЧЕНИЕ, будет безопаснее таким образом: ЗНАЧЕНИЕ = $ {животное # *:}. При наличии только одного символа # сопоставление остановится на первом:. Это позволяет значениям также содержать:. - person Ced-le-pingouin; 14.11.2012
comment
@ Ced-le-pingouin ~ Отличный момент! Я этого не уловил. Я отредактировал свой пост, чтобы отразить предложенные вами улучшения. - person Bubnoff; 15.11.2012
comment
Это довольно хакерская эмуляция ассоциативных массивов с использованием подстановки параметров BASH. Ключ param-sub заменяет все перед двоеточием, а шаблон значения заменяет все после двоеточия. Подобно совпадению с подстановочными знаками регулярного выражения. Итак, НЕ настоящий ассоциативный массив. Не рекомендуется, если вам не нужен простой для понимания способ реализации функций, подобных хеш-функциям / ассоциативным массивам, в BASH 3 или ниже. Но это работает! Подробнее здесь: tldp.org/LDP/abs/html/parameter-substitution .html # PSOREX2 - person Bubnoff; 27.01.2014
comment
При этом не реализуется ассоциативный массив, потому что он не позволяет искать элемент по ключу. Он предоставляет только способ найти каждый ключ (и значение) по числовому индексу. (Элемент можно найти по ключу путем итерации по массиву, но это не то, что требуется для ассоциативного массива.) - person Eric Postpischil; 22.12.2018
comment
@EricPostpischil Верно. Это всего лишь взлом. Это позволяет человеку использовать знакомый синтаксис в настройке, но по-прежнему требует итерации по массиву, как вы говорите. В своем предыдущем комментарии я попытался прояснить, что это определенно не ассоциативный массив, и я даже не рекомендую его, если у вас есть альтернативы. На мой взгляд, единственный аргумент в его пользу состоит в том, что его легко писать и использовать для тех, кто знаком с другими языками, такими как Python. Если вы находитесь в точке, где вы действительно хотите реализовать ассоциативные массивы в BASH 3, вам может потребоваться немного повторить свои шаги. - person Bubnoff; 26.12.2018

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

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

На самом деле, все, что вам нужно для создания ассоциативного массива при программировании оболочки, - это временный каталог. 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 ": СП: "). Тот факт, что он использует файловую систему, не замедляет его; эти файлы никогда не гарантированно будут записаны на диск, если вы не вызовете 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

    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

person Brian Campbell    schedule 27.03.2009
comment
Я не думаю, что вам следует использовать файловую систему для карт, которая в основном использует ввод-вывод для чего-то, что вы можете сделать довольно быстро в памяти. - person Irfan Zulfiqar; 02.04.2009
comment
Файлы не обязательно когда-либо будут записаны на диск; если вы не вызовете синхронизацию, операционная система может просто оставить их в памяти. Ваш код обращается к sed и выполняет несколько линейных поисков, которые очень медленные. Я провел несколько быстрых тестов, и моя версия в 5-35 раз быстрее. - person Brian Campbell; 02.04.2009
comment
с другой стороны, собственные массивы bash4 - это значительно лучший подход, и в bash3 вы по-прежнему можете хранить все на диске без разветвления, используя объявление и косвенное обращение. - person lhunath; 12.08.2010
comment
fast и shell в любом случае на самом деле не идут вместе: уж точно не для тех проблем со скоростью, о которых мы говорим на уровне избегания минимального ввода-вывода. Вы можете искать и использовать / dev / shm, чтобы гарантировать отсутствие ввода-вывода. - person jmtd; 01.04.2011
comment
Это решение меня поразило, и оно просто потрясающее. По-прежнему верно в 2016 году. Это действительно должен быть принятый ответ. - person Gordon; 29.02.2016
comment
Я получаю basename: invalid option - b для первой строки с префиксом. как я могу это исправить? - person Bren; 02.05.2017
comment
@Bren Это произойдет, если вы запустите это непосредственно в оболочке, а не из сценария, потому что в этом случае $0 будет равно -bash, и похоже, что basename интерпретирует это как флаги, а не как аргумент для генерации базового имени от. Одно из исправлений - просто жестко запрограммировать prefix. Это не используется ни для чего очень важного, а просто добавляет строку к имени временных каталогов, поэтому, если вы видите их в /tmp, вы можете сказать, откуда они. Ваша реализация basename может поддерживать использование -- для отделения флагов от аргументов: basename -- "$0". - person Brian Campbell; 02.05.2017
comment
Я искал быстрый подход к этой проблеме, а это злой гений! Большое спасибо Брайану из 2009 года :) - person Michael P; 22.04.2020

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

Подробный и подробный ответ с примером кода см. На странице https://stackoverflow.com/questions/3467959

person lhunath    schedule 12.08.2010

####################################################################
# 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
person Vadim    schedule 28.06.2011

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

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

Карта - это не что иное, как бесконечная строка, в которой 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

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

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

    mapName=$1; 

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

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

Еще один способ, отличный от 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 вывод, но это не проблема, если вы не пурист, который избегает разветвления подоболочки.

person Walter Tross    schedule 05.12.2019

Для 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))]}
person Lloeki    schedule 03.03.2014

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

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

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!"
person Bruno Negrão Zica    schedule 03.06.2016

Добавление другого варианта, если доступен 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 '"' 
person critium    schedule 13.07.2018

Я обнаружил, что, как уже упоминалось, самый эффективный метод - это записать 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 ().

person Al P.    schedule 09.02.2010

Как жаль, что не увидел раньше вопроса - я написал библиотеку 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")"
person Beggy    schedule 01.03.2011
comment
Эти ссылки мертвы. - person Stuart R. Jefferys; 18.12.2020

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

person Beggy    schedule 13.06.2013
comment
Может, стоит выложить на гитхаб - person Mark K Cowan; 21.08.2013

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

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

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

person coanor    schedule 05.08.2015

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

####################################################################
# 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, чтобы он не возвращал ошибки, если вы запрашиваете ключ, которого не существует, хотя побочным эффектом является то, что он также будет молча игнорировать отсутствующие карты, но это лучше подходит для моего варианта использования, поскольку я просто хотел проверить наличие ключа, чтобы пропускать элементы в цикле.

person Haravikk    schedule 11.04.2013

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