【问题标题】:How do I populate a bash associative array with command output?如何使用命令输出填充 bash 关联数组?
【发布时间】:2016-09-09 18:34:31
【问题描述】:

我正在尝试使用命令的输出填充关联数组。我可以在没有命令的情况下做到这一点:

$ declare -A x=( [first]=foo [second]=bar )
$ echo "${x[first]}, ${x[second]}"
foo, bar

我可以用命令输出填充一个非关联数组:

$ declare y=( $(echo 'foo bar') )
$ echo "${y[0]}, ${y[1]}"
foo, bar

但是当我尝试在上述两者的基础上创建一个语句来从命令填充关联数组时,我收到以下错误消息:

$ declare -A z=( $(echo '[first]=foo [second]=bar') )
-bash: z: $(echo '[first]=foo [second]=bar'): must use subscript when assigning associative array

为什么我会收到该错误消息,使用命令输出填充关联数组的正确语法是什么?我试图避免将eval 用于the usual reasons,不想使用临时文件,当然echo 只是用作产生问题效果的命令示例,真正的命令将更复杂。

所以,根据下面的几个答案,看起来问题只是我的引用:

$ declare -A z="( $(echo '[first]=foo [second]=bar') )"
$ echo "${z[first]}, ${z[second]}"
foo, bar

并且在索引和值中有空格:

$ declare -A z="( $(echo '[first field]="foo with space" [second]="space bar"') )"
$ echo "${z[first field]}, ${z[second]}"
foo with space, space bar

编辑以回答 cmets 中关于为什么需要引号的问题 (How do I populate a bash associative array with command output?) - 我不完全知道,但也许其他人可以使用此脚本的结果作为参考来解释(不期望指定的索引要在索引数组中使用,它们只是作为数组值填充的字符串的一部分):

$ cat tst.sh
#!/bin/env bash

set -x

printf 'Indexed, no quotes\n'
declare -a w=( $(echo '[first]=foo [second]=bar') )
declare -p w

printf '\n---\n'

printf 'Indexed, with quotes\n'
declare -a x="( $(echo '[first]=foo [second]=bar') )"
declare -p x

printf '\n---\n'

printf 'Associative, no quotes\n'
declare -A y="( $(echo '[first]=foo [second]=bar') )"
declare -p y

printf '\n---\n'

printf 'Associative, with quotes\n'
declare -A z=( $(echo '[first]=foo [second]=bar') )
declare -p z

.

$ ./tst.sh
+ printf 'Indexed, no quotes\n'
Indexed, no quotes
+ w=($(echo '[first]=foo [second]=bar'))
++ echo '[first]=foo [second]=bar'
+ declare -a w
+ declare -p w
declare -a w=([0]="[first]=foo" [1]="[second]=bar")
+ printf '\n---\n'

---
+ printf 'Indexed, with quotes\n'
Indexed, with quotes
++ echo '[first]=foo [second]=bar'
+ declare -a 'x=( [first]=foo [second]=bar )'
+ declare -p x
declare -a x=([0]="bar")
+ printf '\n---\n'

---
+ printf 'Associative, no quotes\n'
Associative, no quotes
++ echo '[first]=foo [second]=bar'
+ declare -A 'y=( [first]=foo [second]=bar )'
+ declare -p y
declare -A y=([second]="bar" [first]="foo" )
+ printf '\n---\n'

---
+ printf 'Associative, with quotes\n'
Associative, with quotes
+ z=($(echo '[first]=foo [second]=bar'))
./tst.sh: line 24: z: $(echo '[first]=foo [second]=bar'): must use subscript when assigning associative array
+ declare -A z
+ declare -p z
declare -A z=()

【问题讨论】:

    标签: bash


    【解决方案1】:

    这是一种传统的 while 循环方法,用于从命令的输出中填充关联数组:

    while IFS= read -r; do
       declare -A z+="( $REPLY )"
    done < <(printf '[first]=foo [second]=bar\n[third]=baz\n')
    
    # check output
    $> echo "${z[first]}, ${z[second]}, ${z[third]}"
    foo, bar, baz
    
    # or declare -p
    $> declare -p z
    declare -A z='([third]="baz" [second]="bar" [first]="foo" )'
    

    编辑:您最初的尝试也适用于正确的引号:

    $> unset z
    
    $> declare -A z="( $(echo '[first]=foo [second]=bar') )"
    
    $> declare -p z
    declare -A z='([second]="bar" [first]="foo" )'
    

    【讨论】:

    • 这可行。我们需要向它添加一个IFS=,我们不能在整个事情周围使用$'...',或者它将\t 转换为一个制表符等,但我们可能会得到文字换行符甚至NUL 字符(并使用 NUL 标志读取)在每个数组元素之间。也得考虑一下,谢谢!
    • 曾经有人告诉我,如果我们使用内部$REPLY 变量,则不需要IFS=,但我猜使用它没有害处。 (已编辑)
    • 嗯,换行符实际上并没有在分隔值方面产生影响。当值只是用空格分隔时,脚本可以工作,printf 只需要一个换行符。
    • 是的,但我的意思是你不需要它甚至循环,它的工作原理是一样的:declare -A z+="( $(printf '[first]=foo [second]=bar [third]=baz\n') )" 并产生所需的输出:echo "${z[first]}, ${z[second]}, ${z[third]}" 输出foo, bar, baz。那么这与我最初的尝试有什么不同呢?嗯....
    • 是的,我刚刚在my question 的末尾发布了它。不敢相信我不引用什么东西就被咬了!现在需要扩展它以获得处理索引和/或值中的空白字符的正确语法。我会在今天晚些时候接受这个答案,除非有人提出一个很好的理由而不是更好的选择。谢谢。
    【解决方案2】:

    我想这有点脆弱,但您可以将整个 z=(...) 赋值作为命令替换的结果。

    declare -A "$(echo z="($(echo '[first]=foo [second]=bar'))")"
    

    【讨论】:

    • 有趣。我没有想到这一点。可能不想使用echo z=...,但printf 'z="%s" ....' 的一些变体可能很健壮。得考虑一下!谢谢。
    • 正确的结尾应该是)")" 而不是"))",但这还不够编辑:)
    • 我猜因为没有未引用的( 与未引用的) 配对,所以不需要引用。无论如何都已修复,因为我确信其他一些输入可能会将其从工作变为不工作。
    • 看起来我只需要引用我的原始作业,请参阅stackoverflow.com/a/39418782/1745001 的底部。有点尴尬...再次感谢。
    • 嗯。在此处建议的嵌套怪物之前,我真的没有尝试过吗?
    【解决方案3】:

    鉴于这是可行的:

    declare -A z=([first]=$(echo 'foo') [second]=$(echo 'bar'))
    

    我猜 Bash 需要在进行任何替换之前查看关联数组初始化列表。所以我没有办法避免eval

    eval "declare -A z=($(echo '[first]=foo [second]=bar'))"
    

    避免eval 的“通常原因”是什么?

    【讨论】:

    • Google shell eval evileval 周围的问题进行了许多讨论。感谢您的回复,仍然希望有其他选择。
    • 看起来我只需要引用我的原始作业,请参阅stackoverflow.com/a/39418782/1745001 的底部。有点尴尬...再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-07
    • 2016-04-04
    • 1970-01-01
    • 2016-12-13
    • 1970-01-01
    • 1970-01-01
    • 2013-11-26
    相关资源
    最近更新 更多