在 bash 3 中创建关联数组
Posted
技术标签:
【中文标题】在 bash 3 中创建关联数组【英文标题】:Create associative array in bash 3 【发布时间】:2012-07-31 08:46:35 【问题描述】:在彻底寻找在 bash 中创建关联数组的方法后,我发现 declare -A array
可以解决问题。但问题是,它只适用于 bash 版本 4,而我们系统中服务器的 bash 版本是 3.2.16。
如何在 bash 3 中实现某种类似关联数组的 hack?这些值将被传递给像
这样的脚本ARG=array[key];
./script.sh $ARG
编辑:我知道我可以在 awk 或其他工具中执行此操作,但我要解决的场景需要严格的 bash。
【问题讨论】:
由于 bash 3 具有普通数组,因此您只需要基于它们实现关联数组即可。但说真的,为什么有必要这样做? 请看BashFAQ/006。 【参考方案1】:事实证明这非常容易。我不得不将使用一堆关联数组的 bash 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 表达式来处理它们,但我更喜欢上面两个函数的简单性。
【讨论】:
这看起来很酷。我想知道如何处理以下语句:let 'map[$i]++',尤其是单引号。想法/cmets? 在您上面的回答中,当我运行脚本时,array_exp() 中的 eval 语句失败;它在抱怨“糟糕的替代品”【参考方案2】:Bash 3 没有关联数组,因此您将不得不使用其他一些语言功能来实现您的目的。请注意,即使在 bash 4 下,您编写的代码也不会像您声称的那样做:./script.sh $ARG
不会将关联数组传递给子脚本,因为当 ARG
是关联数组时,$ARG
会扩展为空.您不能将关联数组传递给子进程,无论如何都需要对其进行编码。
您需要在父脚本和子脚本之间定义一些参数传递协议。一种常见的方法是以key=value
的形式传递参数。这假定字符 =
没有出现在键中。
您还需要弄清楚如何在父脚本和子脚本中表示关联数组。它们不需要使用相同的表示。
表示关联数组的常用方法是为每个元素使用单独的变量,并使用通用的命名前缀。这要求密钥名称仅由 ASCII 字母(任何一种情况)、数字和下划线组成。例如,不要写$myarray[key]
,而是写$myarray__key
。如果在运行时确定了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 中运行子脚本。这样它就会继承父级的所有变量。
【讨论】:
您可以使用printf -v
而不是declare
来避免在函数中本地化变量。获取子脚本而不是调用它也可能是使父数组和其他变量对子脚本可用的一种方式。
我不明白你为什么在for k in ``$!myarray__*``; do
中加了退格键,这不是命令。
@Jocelyndelalande 退格??哦,我明白了,反引号。看起来像复制粘贴,我已经修好了。
如何直接回显 myarray__$key 而不是将其分配给变量?
@rane 我不确定你的意思。你想避免临时变量n
吗?我不认为你可以。【参考方案3】:
如果您不想处理大量变量,或者键只是无效的变量标识符,并且您的数组保证少于 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[$?]
this answer 中的更多详细信息和变体
【讨论】:
【参考方案4】:这是关于 bash 3 和更早版本中使用参数扩展的关联数组的另一篇文章/说明:https://***.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
参考:Passing arrays as parameters in bash
【讨论】:
【参考方案5】:您可以将键值对写入文件,然后通过键 grep。如果你使用像
这样的模式key=value
那么您可以将egrep
换成^key=
,这样就非常安全了。
要“覆盖”一个值,只需将新值附加到文件末尾并使用tail -1
即可获得egrep
的最后一个结果
或者,您可以将此信息放入普通数组中,使用key=value
作为数组的值,然后对数组进行迭代以查找该值。
【讨论】:
以上是关于在 bash 3 中创建关联数组的主要内容,如果未能解决你的问题,请参考以下文章