Arrays 在bash脚本中导出数组

Arrays 在bash脚本中导出数组,arrays,bash,export,Arrays,Bash,Export,我无法将数组从一个bash脚本导出到另一个bash脚本,如下所示: export myArray[0]="Hello" export myArray[1]="World" foo=("1: &&& - *" "2: two" "3: %# abc" ) preserve_array "${foo[@]}" foo_stuffed=${_RET} restore_array newfoo "$foo_stuffed" for elem in "${newfoo[@]}";

我无法将数组从一个bash脚本导出到另一个bash脚本,如下所示:

export myArray[0]="Hello"
export myArray[1]="World"
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
当我这样写的时候,没有问题:

export myArray=("Hello" "World")
出于几个原因,我需要将数组初始化为多行。你有什么解决办法吗

数组变量可能尚未导出

来自ubuntu 10.04下bash 4.1.5版的主页

Chet Ramey(截至2011年的bash维护人员)的以下声明可能是关于这个“bug”的最正式的文档:

将数组变量编码到环境中并不是一个好方法


我在编辑另一篇文章时出错了。啊。不管怎样,也许这会有帮助?

注意,由于shell的数组格式在bash或任何其他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
你(嗨!)可以使用这个,不需要写文件,对于ubuntu 12.04,bash 4.2.24

此外,还可以导出多行数组

cat>>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一起使用):

我可以改进它


注:如果有人清除/改进/使之成为我想知道/看到的,谢谢!:D

正如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=("\$@")"
}
像这样使用它:

export myArray[0]="Hello"
export myArray[1]="World"
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”调用

TL;DR:在bash-4.3之前(包括bash-4.3)都不直接支持可导出阵列,但您可以(有效地)通过以下两种方式之一导出阵列:

  • 对子脚本调用方式的简单修改
  • 使用导出的函数存储数组初始化,并对子脚本进行简单修改
或者,您可以等到bash-4.3发布(截至2014年2月处于开发/RC状态,请参阅变更日志中的ARRAY_导出)。更新:4.3中未启用此功能。如果在生成时定义
ARRAY\u EXPORT
,则生成将失败。作者不打算完成此功能


首先要了解的是bash环境(更恰当地说)不同于POSIX环境概念。是未键入的
name=value
对的集合,可以从进程传递给其子进程(实际上是一种有限的形式)

bash执行环境实际上是其中的一个超集,具有类型化变量、只读和可导出标志、数组、函数等。这部分解释了为什么
set
(bash内置)和
env
printenv
的输出不同

当您调用另一个bashshell时,您正在启动一个新进程,您失去了一些bash状态。但是,如果您点源一个脚本,该脚本将在相同的环境中运行;或者,如果通过
()
运行子shell,环境也会被保留(因为bash分叉,保留其完整状态,而不是使用流程环境重新初始化)


@lesmana的答案中引用的限制是因为POSIX环境是简单的
name=value
对,没有额外的意义,因此没有商定的编码或格式化类型化变量的方法,请参阅下面关于函数的有趣bash怪癖,以及bash-4.3中即将进行的更改(建议放弃的数组特性)

有两种简单的方法可以使用
declare-p
(内置)将一些bash环境输出为一组或多个
declare
语句,这些语句可以用来重构“name”的类型和值。这是最基本的,但其他一些答案所暗示的内容要少一些
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“…”
表单的变体有时(错误地)在crontab中用于设置变量

备选方案包括:

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
)


如上所述,有一个有趣的怪癖:通过环境导出函数的特殊支持:

function myfoo() {
    echo foo
}
使用
export-f
set+a
启用此行为,将导致在(流程)环境中出现此情况,使用
printenv
可见:

myfoo=() { echo foo
}
变量为
functionname
(或
functionme()
,用于向后兼容),其值为
({functionbody}
)。 当后续bash进程启动时,它将从每个这样的环境变量重新创建一个函数。如果您查看bash-4.2源文件
variables.c
,您将看到以
()开头的变量{
是经过特殊处理的。(尽管禁止使用
declare-f
的此语法创建函数。)更新:“安全问题与此功能相关,同时。”
% function sharearray() {
    array1=(a b c d)
}

% export -f sharearray 

% bash -c 'sharearray; echo ${array1[*]}'
bash -c "sharearray; . otherscript.sh"
[ "`type -t sharearray`" = "function" ] && sharearray
_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[@]}
_strToArray "$arrayName"
newArray=(${array[@]})
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
$ 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
serialise_array "${my_array[@]}"
while unserialise array
do ...
done
export MY_ARRAY=$(IFS='|'; echo "${myArray[*]}")
IFS='|'; myArray=($MY_ARRAY); unset IFS
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