2012-06-27 3 views
32

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

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

array=&d1|v1;v2;v3&d2|v1;v2;v3 

но маршалом/код unmarshall может стать медведем и его далеким от удобства для следующего бедного сока, который должен управлять этим. Если я не могу сделать это легко в bash, я просто напишу configs в xml-файл и напишу скрипт в python.

Есть ли простой способ сделать это в bash?

благодарит всех.

+0

Связано: [Как объявить 2D-массив в bash] (http://stackoverflow.com/q/16487258/55075) – kenorb

ответ

29

Bash не поддерживает многомерные массивы, а также хеширует, и кажется, что вы хотите, чтобы хэш представлял собой массивы. Это решение не очень красиво, решение с файлом XML должен быть лучше:

array=('d1=(v1 v2 v3)' 'd2=(v1 v2 v3)') 
for elt in "${array[@]}";do eval $elt;done 
echo "d1 ${#d1[@]} ${d1[@]}" 
echo "d2 ${#d2[@]} ${d2[@]}" 
+0

Это ответ, который я ожидал, но ему никогда не хотелось спрашивать. Спасибо – scphantm

+16

Просто заметьте. bash поддерживает хеши (ассоциативные массивы), начиная с версии 4. Дополнительная информация: http://mywiki.wooledge.org/BashFAQ/006 – poncha

+0

Ассоциативные массивы FYI могут иметь некоторые из атрибутов 'declare', установленных на них, например 'set в верхнем регистре при присваивании ", однако они не могут установить для них -A [ассоциативный] или -a [числовой] массив или набор -n-ссылок, но вы МОЖЕТЕ пометить свое имя переменной номером и использовать переменную в место: MYARR_ $ i _ [$ j] будет самым близким к этому, однако это не истинный массив md, но это лучшее, что вы собираетесь получить. Вы также могли бы использовать функции как систему псевдо-массива, если бы вы были достаточно отчаянными :) – osirisgothra

8

Независимо от оболочки используется (ш, КШ, баш, ...) следующий подход работает довольно хорошо для n- (образец охватывает двумерный массив).

В образце разделитель строк (1-е измерение) является символом пробела. Для введения разделителя полей (2-й размер) используется стандартный инструмент unix tr. Аналогичным образом можно использовать дополнительные разделители для дополнительных габаритов.

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

array2d="1.1:1.2:1.3 2.1:2.2 3.1:3.2:3.3:3.4" 

function process2ndDimension { 
    for dimension2 in $* 
    do 
     echo -n $dimension2 " " 
    done 
    echo 
} 

function process1stDimension { 
    for dimension1 in $array2d 
    do 
     process2ndDimension `echo $dimension1 | tr : " "` 
    done 
} 

process1stDimension 

Выход этой выборки выглядит следующим образом:

1.1  1.2  1.3  
2.1  2.2  
3.1  3.2  3.3  3.4 
+0

Не работает очень хорошо, если однако значения содержат пробелы или двоеточия. :) – dannysauer

+1

@dannysauer - должно быть хорошо, если вы кодируете пробелы и двоеточия как сущность или шестнадцатеричный код. % 20 и% 3а, например. Должно быть достаточно легко обрабатывать ввод и вывод из массива через такие функции, если это вас беспокоит. – ghoti

+0

Итак, значения не содержат пробелов или двоеточий. : D – dannysauer

-2
echo "Enter no of terms" 
read count 
for i in $(seq 1 $count) 
do 
    t=` expr $i - 1 ` 
    for j in $(seq $t -1 0) 
    do 
    echo -n " " 
    done 
    j=` expr $count + 1 ` 
    x=` expr $j - $i ` 
    for k in $(seq 1 $x) 
    do 
    echo -n "* " 
    done 
    echo "" 
done 
+1

Не забудьте добавить несколько объясняющих слов о том, что должен делать код? Хотя некоторые форматирования также полезны, некоторые комментарии улучшат ответ еще больше. Хотя я вижу несколько массивов, я не вижу здесь многомерного массива. – Izzy

7

Это то, что работает для меня.

# Define each array and then add it to the main one 
SUB_0=("name0" "value0") 
SUB_1=("name1" "value1") 
MAIN_ARRAY=(
    SUB_0[@] 
    SUB_1[@] 
) 

