Bash:如何使用包含方括号或其他特殊字符的键来持久化和恢复关联数组
Bash: How to persist and restore associative arrays with keys that contain square brackets or other special characters
问题
为键包含方括号的有效 Bash 关联数组获取 declare -p
的结果会导致 错误的数组下标 错误。
测试程序
做:
$ declare -A array
$ key='var[0]'
$ array["$key"]=37
$ echo ${array["$key"]}
37
$ declare -p array > def.sh
$ cat def.sh
declare -A array='(["var[0]"]="37" )'
$ . def.sh
bash: [var[0]]=37: bad array subscript
在上面的代码中,注意:
- 我可以指定包含方括号的键:
var[0]
- setter 和 getter 引用了键
- 我可以用这个键做作业
- 我可以使用这个键从关联数组中获取值
- 使用
declare -p
我可以将此定义保存到文件中:def.sh
- 获取文件
def.sh
时出现错误。
我的环境
- 我使用的 Bash 版本是 4.2.46(1)-release (x86_64-redhat-linux-gnu).
- 我在 CentOS 7.3.1611 (Core) 服务器
解决方法
如果我不做 declare -p array > def.sh
,而是做:
{
echo 'declare -A array'
for Key in "${!array[@]}"; do
EscapedKey="$(sed 's|"|\"|g' <<<"$Key")"
echo "array[\"$EscapedKey\"]=${array["$Key"]}"
done
} > def.sh
然后获取 def.sh 文件就可以了。请注意,在上面的示例中,我还转义了可能是密钥一部分的引号字符。我确实明白我上面的内容并不详尽。由于这些复杂性,如果可能的话,我更喜欢不涉及此类解决方法的解决方案。
问题
是否有一些 shopt
、set -o <option>
或我可以做的其他事情来使我能够将其键可能包含方括号或其他特殊字符的关联数组持久保存到文件中以及以后能够成功获取该文件吗?我需要一个适用于上述环境的解决方案。
这是一个错误
这是 bash 4.2
中的错误。它已在 4.3
.
中修复
我通过从 http://ftp.gnu.org/gnu/bash/ 编译 bash 4.2
、4.2.53
和 4.3
来测试它,并重复上述步骤。 4.3
表现得像 4.4
- 没有这样的问题。然而,在 bash 4.3
中,declare
将打印
declare -A array='(["var[0]"]="37" )'
就像4.2
一样。 4.4
不在右侧添加引号,而是打印:
declare -A array=(["var[0]"]="37" )
这使得没有与测试显示的不同。
complete_fullquote
中有一个可能相关的选项,但它是在 4.4
中添加的,因此不能用作解决方法。
似乎除了使用版本 >=4.3
之外,还需要解决这个问题,而您使用的版本是最直接的方法。
解决方法
如果你想避免 sed
调用,还有一个替代方案(使用 bash 4.2
测试):
function array2file {
# local variable for the keys
declare -a keys
# check if the array exists, to protect against injection
# by passing a crafted string
declare -p "" >/dev/null || return 1;
printf "declare -A %s\n" ""
# create a string with all the keys so we can iterate
# because we can't use eval at for's declaration
# we do it this way to allow for spaces in the keys, since that's valid
eval "keys=(\"${![@]}\")"
for k in "${keys[@]}"
do
printf "%s[\"${k//\"/\\\"}\"]=" ""
# the extra quoting here protects against spaces
# within the element's value - injection doesn't work here
# but we still need to make sure there's consistency
eval "printf \"\\"%s\\"\n\" \"${[\"${k//\"/\\"}\"]}\""
done
}
这将正确地在键周围添加引号,并转义键本身内的所有双引号。您可以将其放置在您来源的文件中。然后使用:
array2file array > ./def.sh
其中 array
是您选择的任何数组名称。通过重定向输出,您将获得正确引用的键,您可以像以前一样定义关联数组,然后将其传递给此函数进行存储。
加分
如果您将 for
循环中提供给第一个 printf
的变量从 </code> 更改为 <code>${2:-}
,并在 printf
处执行相同操作在顶部,然后您可以选择创建一个新数组的定义,并将第二个参数作为其名称,允许重命名。只有当您提供 2 个字符串而不是一个(当然是引用)时,才会发生这种情况。该设置允许轻松完成此操作,因此我将其添加到此处。
这可以让您解决使用预定义函数难以与现有代码交互的情况。
问题
为键包含方括号的有效 Bash 关联数组获取 declare -p
的结果会导致 错误的数组下标 错误。
测试程序
做:
$ declare -A array
$ key='var[0]'
$ array["$key"]=37
$ echo ${array["$key"]}
37
$ declare -p array > def.sh
$ cat def.sh
declare -A array='(["var[0]"]="37" )'
$ . def.sh
bash: [var[0]]=37: bad array subscript
在上面的代码中,注意:
- 我可以指定包含方括号的键:
var[0]
- setter 和 getter 引用了键
- 我可以用这个键做作业
- 我可以使用这个键从关联数组中获取值
- 使用
declare -p
我可以将此定义保存到文件中:def.sh
- 获取文件
def.sh
时出现错误。
我的环境
- 我使用的 Bash 版本是 4.2.46(1)-release (x86_64-redhat-linux-gnu).
- 我在 CentOS 7.3.1611 (Core) 服务器
解决方法
如果我不做 declare -p array > def.sh
,而是做:
{
echo 'declare -A array'
for Key in "${!array[@]}"; do
EscapedKey="$(sed 's|"|\"|g' <<<"$Key")"
echo "array[\"$EscapedKey\"]=${array["$Key"]}"
done
} > def.sh
然后获取 def.sh 文件就可以了。请注意,在上面的示例中,我还转义了可能是密钥一部分的引号字符。我确实明白我上面的内容并不详尽。由于这些复杂性,如果可能的话,我更喜欢不涉及此类解决方法的解决方案。
问题
是否有一些 shopt
、set -o <option>
或我可以做的其他事情来使我能够将其键可能包含方括号或其他特殊字符的关联数组持久保存到文件中以及以后能够成功获取该文件吗?我需要一个适用于上述环境的解决方案。
这是一个错误
这是 bash 4.2
中的错误。它已在 4.3
.
我通过从 http://ftp.gnu.org/gnu/bash/ 编译 bash 4.2
、4.2.53
和 4.3
来测试它,并重复上述步骤。 4.3
表现得像 4.4
- 没有这样的问题。然而,在 bash 4.3
中,declare
将打印
declare -A array='(["var[0]"]="37" )'
就像4.2
一样。 4.4
不在右侧添加引号,而是打印:
declare -A array=(["var[0]"]="37" )
这使得没有与测试显示的不同。
complete_fullquote
中有一个可能相关的选项,但它是在 4.4
中添加的,因此不能用作解决方法。
似乎除了使用版本 >=4.3
之外,还需要解决这个问题,而您使用的版本是最直接的方法。
解决方法
如果你想避免 sed
调用,还有一个替代方案(使用 bash 4.2
测试):
function array2file {
# local variable for the keys
declare -a keys
# check if the array exists, to protect against injection
# by passing a crafted string
declare -p "" >/dev/null || return 1;
printf "declare -A %s\n" ""
# create a string with all the keys so we can iterate
# because we can't use eval at for's declaration
# we do it this way to allow for spaces in the keys, since that's valid
eval "keys=(\"${![@]}\")"
for k in "${keys[@]}"
do
printf "%s[\"${k//\"/\\\"}\"]=" ""
# the extra quoting here protects against spaces
# within the element's value - injection doesn't work here
# but we still need to make sure there's consistency
eval "printf \"\\"%s\\"\n\" \"${[\"${k//\"/\\"}\"]}\""
done
}
这将正确地在键周围添加引号,并转义键本身内的所有双引号。您可以将其放置在您来源的文件中。然后使用:
array2file array > ./def.sh
其中 array
是您选择的任何数组名称。通过重定向输出,您将获得正确引用的键,您可以像以前一样定义关联数组,然后将其传递给此函数进行存储。
加分
如果您将 for
循环中提供给第一个 printf
的变量从 </code> 更改为 <code>${2:-}
,并在 printf
处执行相同操作在顶部,然后您可以选择创建一个新数组的定义,并将第二个参数作为其名称,允许重命名。只有当您提供 2 个字符串而不是一个(当然是引用)时,才会发生这种情况。该设置允许轻松完成此操作,因此我将其添加到此处。
这可以让您解决使用预定义函数难以与现有代码交互的情况。