【问题标题】:How can I update an associative array inside a function passing it by parameter?如何在通过参数传递它的函数内更新关联数组?
【发布时间】:2021-09-29 22:46:14
【问题描述】:

我有以下代码读取 Json 文件的所有 fields(路径为 PRIVATE_REGISTRATION_FILE 并将它们存储到关联数组 (PRIVATE_FIELDS) 中,稍后我会在我的代码中查询该数组:

declare -A PRIVATE_FIELDS
for PRICING_FIELD in $(jq -c -r '.fields[]' "${PRIVATE_REGISTRATION_FILE}")
do
  FIELD_KEY=$(jq -r '.label' <<< "${PRICING_FIELD}")
  PRIVATE_FIELDS["${FIELD_KEY}"]=${PRICING_FIELD}
done

问题是我用几个文件多次执行此操作,即使逻辑始终相同。

因此,我想将此逻辑提取到一个函数中,但我很难将 map 参数传递给它。

这是我尝试的:

function update_array
{
    FILE_NAME=$1
    eval "declare -A MAP="${2#*=}
    for PRICING_FIELD in $(jq -c -r '.fields[]' "${FILE_NAME}")
    do
        FIELD_KEY=$(jq -r '.label' <<< "${PRICING_FIELD}")
        MAP["${FIELD_KEY}"]=${PRICING_FIELD}
    done
}

我这样称呼:

declare -A PRIVATE_FIELDS
update_array "myFile.json" "$(declare -p PRIVATE_FIELDS)"

但它不起作用,地图仍然是空的。

echo ${PRIVATE_FIELDS["someKey"]}
>>> (empty)

我已经尝试了in this answer 提出的每个解决方案,但都没有奏效。我做错了什么?

Bash 版本:4.2.46(2)-release


补充说明,Json 文件长这样(显然对jq 的调用可能会减少):

{
    "name": "Something",
    "fields": [
        {
            "label": "key1",
            "value": "value1",
            "other": "other1"
        },
        {
            "label": "key2",
            "value": "value2",
            "other": "other2"
        }
    ]
}

【问题讨论】:

  • $1 函数内部是传递给它的第一个参数,在您的情况下为 "$(declare -p PRIVATE_FIELDS)" 但您的代码似乎是指脚本的第一个参数。
  • @LMC 抱歉,我粘贴了错误的示例,我已正确更新了我的问题。感谢您发现
  • 您可以在函数内部使用 PRIVATE_FIELDS,无需创建另一个变量。
  • 顺便说一句,function 关键字是 bash 支持向后兼容 pre-POSIX ksh 的东西。最好只使用 update_array() { 而不使用 function 来编写与其他 POSIX 系列 shell 更兼容的代码;请参阅wiki.bash-hackers.org/scripting/obsolete 中的两个相关条目(第一个讨论function update_array() {,它与ksh 或POSIX 都不兼容;第二个讨论function update_array { legacy-ksh 形式) .
  • @PaulHodges,OP 的 bash 版本太旧,无法支持 namerefs(因此不接受 glenn 的回答,并且正在更新问题以反映他们正在运行的版本)。

标签: bash bash4


【解决方案1】:

当您在函数中使用declare 时,实际上是在将变量设为local。在 bash 提示符下查看 help declare

使用 nameref(需要 bash 版本 4.3+):

function update_array
{
    local FILE_NAME=$1
    local -n MAP=$2     # MAP is now a _reference_ to the caller's variable
    # the rest stays the same
    for PRICING_FIELD in $(jq -c -r '.fields[]' "${FILE_NAME}")
    do
        FIELD_KEY=$(jq -r '.label' <<< "${PRICING_FIELD}")
        MAP["${FIELD_KEY}"]=${PRICING_FIELD}
    done
}

然后你只需传递数组 name

declare -A PRIVATE_FIELDS
update_array "myFile.json" PRIVATE_FIELDS

declare -p PRIVATE_FIELDS

为了更有效地遍历 JSON 文件:

$ jq -c -r '.fields[] | "\(.label)\t\(.)"' file.json
key1    {"label":"key1","value":"value1","other":"other1"}
key2    {"label":"key2","value":"value2","other":"other2"}

这是假设标签不包含任何制表符。


使用它,加上您的旧 bash 版本,您可以做到这一点

假设结果数组将在全局范围内

update_array() {
    local filename=$1 varname=$2
    local -A map
    while IFS=$'\t' read -r label json; do
        map[$label]=$json
    done < <(
        jq -c -r '.fields[] | "\(.label)\t\(.)"' "$filename"
    )
    eval declare -gA "$varname=$(declare -p map | cut -d= -f2-)"
}

你会这样称呼它

$ echo $BASH_VERSION
4.2.45(1)-release

$ update_array tmp/file.json myArray

$ declare -p myArray
declare -A myArray='([key2]="{\"label\":\"key2\",\"value\":\"value2\",\"other\":\"other2\"}" [key1]="{\"label\":\"key1\",\"value\":\"value1\",\"other\":\"other1\"}" )'

$ for label in "${!myArray[@]}"; do
>     printf '"%s" => >>%s<<\n' "$label" "${myArray[$label]}"
> done
"key2" => >>{"label":"key2","value":"value2","other":"other2"}<<
"key1" => >>{"label":"key1","value":"value1","other":"other1"}<<

【讨论】:

  • 显示JSON数据,我们可以将您需要调用jq的次数减​​少到一次。
  • 改掉使用全大写变量名的习惯,让shell保留这些名称。有一天你会写PATH=something 然后wonder why 你的script is broken
  • 感谢您的回答!关于命名,谢谢提示。我是一个非常偶然的 bash 脚本编写者,我真的不知道好的做法:) 关于 Json,我会更新我的问题来展示它。然后我会尝试您的解决方案并让您知道它是否有效,再次感谢!
  • 我看到了你的 bash 版本,我会尽快更新我的答案
  • 非常感谢完美工作的解决方案以及不错的提示,我的脚本现在看起来更漂亮了:)
猜你喜欢
  • 2020-03-21
  • 1970-01-01
  • 2021-10-18
  • 2011-11-29
  • 1970-01-01
  • 2017-02-09
  • 2017-12-01
  • 2018-08-26
  • 1970-01-01
相关资源
最近更新 更多