# Loop and print it. Using offset and length to extract values 
COUNT=${#MAIN_ARRAY[@]} 
for ((i=0; i<$COUNT; i++)) 
do 
    NAME=${!MAIN_ARRAY[i]:0:1} 
    VALUE=${!MAIN_ARRAY[i]:1:1} 
    echo "NAME ${NAME}" 
    echo "VALUE ${VALUE}" 
done 

Он основан офф this answer here

+0

это должно быть 'NAME = $ {! MAIN_ARRAY [i]: 0: 1}' и 'VALUE = $ {! MAIN_ARRAY [i]: 1: 1}', если я не ошибаюсь, чтобы это работало (т.е. обмен 'URLS' для' MAIN'). – Christian

2

расширяющегося на ответ Павла - вот моя версия работает с ассоциативными подмассивами в Баше:

declare -A SUB_1=(["name1key"]="name1val" ["name2key"]="name2val") 
declare -A SUB_2=(["name3key"]="name3val" ["name4key"]="name4val") 
STRING_1="string1val" 
STRING_2="string2val" 
MAIN_ARRAY=(
    "${SUB_1[*]}" 
    "${SUB_2[*]}" 
    "${STRING_1}" 
    "${STRING_2}" 
) 
echo "COUNT: " ${#MAIN_ARRAY[@]} 
for key in ${!MAIN_ARRAY[@]}; do 
    IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]} 
    echo "VALUE: " ${val[@]} 
    if [[ ${#val[@]} -gt 1 ]]; then 
     for subkey in ${!val[@]}; do 
      subval=${val[$subkey]} 
      echo "SUBVALUE: " ${subval} 
     done 
    fi 
done 

Он работает со смешанными значениями в основном массиве - строки/массивы/ассоциативные.Массивы

Ключевым моментом здесь является, чтобы обернуть подмассива в одинарные кавычки и использовать * вместо @ при хранении подмассив внутри основного массива, так что бы откладываются в виде единого пространства, разделенных строки: "${SUB_1[*]}"

Тогда позволяет легко разобрать массив из этого, когда цикл по значениям с IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]}

код, указанный выше выходов:

COUNT: 4 
VALUE: name1val name2val 
SUBVALUE: name1val 
SUBVALUE: name2val 
VALUE: name4val name3val 
SUBVALUE: name4val 
SUBVALUE: name3val 
VALUE: string1val 
VALUE: string2val 
+0

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

+0

Да, я только что понял, что он сломает строки с пробелами в отдельные подначисления, но я лично могу жить с этим, если это просто значения конфигурации ... Но я думаю, что это самое лучшее, что мы можем сделать в bash , Однако я открыт для предложений/улучшений. Добавлено цитаты, спасибо за указание :) –

+0

Привет, вы можете объяснить, как 'IFS = '' читать -a val <<< $ {MAIN_ARRAY [$ key]}' работать. Что такое IFS? Определяется ли здесь переменная? Почему чтение добавляется к определению переменной? – Boyang

5

Баш г oesn't имеют многомерный массив. Но вы можете имитировать несколько схожий эффект с ассоциативными массивами. Ниже приведен пример ассоциативного массива притворяясь быть использованы в качестве многомерного массива:

declare -A arr 
arr[0,0]=0 
arr[0,1]=1 
arr[1,0]=2 
arr[1,1]=3 
echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1 

Если вы не объявляете массив как ассоциативный (с -A), выше, не будет работать. Например, если опустить declare -A arr линии, echo будет печатать 2 3 вместо 0 1, потому что 0,0, 1,0 и такое будет рассматриваться как арифметическое выражение и оценивается в 0 (значение справа от оператора запятой).

+1

declare -A [arrayname] выдает «недопустимую опцию», по крайней мере, в OS X. – ktappe

+5

EDIT: Кажется, OS X поставляется с bash 3, и эта опция требует bash 4. Спасибо, Apple. – ktappe

2

После большого количества проб и ошибок я нахожу самый лучший, самый чистый и простой многомерный массив в bash, чтобы использовать обычный var. Ага.

Преимущества: вам не нужно проходить через большой массив, вы можете просто эхо «$ var» и использовать grep/awk/sed. Это легко и понятно, и вы можете иметь столько столбцов, сколько хотите.

Пример:

$ var=$(echo -e 'kris hansen oslo\nthomas jonson peru\nbibi abu johnsonville\njohnny lipp peru') 

