BASH关联数组打印


17

有没有办法在不循环所有元素的情况下打印整个数组([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%% )*}"

有没有更干净的方法来获取/打印输出的键=值部分?

Answers:


15

我想您在问两个不同的问题。

有没有一种方法可以使bash在不循环的情况下打印此信息?

是的,但是它们不如仅使用循环好。

有没有更干净的方法来获取/打印输出的键=值部分?

是的,for循环。它的优点是不需要外部程序,很简单,并且很容易控制确切的输出格式而不会出现意外。


任何尝试处理declare -ptypeset -p)输出的解决方案都必须处理以下问题:a)变量本身包含括号或括号的可能性,b)declare -p必须加上引号以使其成为外壳程序的有效输入。

例如,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本身以外的内容,则仍然需要您自己报价,但至少您需要处理原始数据。(如果键或值中包含换行符,则可能需要使用一些引号。)

使用当前的Bash(我认为是4.4),您也可以使用printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"代替printf "%q=%q"。它产生了一种更好的引用格式,但是要记住要写的东西当然要多一些。(并且引用了@作为数组键的特殊情况,而%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

请注意,printf ...%q...如果数组具有@键,则您的变量的输出不适合重新输入到Shell,因为%q不引用它,并且a=([@]=value)是中的语法错误bash
斯特凡Chazelas

@StéphaneChazelas,显然。"${x@Q}"也引用它,因为它引用所有字符串(看起来更好)。添加了有关使用的说明。
ilkkachu

是的,从mksh复制。另一个形状不同的运算符,无法与大多数其他运算符组合。再次,查看zsh它的可变扩展标志(它又早于bash几十年,您可以使用它选择引用样式:$ {(q)var},$ {(qq)var} ...)以获得更好的设计。bash与mksh有相同的问题,因为它不引用空字符串(这里不是问题,因为bash不支持空键)。此外,使用比单引号其他(引用样式时${var@Q}诉诸$'...'于一些值),它的代码是重新输入在同一个语言是很重要的。
斯特凡Chazelas

@StéphaneChazelas,我认为您的意思是未设置的值,而不是空字符串?(x=; echo "${x@Q}"确实给出了''unset x; echo "${x@Q}"什么也没给出。)Bash @Q似乎更喜欢$'\n'文字换行符,这在某些情况下实际上可能很好(但是我无法说出其他人喜欢什么)。当然有了选择不会有什么不好的。
ilkkachu

哦,对不起,我还没有意识到。这与mksh有所不同。该$'...'语法是事物的潜在问题像LC_ALL=zh_HK.big5hkscs bash -c 'a=$'\''\n\u3b1'\''; printf "%s\n" "${a@Q}"'其输出$'\n<0xa3><0x5c>'0x5c单独的反斜线所以如果报价是在不同的语言环境来解释,你就会有一个问题。
斯特凡Chazelas

9
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循环。


虽然看起来很聪明,但两种方法的效率都低于a for。真可惜。
桂桂聪(SatōKatsura)

@SatoKatsura我同意,但是如果速度较慢,语法使用pr的时间会更短...我不确定pr即使使用大数组,语法也不会保持较慢!
F.豪里

2
@MiniMax,因为它不能产生正确的结果(相同的元素,错误的顺序)。您需要压缩数组${!array[@]}${array[@]}然后才能使其工作。
桂聪聪(SatōKatsura)

1
这最后片段与pastefor写在一条线上的问题循环for i in "${!array[@]}"; do echo "$i=${array[$i]}" ; done,但需要两个子shell和外部程序。整洁如何?pr如果有很多元素,则的解决方案也会中断,因为它试图对输出进行分页。| pr -2t -l"${#array[@]}"与简单循环相比,您需要使用类似这样的东西开始变得难以记住,并且比它更长。
ilkkachu

1
bashcmd1 | cmd2装置2个叉,即使CMD1或CMD2或两者都是内置。
斯特凡Chazelas

2

如果您正在寻找具有更好关联数组支持的shell,请尝试zsh

在中zsh(在1998年添加了关联数组,与之相比,对于ksh93添加了关联数组,对于bash则添加了2009年),$var或者${(v)var}扩展为哈希的(非空)${(k)var}(非顺序)键(按相同顺序),以及${(kv)var}键和值。

要保留空值,就像数组一样,您需要引用并使用该@标志。

因此,要打印键和值,只需

printf '%s => %s\n' "${(@kv)var}"

尽管要考虑可能为空的哈希,但您应该执行以下操作:

(($#var)) &&  printf '%s => %s\n' "${(@kv)var}"

另请注意,zsh使用的数组定义语法比ksh93的更为明智和有用(由复制bash):

typeset -A var
var=(k1 v1 k2 v2 '' empty '*' star)

这使得复制或合并关联数组变得容易得多:

var2=("${(@kv)var1}")
var3+=("${(@kv)var2}")
var4=("${@kv)var4}" "${(@kv)var5}")

(您不能在没有循环的情况下轻松复制哈希bash,请注意,bash当前不支持空键或NUL字节的键/值)。

另请参阅zsh数组压缩功能,这些功能通常需要与关联数组一起使用:

keys=($(<keys.txt)) values=($(<values.txt))
hash=(${keys:^values})

1

由于排版是您想要的,所以为什么不编辑其输出呢?

typeset -p array | sed s/^.*\(// | tr -d ")\'\""  | tr "[" "\n" | sed s/]=/' = '/

a2 = 2  
a1 = 1  
b1 = bbb 

哪里

array='([a2]="2" [a1]="1" [b1]="bbb" )'

详细,但是很容易看到格式是如何工作的:只需逐步使用更多sedtr命令来执行管道。修改它们以适合漂亮的印刷品味。


当管道的某些键或值包含您要替换的任何字符(例如括号,方括号或引号)时,这种管道注定会失败。seds和tr'的流水线甚至比for使用printf。循环更简单。
ilkkachu

另外,您确实知道tr按字符翻译,它不匹配字符串吗?不论位置如何,都tr "]=" " ="将“]”更改为空格,将an =更改为=。因此,您可能只需将所有三个组合tr为一个即可。
ilkkachu

关于某些非字母数字字符,这非常正确。但是任何有对付他们得到幅度更复杂和更可读左右的订单,除非有一个真正好的理由,让他们在您的数据,并且在我想他们就过滤掉之前,我们来到这里的问题说明。应该始终有明确的警告。我发现这些管道比诸如printf glob更为简单(例如,用于调试目的),而printf glob可以完美工作或在您的脸上炸毁。在这里,您可以对每个元素进行一个简单的更改,对其进行测试,然后再添加1个。
纳德瑞克'17

我的错!我的_tr_s和_sed_s完全混在一起了!已在最新编辑中修复。
纳德瑞克'17

1

另一种选择是列出所需的所有变量和grep。

set | grep -e '^aa='

我用它来调试。我怀疑它是否具有出色的性能,因为它列出了所有变量。

如果您经常这样做,则可以使其具有以下功能:

aap() { set | grep -e "^$1="; }

不幸的是,当我们使用时间检查性能时:

$ time aap aa aa=([0]="abc") . real 0m0.014s user 0m0.003s sys 0m0.006s

因此,如果您经常这样做,则需要@ F.Hauri的NO FORKS版本,因为它快得多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.