Exporting an array in bash script
我无法将数组从bash脚本导出到另一个bash脚本,如下所示:
1 2 | export myArray[0]="Hello" export myArray[1]="World" |
当我这样写的时候,没有问题:
1 | export myArray=("Hello""World") |
出于多种原因,我需要将数组初始化为多行。你有什么解决办法吗?
Array variables may not (yet) be exported.
来自Ubuntu 10.04下的bash 4.1.5版的手册页。
Chet Ramey(截至2011年的当前bash维护人员)的以下声明可能是有关此"bug"的最官方文档:
There isn't really a good way to encode an array variable into the environment.
http://www.mail-archive.com/[email protected]/msg01774.html
tl;dr:在bash-4.3之前(包括bash-4.3),不直接支持可导出数组,但您可以(有效地)通过以下两种方式之一导出数组:好的。
- 对子脚本调用方式的简单修改
- 使用导出的函数存储数组初始化,只需简单修改子脚本即可
或,您可以等待,直到bash-4.3发布(从2014年2月起处于开发/rc状态,请参见array_export in the changelog)。update:this feature is not enabled in 4.3.如果在构建时定义
首先要了解的是,bash环境(更合适的命令执行环境)与环境的posix概念不同。posix环境是非类型
bash执行环境实际上是其中的一个超集,具有类型化变量、只读和可导出标志、数组、函数等等。这部分解释了为什么EDOCX1(bash builtin)和
当您调用另一个bash shell时,您正在启动一个新的进程,您会释放一些bash状态。但是,如果您点源一个脚本,则该脚本在同一个环境中运行;或者如果您通过
@lesmana的答案中引用的限制出现了,因为posix环境是简单的的有趣bash quirk,以及bash-4.3中即将发生的更改(建议的数组功能已放弃)。好的。
有两种简单的方法可以做到这一点:使用EDOCX1(内置)将一些bash环境输出为一个或多个
这些方法不需要修改要传递数组的脚本。好的。
1 2 | declare -p array1 array2 > .bash_arrays # serialise to an intermediate file bash -c". .bash_arrays; . otherscript.sh" # source both in the same environment |
上面的
备选方案包括:好的。
1 2 | declare -p array1 array2 > .bash_arrays # serialise to an intermediate file BASH_ENV=.bash_arrays otherscript.sh # non-interactive startup script |
或者,作为一条直线:好的。
1 | BASH_ENV=<(declare -p array1 array2) otherscript.sh |
最后一个使用进程替换将
在非交互shell(即shell脚本)中,由
如果您的所有数组名称恰好都有一个公共前缀,那么您可以使用
您可能不应该尝试使用此方法导出和重新导入整个bash环境,一些特殊的bash变量和数组是只读的,在修改特殊变量时可能会有其他副作用。好的。
(您也可以通过将数组定义序列化为可导出变量,并使用
1 2 3 4 5 6 | $ array=([1]=a [10]="b c") $ export scalar_array=$(declare -p array) $ bash # start a new shell $ eval $scalar_array $ declare -p array declare -a array='([1]="a" [10]="b c")' |
)好的。
如上所述,有一个有趣的特性:通过环境导出函数的特殊支持:好的。
1 2 3 | function myfoo() { echo foo } |
使用
1 2 | myfoo=() { echo foo } |
变量为
如果你继续阅读,你会看到一个好消息是,在当前开发版本bash-4.3rc2中,已启用导出索引数组(非关联)的功能。如上文所述,此功能不太可能被启用。好的。
我们可以使用它创建一个函数来恢复所需的任何数组数据:好的。
1 2 3 4 5 6 7 | % function sharearray() { array1=(a b c d) } % export -f sharearray % bash -c 'sharearray; echo ${array1[*]}' |
因此,与前面的方法类似,调用子脚本时使用:好的。
1 | bash -c"sharearray; . otherscript.sh" |
或者,您可以通过在适当的位置添加来有条件地调用子脚本中的
1 | ["`type -t sharearray`" ="function" ] && sharearray |
注意,如果执行此操作,则
你的措辞也表明你可能与
即使可以(将来)导出数组,也可能不支持导出选定的索引(切片)(尽管由于数组是稀疏的,所以没有理由不允许这样做)。尽管bash也支持语法
哎呀。我不知道为什么其他的答案会让事情变得如此复杂。bash对此几乎有内置支持。
在导出脚本中:
1 2 3 4 5 | myArray=( ' foo"bar ' $' '' baz)' ) # an array with two nasty elements myArray="${myArray[@]@Q}" ./importing_script.sh |
(注意,双引号对于正确处理数组元素中的空白是必需的。)
进入
1 2 3 | ' foo"bar ' $' \ baz)' |
然后,下面将重新构建阵列:
1 | eval"myArray=( ${myArray} )" |
小心!如果您不能信任
正如Lesmana报告的那样,不能导出数组。因此,在通过环境之前必须序列化它们。这种序列化在只有字符串适合的其他地方也很有用(su-c"string",ssh主机"string")。最短的代码方法是滥用"getopt"
1 2 3 4 5 6 7 8 9 10 11 12 13 | # preserve_array(arguments). return in _RET a string that can be expanded # later to recreate positional arguments. They can be restored with: # eval set --"$_RET" preserve_array() { _RET=$(getopt --shell sh --options"" -- --"$@") && _RET=${_RET# --} } # restore_array(name, payload) restore_array() { local name="$1" payload="$2" eval set --"$payload" eval"unset $name && $name=("\$@")" } |
这样使用:
1 2 3 4 5 6 7 8 9 10 | foo=("1: &&& - *""2: two""3: %# abc" ) preserve_array"${foo[@]}" foo_stuffed=${_RET} restore_array newfoo"$foo_stuffed" for elem in"${newfoo[@]}"; do echo"$elem"; done ## output: # 1: &&& - * # 2: two # 3: %# abc |
这不会处理未设置/稀疏数组。您可能能够减少restore_数组中的2个"eval"调用。
基于@mr.spuratic使用
相反,
我需要将外部bash的
由于
概念证明:
"但是,"你说,"你在向bash传递参数——我确实是这样,但这只是一个已知字符的简单字符串。"这是
它给了我这个包装函数
SHELL=/bin/bash BASH_ENV=<( declare -a _=("""$@") && declare -p _ && echo 'set --"${_[@]:1}"') script -f -c 'echo"$@"' /tmp/logfile
}
或者这个函数将包装器作为makefiles的可组合字符串:
多亏了@st_phane chazelas,他指出了我以前的尝试中的所有问题,现在似乎可以将数组序列化为stdout或变量。
此技术不解释器解析输入(与
注意:换行不转义,因为
所有这些都在
使用两个神奇的字符,字段分隔符和记录分隔符(以便可以将多个数组序列化到同一个流)。
这些字符可以定义为
转义字符必须是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | serialise() { set --"${@//\\/\\\\}" # \ set --"${@//${FS:-;}/\\${FS:-;}}" # ; - our field separator set --"${@//${RS:-:}/\\${RS:-:}}" # ; - our record separator local IFS="${FS:-;}" printf ${SERIALIZE_TARGET:+-v"$SERIALIZE_TARGET"}"%s""$*${RS:-:}" } serialise_to() { SERIALIZE_TARGET="$1" serialise"${@:2}" } unserialise() { local IFS="${FS:-;}" if test -n"$2" then read -d"${RS:-:}" -a"$1" <<<"${*:2}" else read -d"${RS:-:}" -a"$1" fi } |
与以下内容不符:
1 | unserialise data # read from stdin |
或
1 | unserialise data"$serialised_data" # from args |
例如
1 2 | $ serialise"Now is the time""For all good men""To drink \$drink""At the \`party\`" $'Party\tParty\tParty' Now is the time;For all good men;To drink $drink;At the `party`;Party Party Party: |
(不带尾随换行符)
读回:
1 2 3 4 5 6 7 8 9 10 | $ serialise_to s"Now is the time""For all good men""To drink \$drink""At the \`party\`" $'Party\tParty\tParty' $ unserialise array"$s" $ echo"${array[@]/#/$' '}" Now is the time For all good men To drink $drink At the `party` Party Party Party |
或
1 | unserialise array # read from stdin |
bash的
如果要序列化一个数组而不仅仅是参数列表,那么只需将数组作为参数列表传递:
1 | serialise_array"${my_array[@]}" |
您可以在循环中使用
1 2 3 | while unserialise array do ... done |
对于没有空格的值的数组,我使用了一组简单的函数来迭代每个数组元素并连接数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | _arrayToStr(){ array=($@) arrayString="" for (( i=0; i<${#array[@]}; i++ )); do if [[ $i == 0 ]]; then arrayString=""${array[i]}"" else arrayString="${arrayString} "${array[i]}"" fi done export arrayString="(${arrayString})" } _strToArray(){ str=$1 array=${str//"/} array=(${array//[()]/""}) export array=${array[@]} } |
第一个函数通过添加左括号和右括号并转义所有双引号将数组转换为字符串。第二个函数将去掉引号和括号,并将它们放入一个虚拟数组中。
为了导出数组,您将传入原始数组的所有元素:
1 2 | array=(foo bar) _arrayToStr ${array[@]} |
此时,数组已导出到值$arraystring中。要在目标文件中导入数组,请重命名该数组并执行相反的转换:
1 2 | _strToArray"$arrayName" newArray=(${array[@]}) |
你(嗨!)可以使用这个,不需要写文件,对于Ubuntu12.04,bash 4.2.24
此外,还可以导出多行数组。
CAT>>导出阵列.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | function FUNCarrayRestore() { local l_arrayName=$1 local l_exportedArrayName=${l_arrayName}_exportedArray # if set, recover its value to array if eval '[[ -n ${'$l_exportedArrayName'+dummy} ]]'; then eval $l_arrayName'='`eval 'echo $'$l_exportedArrayName` #do not put export here! fi } export -f FUNCarrayRestore function FUNCarrayFakeExport() { local l_arrayName=$1 local l_exportedArrayName=${l_arrayName}_exportedArray # prepare to be shown with export -p eval 'export '$l_arrayName # collect exportable array in string mode local l_export=`export -p \ |grep"^declare -ax $l_arrayName=" \ |sed 's"^declare -ax '$l_arrayName'"export '$l_exportedArrayName'"'` # creates exportable non array variable (at child shell) eval"$l_export" } export -f FUNCarrayFakeExport |
在终端bash上测试此示例(与bash 4.2.24一起使用):
1 2 3 4 5 6 7 | source exportArray.sh list=(a b c) FUNCarrayFakeExport list bash echo ${list[@]} #empty :( FUNCarrayRestore list echo ${list[@]} #profit! :D |
我可以在这里改进一下
附言:如果有人澄清/改进/使其不受影响,我想知道/看到,THX!D
我正在编辑另一篇文章,犯了一个错误。哦。不管怎样,也许这会有帮助?https://stackoverflow.com/a/11944320/1594168
注意,因为shell的数组格式在bash或任何其他shell端都是未记录的,以平台无关的方式返回shell数组是非常困难的。你必须检查版本,还要制作一个简单的脚本来具体化所有的将数组放入其他进程可以解析为的文件中。
但是,如果您知道要带回家的数组的名称,那么有一种方法,虽然有点脏。
让我说我有
1 2 3 | MyAry[42]="whatever-stuff"; MyAry[55]="foo"; MyAry[99]="bar"; |
所以我想把它带回家
1 2 3 | name_of_child=MyAry take_me_home="`declare -p ${name_of_child}`"; export take_me_home="${take_me_home/#declare -a ${name_of_child}=/}" |
通过从子流程中进行检查,我们可以看到它正在被导出。
1 | echo""|awk '{print"from awk =["ENVIRON["take_me_home"]"]"; }' |
结果:
1 | from awk =['([42]="whatever-stuff" [55]="foo" [99]="bar")'] |
如果必须这样做,请使用env var来转储它。
1 | env > some_tmp_file |
然后
在运行另一个脚本之前,
1 2 | # This is the magic that does it all source some_tmp_file |