$ echo "$var" 
kris hansen oslo 
thomas johnson peru 
bibi abu johnsonville 
johnny lipp peru 

Если вы хотите, чтобы найти все перу

$ echo "$var" | grep peru 
thomas johnson peru 
johnny lipp peru 

Только Grep (СЭД) в третьем поле

$ echo "$var" | sed -n -E '/(.+) (.+) peru/p' 
thomas johnson peru 
johnny lipp peru 

Если вы хотите только й поле

$ echo "$var" | awk '{print $2}' 
hansen 
johnson 
abu 
johnny 

Все в перу, что называется ТОМАС и просто вернуть его ЬавЬЫате

$ echo "$var" |grep peru|grep thomas|awk '{print $2}' 
johnson 

Любой вопрос вы можете думать ... SuperEasy.

Чтобы изменить позицию:

$ var=$(echo "$var"|sed "s/thomas/pete/") 

Чтобы удалить строку, которая содержит «х»

$ var=$(echo "$var"|sed "/thomas/d") 

Для изменения другого поля в той же строке на основе значения из другого пункта

$ var=$(echo "$var"|sed -E "s/(thomas) (.+) (.+)/\1 test \3/") 
$ echo "$var" 
kris hansen oslo                                    
thomas test peru                                   
bibi abu johnsonville 
johnny lipp peru 

Конечно, петлевые работы тоже, если вы хотите это сделать

$ for i in "$var"; do echo "$i"; done 
kris hansen oslo 
thomas jonson peru 
bibi abu johnsonville 
johnny lipp peru 

Единственный глюк iv'e нашел с этим, что вы обязательно всегда процитировать вар (в примере; как вар и я) или вещи будут выглядеть следующим образом

$ for i in "$var"; do echo $i; done 
kris hansen oslo thomas jonson peru bibi abu johnsonville johnny lipp peru 

и кто-то, несомненно, будет сказать, что это не будет работать, если у вас есть пробелы в вашем входе, однако, что может быть исправлено с помощью другой разделители в вашем входе, например, (с использованием utf8 полукокса Теперь, чтобы подчеркнуть, что вы можете выбрать что-то ваш вход не будет содержать, но вы можете выбрать любой OFC):

$ var=$(echo -e 'field one☥field two hello☥field three yes moin\nfield 1☥field 2☥field 3 dsdds aq') 

$ for i in "$var"; do echo "$i"; done 
field one☥field two hello☥field three yes moin 
field 1☥field 2☥field 3 dsdds aq 

$ echo "$var" | awk -F '☥' '{print $3}' 
field three yes moin 
field 3 dsdds aq 

$ var=$(echo "$var"|sed -E "s/(field one)☥(.+)☥(.+)/\1☥test☥\3/") 
$ echo "$var" 
field one☥test☥field three yes moin 
field 1☥field 2☥field 3 dsdds aq 

Если вы хотите сохранить переводы строк в вашем входе, можно преобразовать строку к чему-то еще до ввода и конвертировать его обратно на выходе (или не использовать bash ...). Наслаждайтесь!

+0

Это может работать в двух измерениях, но для общего случая с n измерениями это становится тяжелым. –

+0

Это правда. Когда я написал это, я не понимал разницы между 2d, 3d, 4d и всем, что мне когда-либо понадобилось, была хорошая табличная поддержка/db. Теперь мне все еще не нужно ничего больше, чем 2d, но если вам нужно больше, я полагаю, что это не сработает. – n00p

0

я сделать это с помощью ассоциативных массивов так Баш 4 и установка IFS к значению, которое может быть определено вручную.

Целью этого подхода является наличие массивов в качестве значений ассоциативных ключей массива.

Чтобы установить IFS обратно по умолчанию, просто отключите его.

  • unset IFS

Это пример:

#!/bin/bash 

set -euo pipefail 

# used as value in asscciative array 
test=(
    "x3:x4:x5" 
) 
# associative array 
declare -A wow=(
    ["1"]=$test 
    ["2"]=$test 
) 
echo "default IFS" 
for w in ${wow[@]}; do 
    echo " $w" 
done 

IFS=: 
echo "IFS=:" 
for w in ${wow[@]}; do 
    for t in $w; do 
    echo " $t" 
    done 
done 
echo -e "\n or\n" 
for w in ${!wow[@]} 
do 
    echo " $w" 
    for t in ${wow[$w]} 
    do 
    echo " $t" 
    done 
