2017-08-13 135 views
4

问题击:如何与包含方括号或其他特殊字符

采购的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和getters
  • 我可以用做一个分配这一关键
  • 我能够使用此键
  • 使用declare -p我可以这样定义保存到一个文件从关联数组获得的价值:def.sh
  • 当源文件def.sh发出错误。

我的环境

  • 我使用的Bash的版本是4.2.46 (1)-release下(x86_64-红帽Linux的GNU的)
  • 我在CentOS的1611年3月7日(核心)服务器

解决办法

如果不是做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文件起作用。请注意,在上面的例子中,我也在转义引用字符,这可能是键的一部分。我明白,我上面的内容并不详尽。由于这些复杂因素,如果可能的话,我宁愿选择不涉及此类解决方法的解决方案。

问题

有一些shoptset -o <option>,或别的东西,我可以做,使我坚持一个关联数组,它的键可能包含括号或其他特殊字符的文件,并在以后能够成功发送该文件?我需要一个可以在我的环境中工作的解决方案。

+0

对上述解决方法的改进可能包括使用'%q'使用'printf'。再次,我希望有一个解决方案,涉及'declare -p'和可能的一些行为改变命令;然而,如果这只是Bash 4.2.26(1)发布中的一个bug,那么我的目标将转向一个强大的解决方案,或许是我上面显示的改进。 –

+0

我刚刚使用bash 4.4在Arch Linux上进行了测试,并且没有这样的问题 - 我可以像使用字符串那样使用字符串,不需要解决方法。我试图找到4.2和4.4之间的所有变化的一些参考,看看这是固定的,或者如果后来的版本只是改变了行为。 – mechalynx

+0

好吧,我不确定这是相关的,但我发现它可能是一个错误的轻微提示。我从http://ftp.gnu获得了资源。org/gnu/bash/- 在bash 4.4的源文件中有'CHANGES'下的完整更新日志。第2370行:“e。修复了在关联 数组赋值和扩展中读取下标时遇到的几个错误 ” - 可能是这样,但使用'shopt -s compat42'似乎对我的环境没有任何影响,所以我如果不重新编译bash,我不确定如何测试。 – mechalynx

回答

5

这是一个错误

这是bash 4.2的错误。它在4.3中修复。

我通过编译bash 4.2,4.2.534.3http://ftp.gnu.org/gnu/bash/我测试了这一点,并重复上述步骤。 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 "$1" >/dev/null || return 1; 

    printf "declare -A %s\n" "$1" 

    # 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=(\"\${!$1[@]}\")" 

    for k in "${keys[@]}" 
    do 
    printf "%s[\"${k//\"/\\\\\"}\"]=" "$1" 
    # 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\" \"\${$1[\"${k//\"/\\\"}\"]}\"" 
    done 
} 

这将适当加围绕重点引号,也逃脱内的所有双引号关键本身。你可以把它放在一个文件中,你可以找到它。然后使用:

array2file array > ./def.sh 

其中array是您选择的任何数组名称。通过重定向输出,您将得到正确引用的键,并且可以像以前那样定义关联数组,然后将其传递给此函数进行存储。

附加题

如果更改提供给第一printffor循环内从$1${2:-$1}变量做同样的printf上方,那么你可以选择创建一个新的定义数组第二个参数作为它的名字,允许重新命名。这只会发生,如果你提供2个字符串,而不是一个(当然引用)。该设置可以轻松完成,所以我在这里添加了它。

这样可以让您解决使用预定义函数与现有代码进行接口可能很困难的情况。

+0

非常感谢您的周到解决方案。我将把它加入我在eggsh.com工作的开源Bash脚本平台。加上我的感谢! –

+0

@SteveAmerige np,但你能解释一下编辑吗?我已经测试了以前的版本和这个使用bash 4.2下的测试数组,并且它们似乎功能相同 - 我很好奇你是否遇到了一些问题。顺便说一句,在写邮件中的代码时,我不小心将括号变成了括号,它已被更正。 – mechalynx

+0

@SteveAmerige没有见过,看到了你编辑的评论。然而,问题仍然存在,它们在测试字符串上的功能是一样的,包括你在注释中的那个(它变成了'array [“0”] =“[”com.acme“.var [0]] = 73 “'奇怪的工作,但与两者合作)。我省略了各种特殊字符,因为它们可以用相同的引号处理,而空格和注入的问题更加复杂和细微。 – mechalynx