62

我正在计划一个脚本来管理我的一些 Linux 系统,并且正在决定是否要使用

我更愿意将其作为 Bash 脚本执行,因为命令更简单,但真正的决定因素是配置。我需要能够在配置文件中存储一个多维数组来告诉脚本如何处理自己。使用 bash 在配置文件中存储简单的 key=value 对很容易,但我能想到的唯一方法是做一个多维数组是一个两层解析引擎,比如

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

但是 marshall/unmarshall 代码可能会变成熊,对于下一个必须管理它的可怜的 sap 来说,它远非用户友好。如果我不能在 bash 中轻松做到这一点,我只需将配置写入 xml 文件并在 python 中编写脚本。

有没有一种简单的方法可以在 bash 中做到这一点?

感谢大家。

4

13 回答 13

56

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[@]}"

编辑:这个答案很老,因为 bash 4 支持哈希表,另请参阅这个答案以获得没有 eval 的解决方案。

于 2012-06-27T20:13:37.773 回答
29

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

如果您不将数组声明为关联数组(使用-A),则上述方法将不起作用。例如,如果您省略该declare -A arr行,echo将打印2 3而不是0 1, 因为0,0,1,0并且这样将被视为算术表达式并计算为0(逗号运算符右侧的值)。

于 2015-07-17T05:27:46.077 回答
21

这要归功于 1. “间接扩展” !它增加了一层间接性,以及 2.“子字符串扩展”,它与数组的行为不同,可用于“切片”它们,如https://stackoverflow.com/a/1336245/317623所述

# Define each array and then add it to the main one
SUB_0=("name0" "value 0")
SUB_1=("name1" "value;1")
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

它基于这里的答案

于 2015-02-03T20:32:39.437 回答
10

如果您想使用 bash 脚本并使其易于阅读,建议将数据放在结构化 JSON 中,然后在 bash 命令中使用轻量级工具 jq来遍历数组。例如,使用以下数据集:

[

    {"specialId":"123",
    "specialName":"First"},

    {"specialId":"456",
    "specialName":"Second"},

    {"specialId":"789",
    "specialName":"Third"}
]

您可以使用 bash 脚本和 jq 遍历这些数据,如下所示:

function loopOverArray(){

    jq -c '.[]' testing.json | while read i; do
        # Do stuff here
        echo "$i"
    done
}

loopOverArray

输出:

{"specialId":"123","specialName":"First"}
{"specialId":"456","specialName":"Second"}
{"specialId":"789","specialName":"Third"}
于 2019-06-15T22:28:47.247 回答
8

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).

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.

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 
于 2013-10-05T00:42:45.917 回答
5

经过大量的反复试验,我实际上发现 bash 上最好、最清晰和最简单的多维数组是使用常规 var。是的。

优点:您不必循环遍历一个大数组,您只需 echo "$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(sed)

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

如果你只想要 x 字段

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

秘鲁每个叫 thomas 的人都返回他的姓氏

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

您能想到的任何查询...超级简单。

要更改项目:

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

删除包含“x”的行

$ 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

发现的唯一问题是您必须始终引用 var(在示例中;var 和 i),否则事情将如下所示

$ 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 ...)。享受!

于 2017-04-25T12:42:31.870 回答
4

我发布以下内容是因为它是一种非常简单明了的方式来模拟(至少在某种程度上)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.$$

输出: 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

它的工作方式:

  • 创建的临时文件中的行扮演一维向量的角色,其中空格(或您选择的任何分隔字符;请参阅readBash 手册中的命令描述)分隔这些向量的元素。
  • 然后,使用read带有-a选项的命令,我们遍历文件的每一行(直到我们到达文件末尾)。对于每一行,我们可以将所需的字段(= 单词)分配给我们在循环之前声明的数组。该命令的-r选项read可防止反斜杠充当转义字符,以防我们在 here-document 中键入反斜杠physicists.$$

总之,一个文件被创建为一个二维数组,它的元素是使用每行循环提取的,并使用read命令的能力将单词分配给(索引)数组的元素。

轻微改进:

在上面的代码中,文件physicists.$$作为循环的输入给出while,因此它实际上是传递给read命令的。while但是,我发现当我在循环中有另一个命令要求输入时,这会导致问题。例如,select命令等待标准输入,如果放在while循环中,它将从 中获取输入physicists.$$,而不是在命令行中提示用户输入。为了纠正这个问题,我使用了允许从文件描述符中读取的-u选项。read我们只需要创建一个对应的文件描述符(使用exec命令)physicists.$$并将其提供给-uread 选项,如下面的代码所示:

## 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_file}<./physicists.$$     # Create a file descriptor stored in 'id_file'.

## Extract the needed data
declare -a person     # Create an indexed array (necessary for the read command).                                                                                 
while read -ra person -u "${id_file}"; 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_file}<&-
## Remove the temporary file
rm physicists.$$

请注意,文件描述符在最后关闭。

于 2017-09-27T12:19:36.467 回答
2

扩展保罗的答案 - 这是我在 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

它适用于主数组中的混合值 - 字符串/数组/关联。数组

这里的关键是将子数组用单引号括起来,*而不是@在将子数组存储在主数组中时使用,以便将其存储为单个空格分隔的字符串:"${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
于 2015-02-12T10:45:44.410 回答
2

在这里找到了很多在 bash 中创建多维数组的答案。

无一例外,都是钝的,难用的。

如果 MD 数组是必需的标准,那么是时候做出决定了:

使用支持 MD 数组的语言

我的首选是 Perl。大多数人可能会选择 Python。要么工作。

将数据存储在别处

JSON 和 jq 已经被推荐了。还建议使用 XML,但对于您的使用 JSON 和 jq 可能会更简单。

尽管 Bash 似乎不是您需要做的事情的最佳选择。

有时正确的问题不是“我如何在工具 Y 中做 X?”,而是“哪种工具最适合做 X?”

于 2019-09-03T20:45:14.533 回答
1

我从bash 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
于 2017-06-30T18:00:56.687 回答
1

Bash 不支持多维数组,但是我们可以使用 Associate 数组来实现。这里的索引是检索值的关键。关联数组在bash版本 4 中可用。

#!/bin/bash

declare -A arr2d
rows=3
columns=2

for ((i=0;i<rows;i++)) do
    for ((j=0;j<columns;j++)) do
        arr2d[$i,$j]=$i
    done
done


for ((i=0;i<rows;i++)) do
    for ((j=0;j<columns;j++)) do
        echo ${arr2d[$i,$j]}
    done
done
于 2018-04-23T07:54:32.753 回答
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

不知道这是否有帮助,因为它不完全符合您的要求,但它对我有用。(同样可以使用没有数组的变量来实现)

于 2018-01-18T17:14:42.357 回答
-3
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
于 2014-01-19T16:44:50.810 回答