done 

unset IFS 
unset w 
unset t 
unset wow 
unset test 

Выход ниже сценария:

default IFS 
    x3:x4:x5 
    x3:x4:x5 
IFS=: 
    x3 
    x4 
    x5 
    x3 
    x4 
    x5 

or 

    1 
    x3 
    x4 
    x5 
    2 
    x3 
    x4 
    x5 
+0

Я не вижу причины использовать 'test = (" x3: x4: x5 ")', когда вы ссылаетесь на него как '$ test': вы можете просто выполнить' test = "x3: x4: x5" '. –

+0

Да, вы абсолютно правы. Спасибо за подсказку. – rocksteady

1

Я отправляю следующее потому, что он я s - очень простой и понятный способ имитировать (по крайней мере, до некоторой степени) поведение двумерного массива в Bash.Он использует здесь-файл (см инструкцию Баш) и read (Баш встроенные команды):

## Store the "two-dimensional data" in a file ($$ is just the process ID of the shell, to make sure the filename is unique) 
cat > physicists.$$ <<EOF 
Wolfgang Pauli 1900 
Werner Heisenberg 1901 
Albert Einstein 1879 
Niels Bohr 1885 
EOF 
nbPhysicists=$(wc -l physicists.$$ | cut -sf 1 -d ' ')  # Number of lines of the here-file specifying the physicists. 

## Extract the needed data 
declare -a people  # Create an indexed array (necessary for the read command).                     
while read -ra people; do 
    firstName=${people[0]} 
    familyName=${people[1]} 
    birthYear=${people[2]} 
    echo "Physicist ${firstName} ${familyName} was born in ${birthYear}" 
    # Do whatever you need with data 
done < physicists.$$ 

## Remove the temporary file 
rm physicists.$$ 

Выход: Physicist Wolfgang Pauli was born in 1900 Physicist Werner Heisenberg was born in 1901 Physicist Albert Einstein was born in 1879 Physicist Niels Bohr was born in 1885

Как это работает:

  • линии во временном файле создают роль одномерных векторов, где пробелы (или любой другой символ разделения вы выбираете, см. описание команды read в руководстве Bash) sepa Оценить элементы этих векторов.
  • Затем, используя команду read с ее опцией -a, мы перебираем каждую строку файла (пока не достигнем конца файла). Для каждой строки мы можем назначить нужные поля (= слова) массиву, который мы объявили непосредственно перед циклом. Опция -r команды read предотвращает выполнение обратных косых черт как escape-символы, если мы набрали обратную косую черту в документе здесь physicists.$$.

В заключении файл создаются как 2D-массив, а его элементы извлекаются с помощью цикла по каждой линии, и используя способность команды read назначать слова к элементам (индексируется) массив ,

+0

Это очень похоже на решение user7909577. Однако то, что вы делаете, это написать последовательность строк в текстовом файле. Таким образом, размер 1 - это номер строки. Размер 2 - это слово в строке. Истинное общее решение, однако, позволило бы поместить любой массив в любой массив. И это не сработает. Хорошо, вы могли бы реализовать измерения, вставляя строки, но это все еще не очень элегантно. –

+0

@U. Windi "помещает любой массив в любой массив, и это не сработает". Вот почему я подчеркиваю в своем ответе, что он должен имитировать 2D-ARRAYS. «Это очень похоже на решение user7909577»: я так не думаю, что он/она разбирает строку с sed/grep/awk в своем ответе, и я делаю это путем «преобразования» строки в массив сначала, затем используя индексы (поэтому имитирует поведение массива). – Giuseppe

+0

@Guiseppe: Но оба решения используют текстовый файл как представление для двумерного массива с размерами, как я описал. В частности, речь шла о многомерных массивах (для двумерных массивов есть отдельный вопрос). –

0

У меня есть довольно простой, но умный обходной путь: Просто определите массив с переменными в его имени. Например:

for ((i=0 ; i<$(($maxvalue + 1)) ; i++)) 
    do 
    for ((j=0 ; j<$(($maxargument + 1)) ; j++)) 
    do 
    declare -a array$i[$j]=((Your rule)) 
    done 
done 

Не знаю, помогает ли это, так как это не совсем то, что вы просили, но это работает для меня. (То же самое можно было бы сделать только с переменными без массива)

Смежные вопросы