我无法将数据从bash脚本导出到另一个bash脚本,如下所示:
export myArray[0]="Hello"
export myArray[1]="World"
当我这样写的时候没有问题:
export myArray=("Hello" "World")
由于多种原因,我需要将数组初始化为多行。你有任何解决方案吗?
答案 0 :(得分:44)
可能尚未导出数组变量。
来自ubuntu 10.04下的bash版本4.1.5的联机帮助页。
Chet Ramey(截至2011年的当前bash维护者)的以下声明可能是关于此“bug”的最官方文档:
将数组变量编码到环境中并不是一个好方法。
答案 1 :(得分:31)
TL; DR:可导出的数组不受直接支持,包括bash-4.3,但您可以(有效地)以两种方式之一导出数组:< / p>
或者,您可以等到bash-4.3发布(从2014年2月开始/处于RC状态,请参阅更改日志中的ARRAY_EXPORT)。更新:此功能不在4.3中启用。如果在构建时定义ARRAY_EXPORT
,则构建将失败。作者has stated并未计划完成此功能。
首先要理解的是bash环境(更恰当command execution environment)与环境的POSIX概念不同。 POSIX environment是未列出的name=value
对的集合,可以从流程传递到其子in various ways(实际上是IPC的有限形式)。
bash执行环境实际上是这个的超集,具有类型变量,只读和可导出标志,数组,函数等。这部分解释了为什么set
(bash builtin)和env
或printenv
的输出不同。
当你调用另一个bash shell时,你开始一个新进程,你就会失去一些bash状态。但是,如果您点源脚本,脚本将在同一环境中运行;或者如果您通过( )
运行子shell,则环境也会被保留(因为bash forks,保留其完整状态,而不是使用进程环境重新初始化)。
@ lesmana的答案中引用的限制是因为POSIX环境只是name=value
对而没有额外的意义,所以没有同意的方式来编码或格式化类型变量,见下文关于函数的一个有趣的bash怪癖,以及即将发生的bash-4.3 的变化(建议的数组功能被抛弃)。
有几种简单的方法可以使用declare -p
(内置)将一些bash环境输出为一组一个或多个declare
语句,可用于重构名称&#34;的类型和价值。这是基本的serialisation,但其中一些其他答案意味着更少complexity。 declare -p
保留数组索引,稀疏数组和引用麻烦的值。对于数组的简单序列化,您可以逐行转储值,并使用read -a myarray
来恢复它(使用连续的0索引数组,因为read -a
自动分配索引)。
这些方法不需要对要传递数组的脚本进行任何修改。
declare -p array1 array2 > .bash_arrays # serialise to an intermediate file
bash -c ". .bash_arrays; . otherscript.sh" # source both in the same environment
上述bash -c "..."
形式的变化有时(错误地)用于crontabs中以设置变量。
替代方案包括:
declare -p array1 array2 > .bash_arrays # serialise to an intermediate file
BASH_ENV=.bash_arrays otherscript.sh # non-interactive startup script
或者,作为一个单行:
BASH_ENV=<(declare -p array1 array2) otherscript.sh
最后一个使用进程替换将declare
命令的输出作为rc脚本传递。 (此方法仅适用于bash-4.0或更高版本:早期版本无条件fstat()
rc文件,并且一次性使用返回到read()
文件的大小; FIFO返回大小为0,因此赢得& #39;按照希望工作。)
在非交互式shell (即shell脚本)中,BASH_ENV
变量指向的文件为automatically sourced。你必须确保正确调用bash,可能使用shebang来调用&#34; bash&#34;明确地,而不是#!/bin/sh
,因为bash在历史/ POSIX模式下不会尊重BASH_ENV
。
如果您的所有数组名称碰巧都有一个共同的前缀,您可以使用declare -p ${!myprefix*}
扩展它们的列表,而不是枚举它们。
您可能不应该尝试使用此方法导出和重新导入整个 bash环境,一些特殊的bash变量和数组是只读的,修改时可能会有其他副作用特殊变量。
(您也可以通过将数组定义序列化为可导出变量并使用eval
来做一些有些不愉快的事情,但是不要鼓励使用eval
...
$ 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")'
)
如上所述,有一个有趣的怪癖:通过环境导出功能的特殊支持:
function myfoo() {
echo foo
}
使用export -f
或set +a
启用此行为的将在(进程)环境中生成此内容,并使用printenv
显示:
myfoo=() { echo foo
}
变量为functionname
(或functioname()
以实现向后兼容性),其值为() { functionbody }
。
当后续的bash进程启动时,它将从每个这样的环境变量中重新创建一个函数。如果您查看bash-4.2源文件variables.c
,您将看到以() {
开头的变量被特别处理。 (虽然禁止使用declare -f
使用此语法创建函数。)更新:&#34; shellshock"安全问题与此功能相关,当前系统可能会禁用自动从环境中导入的函数作为缓解。
如果您继续阅读,您会看到#if 0
(或#if ARRAY_EXPORT
)保护代码,用于检查以([
开头并以)
结尾的变量,并且注释说明&#34; 数组变量可能尚未导出&#34;。 好消息是,在当前的开发版本bash-4.3rc2中,导出索引数组(非关联)的能力已启用。此功能不太可能启用,如上所述。
我们可以使用它来创建一个恢复所需数组数据的函数:
% function sharearray() {
array1=(a b c d)
}
% export -f sharearray
% bash -c 'sharearray; echo ${array1[*]}'
因此,与前一种方法类似,使用以下命令调用子脚本:
bash -c "sharearray; . otherscript.sh"
或者,您可以通过在某个适当的位置添加来有条件地调用子脚本中的sharearray
函数:
[ "`type -t sharearray`" = "function" ] && sharearray
注意declare -a
函数中没有sharearray
,如果这样做,数组隐式本地到函数,这不是想要的。 bash-4.2支持显式地使变量成为全局变量的declare -g
,因此可以使用(declare -ga
)。 (由于关联数组需要 declare -A
,您无法在bash-4.2之前将此方法用于关联数组。)GNU parallel
文档具有有用的变体有关此方法的信息,请参阅man page中对--env
的讨论。
您提出的问题也表明您可能遇到export
本身的问题。您可以在创建或修改名称后导出名称。 &#34;导出&#34;是变量的标志或属性,为方便起见,您还可以在单个语句中设置和导出。高达bash-4.2 export
只需要一个名称,或者支持简单(标量)变量或函数名称。
即使您(将来)可以导出数组,也可能不支持导出所选索引(切片)(尽管由于数组稀疏,因此没有理由不允许)。虽然bash也支持语法declare -a name[0]
,但是下标被忽略,&#34; name&#34;只是一个普通的索引数组。
答案 2 :(得分:8)
哎呀。我不知道为什么其他答案让这么复杂。 Bash几乎内置了对此的支持。
在导出脚本中:
myArray=( ' foo"bar ' $'\n''\nbaz)' ) # an array with two nasty elements
myArray="${myArray[@]@Q}" ./importing_script.sh
(注意,双引号对于正确处理数组元素中的空格是必要的。)
进入importing_script.sh
后,myArray
环境变量的值包含这26个字节:
' foo"bar ' $'\n\\nbaz)'
然后以下内容将重新构建数组:
eval "myArray=( ${myArray} )"
小心!如果您不信任eval
环境变量的来源,请不要myArray
这样。这个技巧表现出"Little Bobby Tables"漏洞。想象一下,如果有人要将myArray
的值设置为) ; rm -rf / #
。
答案 3 :(得分:2)
环境只是键-值对的集合,它们都是字符串。一个适用于任何类型数组的正确解决方案都可以
这些在其他帖子中都有介绍。如果您知道您的值永远不会包含某个字符(例如|
)并且您的键是连续的整数,则可以将数组另存为定界列表:
export MY_ARRAY=$(IFS='|'; echo "${myArray[*]}")
并在子进程中将其还原:
IFS='|'; myArray=($MY_ARRAY); unset IFS
答案 4 :(得分:1)
正如lesmana报道的那样,您无法导出数组。所以你必须在通过环境之前序列化它们。这个序列化也很有用,只有一个字符串适合(su -c'string',ssh host'string')。执行此操作的最短代码方式是滥用“getopt”
# 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=("\$@")"
}
像这样使用:
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_array中减少2'eval'调用。
答案 5 :(得分:0)
我正在编辑一个不同的帖子并犯了一个错误。 Augh。无论如何,也许这可能会有所帮助? https://stackoverflow.com/a/11944320/1594168
请注意,因为shell的数组格式在bash或任何其他shell的一侧没有记录, 以平台无关的方式返回shell数组非常困难。 您必须检查版本,并制作一个简洁的脚本,以便将所有内容连接起来 shell数组到其他进程可以解析的文件中。
但是,如果您知道要带回家的阵列的名称那么有一种方法,虽然有点脏。
让我说我有
MyAry[42]="whatever-stuff";
MyAry[55]="foo";
MyAry[99]="bar";
所以我想带回家
name_of_child=MyAry
take_me_home="`declare -p ${name_of_child}`";
export take_me_home="${take_me_home/#declare -a ${name_of_child}=/}"
我们可以通过检查子流程
来看到它被导出echo ""|awk '{print "from awk =["ENVIRON["take_me_home"]"]"; }'
结果:
from awk =['([42]="whatever-stuff" [55]="foo" [99]="bar")']
如果我们绝对必须,请使用env var来转储它。
env > some_tmp_file
然后
在运行另一个脚本之前,
# This is the magic that does it all
source some_tmp_file
答案 6 :(得分:0)
你(嗨!)可以使用这个,不需要写文件,对于ubuntu 12.04,bash 4.2.24
此外,您的多行阵列可以导出。
cat&gt;&gt; exportArray.sh
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):
source exportArray.sh
list=(a b c)
FUNCarrayFakeExport list
bash
echo ${list[@]} #empty :(
FUNCarrayRestore list
echo ${list[@]} #profit! :D
我可以改进它here
PS:如果有人清除/改进/ makeItRunFaster我想知道/看,thx! :d
答案 7 :(得分:0)
对于值不带空格的数组,我一直在使用一组简单的函数迭代每个数组元素并连接数组:
_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[@]}
}
第一个函数,通过添加左括号和右括号并转义所有双引号,将数组转换为字符串。第二个函数将剥离引号和括号,并将它们放入虚拟数组中。
为了导出数组,您将传入原始数组的所有元素:
array=(foo bar)
_arrayToStr ${array[@]}
此时,数组已导出到值$ arrayString。要在目标文件中导入数组,请重命名该数组并执行相反的转换:
_strToArray "$arrayName"
newArray=(${array[@]})
答案 8 :(得分:0)
很多感谢@stéphane-chazelas指出我之前尝试的所有问题,现在似乎可以将数组序列化为stdout或变量。
此技术不会对输入进行shell解析(与declare -a
/ declare -p
不同),因此可以安全地防止在序列化文本中恶意插入元字符。
注意:换行不会被转义,因为read
会删除\<newlines>
字符对,因此必须将-d ...
传递给读取,然后保留未转义的换行符。
所有这些都在unserialise
函数中进行管理。
使用了两个魔术字符,即字段分隔符和记录分隔符(以便可以将多个数组序列化为同一个流)。
这些字符可以定义为FS
和RS
,但两者都不能定义为newline
个字符,因为转义的换行符会被read
删除。
转义字符必须是\
反斜杠,因为这是read
用来避免字符被识别为IFS
字符的内容。
serialise
会将"$@"
序列化为标准输出,serialise_to
将序列化为$1
中指定的变种
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
}
并取消序列化:
unserialise data # read from stdin
或
unserialise data "$serialised_data" # from args
e.g。
$ 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:
(没有尾随换行符)
读回来:
$ 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[@]/#/$'\n'}"
Now is the time
For all good men
To drink $drink
At the `party`
Party Party Party
或
unserialise array # read from stdin
Bash的read
尊重转义字符\
(除非你传递-r标志)以删除字符的特殊含义,例如输入字段分隔或行分隔。
如果你想序列化数组而不是仅仅参数列表,那么只需将数组作为参数列表传递:
serialise_array "${my_array[@]}"
您可以像unserialise
一样在循环中使用read
,因为它只是一个包装读取 - 但请记住,该流不是换行符:
while unserialise array
do ...
done
答案 9 :(得分:0)
基于@ {mr.spuratic使用BASH_ENV
,我在这里$@
通过script -f -c
script -c <command> <logfile>
可用于在另一个pty(和进程组)中运行命令,但它不能将任何结构化参数传递给<command>
。
而<command>
是一个简单的字符串,可作为system
库调用的参数。
我需要将外部bash的$@
隧道传送到脚本调用的bash的$@
中。
由于declare -p
无法取@
,所以我在这里使用magic bash变量_
(带有虚拟的第一个数组值,因为它会被bash覆盖)。这可以节省我对任何重要变量的影响:
概念证明:
BASH_ENV=<( declare -a _=("" "$@") && declare -p _ ) bash -c 'set -- "${_[@]:1}" && echo "$@"'
“但是,”你说,“你正在向bash传递论据 - 事实上我是,但这些是一个已知字符的简单字符串。这是script
使用
SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "$@") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "$@"' /tmp/logfile
给了我这个包装函数in_pty
:
in_pty() {
SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "$@") && declare -p _ && echo 'set -- "${_[@]:1}"') script -f -c 'echo "$@"' /tmp/logfile
}
或此无函数包装器作为Makefile的可组合字符串:
in_pty=bash -c 'SHELL=/bin/bash BASH_ENV=<( declare -a _=("" "$$@") && declare -p _ && echo '"'"'set -- "$${_[@]:1}"'"'"') script -qfc '"'"'"$$@"'"'"' /tmp/logfile' --
...
$(in_pty) test --verbose $@ $^
答案 10 :(得分:0)
我为此编写了自己的函数,并使用IFS
改进了方法:
功能:
$(...)
,因此不会产生另一个bash shell进程?
和|
字符序列化为?00
和?01
序列并返回,因此可以在具有这些字符的数组上使用cygwin bash 3.2.48
和Linux bash 4.3.48
中进行了测试function tkl_declare_global()
{
eval "$1=\"\$2\"" # right argument does NOT evaluate
}
function tkl_declare_global_array()
{
local IFS=$' \t\r\n' # just in case, workaround for the bug in the "[@]:i" expression under the bash version lower than 4.1
eval "$1=(\"\${@:2}\")"
}
function tkl_serialize_array()
{
local __array_var="$1"
local __out_var="$2"
[[ -z "$__array_var" ]] && return 1
[[ -z "$__out_var" ]] && return 2
local __array_var_size
eval declare "__array_var_size=\${#$__array_var[@]}"
(( ! __array_var_size )) && { tkl_declare_global $__out_var ''; return 0; }
local __escaped_array_str=''
local __index
local __value
for (( __index=0; __index < __array_var_size; __index++ )); do
eval declare "__value=\"\${$__array_var[__index]}\""
__value="${__value//\?/?00}"
__value="${__value//|/?01}"
__escaped_array_str="$__escaped_array_str${__escaped_array_str:+|}$__value"
done
tkl_declare_global $__out_var "$__escaped_array_str"
return 0
}
function tkl_deserialize_array()
{
local __serialized_array="$1"
local __out_var="$2"
[[ -z "$__out_var" ]] && return 1
(( ! ${#__serialized_array} )) && { tkl_declare_global $__out_var ''; return 0; }
local IFS='|'
local __deserialized_array=($__serialized_array)
tkl_declare_global_array $__out_var
local __index=0
local __value
for __value in "${__deserialized_array[@]}"; do
__value="${__value//\?01/|}"
__value="${__value//\?00/?}"
tkl_declare_global $__out_var[__index] "$__value"
(( __index++ ))
done
return 0
}
示例:
a=($'1 \n 2' "3\"4'" 5 '|' '?')
tkl_serialize_array a b
tkl_deserialize_array "$b" c
答案 11 :(得分:0)
我认为您可以尝试这种方式(在 export 之后通过 sourcing 您的脚本):
export myArray =(Hello World)
。 yourScript.sh
答案 12 :(得分:0)
虽然这个问题/答案已经很老了,但在搜索 "bash serialize array"
时这篇文章似乎是最热门的
而且,虽然最初的问题与序列化/反序列化数组并不完全相关,但答案似乎确实朝着这个方向发展。
所以……我提供了我的解决方案:
serialize_array.sh
#!/usr/bin/env bash
##
# serialize_array
# Serializes a bash array to a string, with a configurable seperator.
#
# $1 = source varname ( contains array to be serialized )
# $2 = target varname ( will contian the serialized string )
# $3 = seperator ( optional, defaults to $'\x01' )
#
# example:
#
# my_arry=( one "two three" four )
# serialize_array my_array my_string '|'
# declare -p my_string
#
# result:
#
# declare -- my_string="one|two three|four"
#
function serialize_array() {
declare -n _array=${1} # _array => local
local IFS
IFS="${3:-$'\x01'}"
printf -v ${2} "%s" "${_array[*]}"
}
##
# deserialize_array
# Deserializes a string into a bash array, with a configurable seperator.
#
# $1 = source varname ( contains string to be deserialized )
# $2 = target varname ( will contain the deserialized array )
# $3 = seperator ( optional, defaults to $'\x01' )
#
# example:
#
# my_string="one|two three|four"
# deserialize_array my_string my_array '|'
# declare -p my_array
#
# result:
#
# declare -a my_array=([0]="one" [1]="two three" [2]="four")
#
function deserialize_array() {
declare -n _array=${2} #_array => local
local IFS
IFS="${3:-$'\x01'}"
_array=( ${!1} )
}