38

在彻底搜索了一种在 bash 中创建关联数组的方法之后,我发现这declare -A array可以解决问题。但问题是,它只适用于 bash 版本 4,而我们系统中服务器的 bash 版本是 3.2.16。

如何在 bash 3 中实现某种类似关联数组的 hack?这些值将传递给脚本,例如

ARG=array[key];

./script.sh ${ARG}

编辑:我知道我可以在 awk 或其他工具中执行此操作,但我要解决的场景需要严格的 bash。

4

5 回答 5

34

Bash 3 没有关联数组,因此您将不得不使用其他一些语言功能来实现您的目的。请注意,即使在 bash 4 下,您编写的代码也不会像您声称的那样做:./script.sh ${ARG}不会将关联数组传递给子脚本,因为当关联数组${ARG}时扩展为空。ARG您不能将关联数组传递给子进程,无论如何您都需要对其进行编码。

您需要在父脚本和子脚本之间定义一些参数传递协议。一种常见的方法是以 形式传递参数key=value。这假设字符=没有出现在键中。

您还需要弄清楚如何在父脚本和子脚本中表示关联数组。它们不需要使用相同的表示。

表示关联数组的常用方法是为每个元素使用单独的变量,并使用通用的命名前缀。这要求密钥名称仅由 ASCII 字母(任何一种情况)、数字和下划线组成。例如,代替${myarray[key]},写${myarray__key}。如果密钥是在运行时确定的,则需要先进行一轮扩展:而不是${myarray[$key]},写

n=myarray__${key}; echo ${!n}

对于作业,使用printf -v. 请注意使用指定值的%s格式。printf不要写printf -v "myarray__${key}" %s "$value",因为这会被$value视为一种格式并对其执行 printf%扩展。

printf -v "myarray__${key}" %s "$value"

如果您需要将这样表示的关联数组传递给具有key=value参数表示的子进程,您可以使用它${!myarray__*}来枚举名称以 . 开头的所有变量myarray__

args=()
for k in ${!myarray__*}; do
  n=$k
  args+=("$k=${!n}")
done

在子进程中,将表单的参数转换为key=value带有前缀的分隔变量:

for x; do
  if [[ $x != *=* ]]; then echo 1>&2 "KEY=VALUE expected, but got $x"; exit 120; fi
  printf -v "myarray__${x%%=*}" %s "${x#*=}"
done

顺便问一下,你确定这是你需要的吗?与其从另一个 bash 脚本调用 bash 脚本,不如在子 shell 中运行子脚本。这样它将继承父级的所有变量。

于 2012-08-02T11:55:50.750 回答
11

这是关于 bash 3 和更早版本中使用参数扩展的关联数组的另一篇文章/解释:
https ://stackoverflow.com/a/4444841

Gilles 的方法有一个很好的if声明来捕捉分隔符问题,清理奇怪的输入......等等。用那个。

如果您对参数扩展有点熟悉:
http ://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html

要在您的场景中使用 [如上所述:发送到脚本]:脚本 1: sending_array.sh

# A pretend Python dictionary with bash 3 
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

bash ./receive_arr.sh "${ARRAY[@]}"

脚本 2:receive_arr.sh

argAry1=("$@")

function process_arr () {
    declare -a hash=("${!1}")
    for animal in "${hash[@]}"; do
        echo "Key: ${animal%%:*}"
        echo "Value: ${animal#*:}"
    done
}

process_arr argAry1[@]

exit 0

方法 2,采购第二个脚本: 脚本 1: sending_array.sh

source ./receive_arr.sh
# A pretend Python dictionary with bash 3 
ARRAY=( "cow:moo"
        "dinosaur:roar"
        "bird:chirp"
        "bash:rock" )

process_arr ARRAY[@]

脚本 2:receive_arr.sh

function process_arr () {
    declare -a hash=("${!1}")
    for animal in "${hash[@]}"; do
        echo "Key: ${animal%%:*}"
        echo "Value: ${animal#*:}"
    done
}

参考:
在 bash 中将数组作为参数传递

于 2013-02-23T02:48:43.960 回答
6

如果您不想处理大量变量,或者键只是无效的变量标识符,并且您的数组保证少于 256 项,则可以滥用函数返回值。该解决方案不需要任何子shell,因为该值很容易作为变量获得,也不需要任何迭代,因此性能会尖叫。它的可读性也很强,几乎就像 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[$?]}

此答案中的更多详细信息和变体

于 2014-03-03T16:06:46.847 回答
2

事实证明这非常容易。我必须将使用一堆关联数组的 ba​​sh 4 脚本转换为 bash 3。这两个辅助函数完成了所有工作:

array_exp() {
    exp=${@//[/__}
    eval "${exp//]}"
}

array_clear() {
    unset $(array_exp "echo \${!$1__*}")
}

我很惊讶这确实有效,但这就是 bash 的美妙之处。例如

((all[ping_lo] += counts[ping_lo]))

变成

array_exp '((all[ping_lo] += counts[ping_lo]))'

或者这个打印声明:

printf "%3d" ${counts[ping_lo]} >> $return

变成

array_exp 'printf "%3d" ${counts[ping_lo]}' >> $return

唯一改变的语法是清除。这个:

counts=()

变成

array_clear counts

你已经准备好了。您可以轻松地告诉 array_exp 识别像 "=()" 这样的表达式,并通过将它们重写为 array_clear 表达式来处理它们,但我更喜欢上述两个函数的简单性。

于 2015-04-01T03:24:27.717 回答
2

您可以将键值对写入文件,然后按键 grep。如果您使用类似的模式

key=value

那么你可以egrep为此^key=非常安全。

要“覆盖”一个值,只需将新值附加到文件末尾并用于tail -1获取最后一个结果egrep

或者,您可以将此信息放入普通数组中,使用key=value作为数组的值,然后对数组进行迭代以查找该值。

于 2012-08-02T11:45:26.310 回答