PAYATHON 2026

Bash 函数为什么只收到两个关联数组键?如何正确传递全部键?

数据阿明

第二个参数在两次调用中的含义不一致:函数需要数组变量名,而 "${!associative_array[@]}" 会把键直接展开为多个参数。第一个键被当作变量名,随后又被 shift 2 移除,因此最终命令中只出现了另外两个键。

可以保留现有函数接口:先将关联数组的键存入普通索引数组,再把该数组的变量名传给函数。

为什么直接展开键会少一个

函数开头约定了两个固定参数:

local -r enable_logging="$1"
local -n parameter_names="$2"
shift 2

local -n 创建变量引用,要求 $2 是被引用变量的名称;它不会把后面的多个参数收集成数组。

原调用:

run_aws_ssm_delete_parameters true "${!associative_array[@]}" --color on

会将每个键展开成独立参数。假设这次键的展开顺序是 two three one,实际传参相当于:

run_aws_ssm_delete_parameters true two three one --color on

函数随后会:

  1. 将 true 作为日志开关。
  2. 让 parameter_names 引用名为 two 的变量。
  3. 用 shift 2 移除 true 和 two。
  4. 通过 aws_cli_flags+=("$@"),将剩余的 three one --color on 追加到命令参数中。

如果变量 two 未定义,"${parameter_names[@]}" 就不会提供数组元素。输出中的 three one 来自剩余的位置参数,并非正确引用后的数组展开结果。

关联数组的遍历顺序没有固定保证,不能依赖某个键总是先出现。如果第一个键不是合法的变量引用,或者恰好对应已有变量,还可能得到其他结果。

保留现有函数:先把键放入索引数组

将 main 改为下面这样,函数中的 "${parameter_names[@]}" 可以保持原样:

function main() {
    local -a normal_array=("one" "two" "three")
    run_aws_ssm_delete_parameters true normal_array --color on

    local -A associative_array=(
        ["one"]="value_one"
        ["two"]="value_two"
        ["three"]="value_three"
    )

    local -a associative_keys=("${!associative_array[@]}")
    run_aws_ssm_delete_parameters true associative_keys --color on
}

main "$@"

两次调用的第二个参数现在都是数组变量名。函数通过 nameref 读取索引数组中的元素,shift 2 后剩下的都是附加参数。

保留 "${!associative_array[@]}" 外面的双引号,可以确保每个键都作为单独且完整的数组元素传递。

为什么传入关联数组名后得到的是值

下面的调用正确传递了变量名:

run_aws_ssm_delete_parameters true associative_array --color on

但函数使用的是:

"${parameter_names[@]}"

对于关联数组,这个表达式展开的是值。要读取键,应使用:

"${!parameter_names[@]}"

如果函数专门接收关联数组,可以把构造命令参数的部分改为:

local -a aws_cli_flags=(
    "delete-parameters"
    "--names"
    "${!parameter_names[@]}"
)

然后直接传入 associative_array 即可。

这个修改也会影响普通索引数组:对索引数组使用 "${!parameter_names[@]}",得到的是 0 1 2 这样的下标。如果需要同时支持题目中的两种调用,先生成 associative_keys 更符合现有函数的用途。

为什么错误输出是 Bad: 0

原代码的错误分支是:

else
set +x
printf "Bad: %s" "$?"
return 1
fi

这里的 $? 读取的是刚执行完的 set +x 的状态,AWS 命令的失败状态已经被覆盖。应在进入错误分支后立即保存状态,再关闭跟踪:

local result status

set -x
if result="$(aws ssm "${aws_cli_flags[@]}")"; then
    set +x
    [[ "${enable_logging}" = true ]] && printf "Good: %s\n" "${result}"
    return 0
else
    status=$?
    set +x
    printf "Bad: %s\n" "$status" >&2
    return "$status"
fi

这样可以保留 AWS CLI 的退出状态。凭证错误仍需单独解决;参数跟踪只能说明命令收到了哪些参数,不能说明删除操作成功。

确认脚本由 Bash 执行

这段代码使用 Bash 的关联数组和 local -n,需要支持这些特性的 Bash;local -n 要求 Bash 4.3 或更高版本。

终端的交互 shell 是 zsh,并不意味着脚本也由 zsh 执行。使用 bash script.sh,或直接执行带有 #!/usr/bin/env bash 的可执行脚本,会按相应方式启动 Bash。zsh script.sh 则会使用 zsh,不能按这里的 Bash 语义分析。

可以检查实际使用的 Bash 版本:

bash --version

备注:内容仅供参考。