Bash 中的多维数组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11233825/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Multi-dimensional arrays in Bash
提问by scphantm
I am planning a script to manage some pieces of my Linux systems and am at the point of deciding if I want to use bashor python.
我正在计划一个脚本来管理我的 Linux 系统的某些部分,并且正在决定是否要使用bash或python。
I would prefer to do this as a Bash script simply because the commands are easier, but the real deciding factor is configuration. I need to be able to store a multi-dimensional array in the configuration file to tell the script what to do with itself. Storing simple key=value pairs in config files is easy enough with bash, but the only way I can think of to do a multi-dimensional array is a two layer parsing engine, something like
我更喜欢将其作为 Bash 脚本执行,因为命令更简单,但真正的决定因素是配置。我需要能够在配置文件中存储一个多维数组来告诉脚本如何处理它自己。使用 bash 在配置文件中存储简单的键=值对很容易,但我能想到的做多维数组的唯一方法是两层解析引擎,比如
array=&d1|v1;v2;v3&d2|v1;v2;v3
but the marshall/unmarshall code could get to be a bear and its far from user friendly for the next poor sap that has to administer this. If i can't do this easily in bash i will simply write the configs to an xml file and write the script in python.
但是 marshall/unmarshall 代码可能会变得很糟糕,对于下一个必须管理它的可怜的 sap 来说,它远非用户友好。如果我不能在 bash 中轻松做到这一点,我将简单地将配置写入 xml 文件并在 python 中编写脚本。
Is there an easy way to do this in bash?
有没有一种简单的方法可以在 bash 中做到这一点?
thanks everyone.
谢谢大家。
采纳答案by Nahuel Fouilleul
Bash does not support multidimensional arrays, nor hashes, and it seems that you want a hash that values are arrays. This solution is not very beautiful, a solution with an xml file should be better :
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[@]}"
回答by Jahid
Bash doesn't have multi-dimensional array. But you can simulate a somewhat similar effect with associative arrays. The following is an example of associative array pretending to be used as multi-dimensional array:
Bash 没有多维数组。但是您可以使用关联数组模拟一些类似的效果。下面是一个假装用作多维数组的关联数组的例子:
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
If you don't declare the array as associative (with -A
), the above won't work. For example, if you omit the declare -A arr
line, the echo
will print 2 3
instead of 0 1
, because 0,0
, 1,0
and such will be taken as arithmetic expression and evaluated to 0
(the value to the right of the comma operator).
如果您不将数组声明为关联(与-A
),则上述方法将不起作用。例如,如果您省略该declare -A arr
行,echo
将打印2 3
而不是0 1
, 因为0,0
,1,0
并且此类将被视为算术表达式并计算为0
(逗号运算符右侧的值)。
回答by Paul Sheldrake
This is what worked for me.
这对我有用。
# 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
It's based off of this answer here
它基于这里的答案
回答by yaccob
Independent of the shell being used (sh, ksh, bash, ...) the following approach works pretty well for n-dimensional arrays (the sample covers a 2-dimensional array).
独立于所使用的 shell(sh、ksh、bash、...),以下方法对于 n 维数组非常有效(示例涵盖了一个 2 维数组)。
In the sample the line-separator (1st dimension) is the space character. For introducing a field separator (2nd dimension) the standard unix tool tr
is used. Additional separators for additional dimensions can be used in the same way.
在示例中,行分隔符(第一维)是空格字符。为了引入字段分隔符(第二维),使用了标准的 unix 工具tr
。可以以相同的方式使用附加维度的附加分隔符。
Of course the performance of this approach is not very well, but if performance is not a criteria this approach is quite generic and can solve many problems:
当然这种方法的性能不是很好,但是如果性能不是一个标准,这种方法很通用,可以解决很多问题:
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
The output of that sample looks like this:
该示例的输出如下所示:
1.1 1.2 1.3
2.1 2.2
3.1 3.2 3.3 3.4
回答by n00p
After a lot of trial and error i actually find the best, clearest and easiest multidimensional array on bash is to use a regular var. Yep.
经过大量的反复试验,我实际上发现 bash 上最好、最清晰和最简单的多维数组是使用常规 var。是的。
Advantages: You don't have to loop through a big array, you can just echo "$var" and use grep/awk/sed. It's easy and clear and you can have as many columns as you like.
优点:你不必遍历一个大数组,你可以只回显 "$var" 并使用 grep/awk/sed。它简单明了,您可以拥有任意数量的列。
Example:
例子:
$ 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
If you want to find everyone in peru
如果你想找到秘鲁的每个人
$ echo "$var" | grep peru
thomas johnson peru
johnny lipp peru
Only grep(sed) in the third field
仅在第三个字段中使用 grep(sed)
$ echo "$var" | sed -n -E '/(.+) (.+) peru/p'
thomas johnson peru
johnny lipp peru
If you only want x field
如果你只想要 x 字段
$ echo "$var" | awk '{print }'
hansen
johnson
abu
johnny
Everyone in peru that's called thomas and just return his lastname
秘鲁的每个人都叫 thomas 并且只返回他的姓氏
$ echo "$var" |grep peru|grep thomas|awk '{print }'
johnson
Any query you can think of... supereasy.
您能想到的任何查询......超级简单。
To change an item:
要更改项目:
$ var=$(echo "$var"|sed "s/thomas/pete/")
To delete a row that contains "x"
删除包含“x”的行
$ var=$(echo "$var"|sed "/thomas/d")
To change another field in the same row based on a value from another item
根据另一个项目的值更改同一行中的另一个字段
$ var=$(echo "$var"|sed -E "s/(thomas) (.+) (.+)/ test /")
$ echo "$var"
kris hansen oslo
thomas test peru
bibi abu johnsonville
johnny lipp peru
Of course looping works too if you want to do that
当然,如果您想这样做,循环也可以工作
$ for i in "$var"; do echo "$i"; done
kris hansen oslo
thomas jonson peru
bibi abu johnsonville
johnny lipp peru
The only gotcha iv'e found with this is that you mustalways quote the var(in the example; both var and i) or things will look like this
我发现的唯一问题是您必须始终引用 var(在示例中;var 和 i),否则事情将如下所示
$ for i in "$var"; do echo $i; done
kris hansen oslo thomas jonson peru bibi abu johnsonville johnny lipp peru
and someone will undoubtedly say it won't work if you have spaces in your input, however that can be fixed by using another delimeter in your input, eg(using an utf8 char now to emphasize that you can choose something your input won't contain, but you can choose whatever ofc):
如果您的输入中有空格,那么无疑有人会说它不起作用,但是可以通过在输入中使用另一个分隔符来解决这个问题,例如(现在使用 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 }'
field three yes moin
field 3 dsdds aq
$ var=$(echo "$var"|sed -E "s/(field one)?(.+)?(.+)/?test?/")
$ echo "$var"
field one?test?field three yes moin
field 1?field 2?field 3 dsdds aq
If you want to store newlines in your input, you could convert the newline to something else before input and convert it back again on output(or don't use bash...). Enjoy!
如果您想在输入中存储换行符,您可以在输入之前将换行符转换为其他内容,并在输出时再次将其转换回(或不使用 bash...)。享受!
回答by Dustin
If you want to use a bash script and keep it easy to read recommend putting the data in structured JSON, and then use lightweight tool jqin your bash command to iterate through the array. For example with the following dataset:
如果您想使用 bash 脚本并使其易于阅读,建议将数据放入结构化 JSON 中,然后在您的 bash 命令中使用轻量级工具 jq来遍历数组。例如使用以下数据集:
[
{"specialId":"123",
"specialName":"First"},
{"specialId":"456",
"specialName":"Second"},
{"specialId":"789",
"specialName":"Third"}
]
You can iterate through this data with a bash script and jq like this:
您可以使用 bash 脚本和 jq 遍历这些数据,如下所示:
function loopOverArray(){
jq -c '.[]' testing.json | while read i; do
# Do stuff here
echo "$i"
done
}
loopOverArray
Outputs:
输出:
{"specialId":"123","specialName":"First"}
{"specialId":"456","specialName":"Second"}
{"specialId":"789","specialName":"Third"}
回答by Giuseppe
I am posting the following because it is a very simple and clear way to mimic (at least to some extent) the behavior of a two-dimensional array in Bash. It uses a here-file (see the Bash manual) and read
(a Bash builtin command):
我发布以下内容是因为它是一种非常简单明了的方式来模仿(至少在某种程度上)Bash 中二维数组的行为。它使用了一个 here-file(参见 Bash 手册)和read
(一个 Bash 内置命令):
## 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 person # Create an indexed array (necessary for the read command).
while read -ra person; do
firstName=${person[0]}
familyName=${person[1]}
birthYear=${person[2]}
echo "Physicist ${firstName} ${familyName} was born in ${birthYear}"
# Do whatever you need with data
done < physicists.$$
## Remove the temporary file
rm physicists.$$
Output:
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
输出:
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
The way it works:
它的工作方式:
- The lines in the temporary file created play the role of one-dimensional vectors, where the blank spaces (or whatever separation character you choose; see the description of the
read
command in the Bash manual) separate the elements of these vectors. - Then, using the
read
command with its-a
option, we loop over each line of the file (until we reach end of file). For each line, we can assign the desired fields (= words) to an array, which we declared just before the loop. The-r
option to theread
command prevents backslashes from acting as escape characters, in case we typed backslashes in the here-documentphysicists.$$
.
- 创建的临时文件中的行扮演一维向量的角色,其中空格(或您选择的任何分隔符;请参阅
read
Bash 手册中的命令描述)分隔这些向量的元素。 - 然后,使用
read
带有-a
选项的命令,我们遍历文件的每一行(直到到达文件末尾)。对于每一行,我们可以将所需的字段(= 单词)分配给我们在循环之前声明的数组。该命令的-r
选项read
可防止反斜杠充当转义字符,以防我们在 here-document 中键入反斜杠physicists.$$
。
In conclusion a file is created as a 2D-array, and its elements are extracted using a loop over each line, and using the ability of the read
command to assign words to the elements of an (indexed) array.
总之,一个文件被创建为一个 2D 数组,它的元素使用每行的循环提取,并使用read
命令的能力将单词分配给(索引)数组的元素。
Slight improvement:
小幅改进:
In the above code, the file physicists.$$
is given as input to the while
loop, so that it is in fact passed to the read
command. However, I found that this causes problems when I have another command asking for input inside the while
loop. For example, the select
command waits for standard input, and if placed inside the while
loop, it will take input from physicists.$$
, instead of prompting in the command-line for user input.
To correct this, I use the -u
option of read
, which allows to read from a file descriptor. We only have to create a file descriptor (with the exec
command) corresponding to physicists.$$
and to give it to the -u
option of read, as in the following code:
在上面的代码中,文件physicists.$$
作为while
循环的输入给出,因此它实际上被传递给read
命令。但是,我发现当我有另一个命令要求while
循环内的输入时,这会导致问题。例如,该select
命令等待标准输入,如果放置在while
循环内,它将从 获取输入physicists.$$
,而不是在命令行中提示用户输入。为了纠正这个问题,我使用了-u
选项read
,它允许从文件描述符中读取。我们只需要创建一个文件描述符(用exec
命令)对应physicists.$$
并赋予它-u
读取的选项,如下代码所示:
## 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.
exec {id_nuclei}<./physicists.$$ # Create a file descriptor stored in 'id_nuclei'.
## Extract the needed data
declare -a person # Create an indexed array (necessary for the read command).
while read -ra person -u "${id_nuclei}"; do
firstName=${person[0]}
familyName=${person[1]}
birthYear=${person[2]}
echo "Physicist ${firstName} ${familyName} was born in ${birthYear}"
# Do whatever you need with data
done
## Close the file descriptor
exec {id_nuclei}<&-
## Remove the temporary file
rm physicists.$$
Notice that the file descriptor is closed at the end.
请注意,文件描述符在最后关闭。
回答by Vigintas Labakojis
Expanding on Paul's answer - here's my version of working with associative sub-arrays in bash:
扩展保罗的答案 - 这是我在 bash 中使用关联子数组的版本:
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
It works with mixed values in the main array - strings/arrays/assoc. arrays
它适用于主数组中的混合值 - 字符串/数组/关联。数组
The key here is to wrap the subarrays in single quotes and use *
instead of @
when storing a subarray inside the main array so it would get stored as a single, space separated string: "${SUB_1[*]}"
这里的关键是将子数组用单引号括起来,并在将子数组存储在主数组*
中@
时使用而不是使用它,这样它就会被存储为单个空格分隔的字符串:"${SUB_1[*]}"
Then it makes it easy to parse an array out of that when looping through values with IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]}
然后它可以很容易地在循环遍历值时从中解析出一个数组 IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]}
The code above outputs:
上面的代码输出:
COUNT: 4
VALUE: name1val name2val
SUBVALUE: name1val
SUBVALUE: name2val
VALUE: name4val name3val
SUBVALUE: name4val
SUBVALUE: name3val
VALUE: string1val
VALUE: string2val
回答by rocksteady
I do this using associative arrayssince bash 4and setting IFS
to a value that can be defined manually.
我使用自bash 4以来的关联数组并设置为可以手动定义的值来执行此操作。IFS
The purpose of this approach is to have arrays as values of associative array keys.
这种方法的目的是将数组作为关联数组键的值。
In order to set IFS back to default just unset it.
为了将 IFS 设置回默认值,只需取消设置即可。
unset IFS
unset IFS
This is an example:
这是一个例子:
#!/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
The output of the script below is:
下面脚本的输出是:
default IFS
x3:x4:x5
x3:x4:x5
IFS=:
x3
x4
x5
x3
x4
x5
or
1
x3
x4
x5
2
x3
x4
x5
回答by testiner
I've got a pretty simple yet smart workaround: Just define the array with variables in its name. For example:
我有一个非常简单但聪明的解决方法:只需在名称中定义带有变量的数组。例如:
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
Don't know whether this helps since it's not exactly what you asked for, but it works for me. (The same could be achieved just with variables without the array)
不知道这是否有帮助,因为它不完全符合您的要求,但对我有用。(同样可以用没有数组的变量来实现)