有没有办法打印整个数组([key]=value)而不循环遍历所有元素?
假设我创建了一个包含一些元素的数组:
declare -A array
array=([a1]=1 [a2]=2 ... [b1]=bbb ... [f500]=abcdef)
我可以打印回整个数组
for i in "${!array[@]}"
do
echo "${i}=${array[$i]}"
done
然而,bash 似乎已经知道如何一次性获取所有数组元素——键${!array[@]}
和值${array[@]}
。
有没有办法让 bash 在没有循环的情况下打印这些信息?
编辑:
typeset -p array
这样做!
但是我无法在一次替换中同时删除前缀和后缀:
a="$(typeset -p array)"
b="${a##*(}"
c="${b%% )*}"
有没有更干净的方法来仅获取/打印输出的 key=value 部分?
答案1
我认为你在那里问两件不同的事情。
有没有办法让 bash 在没有循环的情况下打印这些信息?
是的,但它们不如仅使用循环好。
有没有更干净的方法来仅获取/打印输出的 key=value 部分?
是的,for
循环。它的优点是不需要外部程序,简单,并且可以很容易地控制精确的输出格式而不会出现意外。
任何尝试处理declare -p
( typeset -p
) 输出的解决方案都必须处理 a) 变量本身包含圆括号或方括号的可能性,b)declare -p
必须添加的引用以使其输出成为 shell 的有效输入。
例如,b="${a##*(}"
如果任何键/值包含左括号,则您的扩展会吃掉一些值。这是因为您使用了##
,它删除了最长字首。对于 也一样c="${b%% )*}"
。尽管您当然可以declare
更准确地匹配打印的样板,但如果您不想要它所引用的所有内容,您仍然会遇到困难。
除非你需要它,否则这看起来不太好。
$ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
$ declare -p array
declare -A array='([def]="\"foo bar\"" [abc]="'\''foobar'\''" )'
使用for
循环,可以更轻松地根据需要选择输出格式:
# without quoting
$ for x in "${!array[@]}"; do printf "[%s]=%s\n" "$x" "${array[$x]}" ; done
[def]="foo bar"
[abc]='foobar'
# with quoting
$ for x in "${!array[@]}"; do printf "[%q]=%q\n" "$x" "${array[$x]}" ; done
[def]=\"foo\ bar\"
[abc]=\'foobar\'
从那里开始,也是简单的否则更改输出格式(删除键周围的括号,将所有键/值对放在一行上...)。如果您需要引用 shell 本身以外的其他内容,您仍然需要自己完成,但至少您拥有可以处理的原始数据。 (如果键或值中有换行符,则可能需要一些引用。)
printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"
对于当前的 Bash(我认为是 4.4),您也可以使用printf "%q=%q"
.它产生了一种更好的引用格式,但当然需要记住编写更多的工作。 (它引用了as 数组键的极端情况,但它不引用。)@
%q
如果 for 循环看起来太累而无法编写,请将其保存为一个函数(此处不引用):
printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ; }
然后使用它:
$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)
也适用于索引数组:
$ b=(abba acdc)
$ printarr b
0=abba
1=acdc
答案2
declare -p array
declare -A array='([a2]="2" [a1]="1" [zz]="Hello World" [b1]="bbb" [f50]="abcd" )'
2个叉子
也许这个:
printf "%s\n" "${!array[@]}"
a2
a1
f50
zz
b1
printf "%s\n" "${array[@]}"
2
1
abcd
Hello World
bbb
printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t
a2 2
a1 1
f50 abcd
zz Hello World
b1 bbb
3 个叉子
或这个:
paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}")
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb
没有叉子
来比较
for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done
a2=2
a1=1
f50=abcd
zz=Hello World
b1=bbb
执行时间比较
由于最后的语法不使用 fork,它们可能会更快:
time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
5 11 76
real 0m0.005s
user 0m0.000s
sys 0m0.000s
time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
5 6 41
real 0m0.008s
user 0m0.000s
sys 0m0.000s
time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
5 6 41
real 0m0.002s
user 0m0.000s
sys 0m0.001s
但如果数组变大,这个断言就不再成立。如果减少分叉对于小型流程来说是有效的,那么使用专用工具对于较大的流程来说会更有效。
for i in {a..z}{a..z}{a..z};do array[$i]=$RANDOM;done
time printf "%s\n" "${!array[@]}" "${array[@]}" | pr -2t | wc
17581 35163 292941
real 0m0.150s
user 0m0.124s
sys 0m0.036s
time paste -d= <(printf "%s\n" "${!array[@]}") <(printf "%s\n" "${array[@]}") | wc
17581 17582 169875
real 0m0.140s
user 0m0.000s
sys 0m0.004s
time for i in "${!array[@]}";do printf "%s=%s\n" "$i" "${array[$i]}";done | wc
17581 17582 169875
real 0m0.312s
user 0m0.268s
sys 0m0.076s
评论
既 (分叉的)解决方案使用结盟,如果任何变量包含新队。在这种情况下,唯一的方法就是for
循环。
StackOverflow 上有更可靠、更详细的答案
答案3
K
Bash 5.1 允许通过使用如下所示的值以非常直接的方式显示关联数组${arr[@]@K}
:
$ declare -A arr
$ arr=(k1 v1 k2 v2)
$ printf "%s\n" "${arr[@]@K}"
k1 "v1" k2 "v2"
呵呵。新的“K”参数转换将关联数组显示为键值对。
中对此有很好的解释Bash 参考手册 → 3.5.3 Shell 参数扩展:
${parameter@operator}
K
生成参数值的可能引用版本,不同之处在于它将索引数组和关联数组的值打印为引用键值对的序列(请参阅数组)。
答案4
自从排版做你想要的为什么不直接编辑它的输出呢?
typeset -p array | sed s/^.*\(// | tr -d ")\'\"" | tr "[" "\n" | sed s/]=/' = '/
给出
a2 = 2
a1 = 1
b1 = bbb
在哪里
array='([a2]="2" [a1]="1" [b1]="bbb" )'
冗长,但很容易看出格式化是如何工作的:只需使用逐渐更多的内容执行管道即可sed和t命令。修改它们以适应漂亮的打印品味。