How to return an array in bash without using globals?

I have a function that creates an array and I want to return the array to the caller:

create_array() {
local my_list=("a", "b", "c")
echo "${my_list[@]}"
}


my_algorithm() {
local result=$(create_array)
}

With this, I only get an expanded string. How can I "return" my_list without using anything global?

121982 次浏览

Bash 不能将数据结构作为返回值传递。返回值必须是0-255之间的数字退出状态。但是,如果您愿意的话,当然可以使用命令或进程替换将命令传递给 eval 语句。

恕我直言,这很少值得麻烦。如果必须在 Bash 中传递数据结构,那么使用全局变量——这就是它们的用途。但是,如果出于某种原因不想这样做,可以从位置参数的角度考虑。

您的示例可以很容易地重写为使用位置参数而不是全局变量:

use_array () {
for idx in "$@"; do
echo "$idx"
done
}


create_array () {
local array=("a" "b" "c")
use_array "${array[@]}"
}

不过,这一切都造成了一定程度的不必要的复杂性。如果将 Bash 函数视为具有副作用的过程,并按顺序调用它们,则它们的工作效果通常最好。

# Gather values and store them in FOO.
get_values_for_array () { :; }


# Do something with the values in FOO.
process_global_array_variable () { :; }


# Call your functions.
get_values_for_array
process_global_array_variable

如果您所担心的只是污染您的全局名称空间,那么您还可以在使用完全局变量之后使用 未建成的来删除它。使用您的原始示例,让 我的名单是全局的(通过删除 本地关键字) ,并将 unset my_list添加到 我的算法的末尾,以清理自己。

全球化有什么不好?

返回数组实际上并不实用,有很多陷阱。

也就是说,如果变量的名称相同,这里有一种技术是可行的:

$ f () { local a; a=(abc 'def ghi' jkl); declare -p a; }
$ g () { local a; eval $(f); declare -p a; }
$ f; declare -p a; echo; g; declare -p a
declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found


declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

declare -p命令(f()中的命令除外)用于显示数组的状态,以便演示。在 f()中,它被用作返回数组的机制。

如果您需要数组有一个不同的名称,您可以这样做:

$ g () { local b r; r=$(f); r="declare -a b=${r#*=}"; eval "$r"; declare -p a; declare -p b; }
$ f; declare -p a; echo; g; declare -p a
declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found


-bash: declare: a: not found
declare -a b='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

你的原始解决方案并没有实现太多。您遇到了一些问题,您使用逗号作为分隔符,并且您未能将返回的项目捕获到列表中,请尝试以下操作:

my_algorithm() {
local result=( $(create_array) )
}


create_array() {
local my_list=("a" "b" "c")
echo "${my_list[@]}"
}

考虑到关于嵌入式空间的注释,使用 IFS进行一些调整可以解决这个问题:

my_algorithm() {
oldIFS="$IFS"
IFS=','
local result=( $(create_array) )
IFS="$oldIFS"
echo "Should be 'c d': ${result[1]}"
}


create_array() {
IFS=','
local my_list=("a b" "c d" "e f")
echo "${my_list[*]}"
}

有用的例子: 从函数返回一个数组

function Query() {
local _tmp=`echo -n "$*" | mysql 2>> zz.err`;
echo -e "$_tmp";
}


function StrToArray() {
IFS=$'\t'; set $1; for item; do echo $item; done; IFS=$oIFS;
}


sql="SELECT codi, bloc, requisit FROM requisits ORDER BY codi";
qry=$(Query $sql0);
IFS=$'\n';
for row in $qry; do
r=( $(StrToArray $row) );
echo ${r[0]} - ${r[1]} - ${r[2]};
done

我尝试了各种实现,没有一个保留了带有空格的元素的数组... ... 因为它们都必须使用 echo

# These implementations only work if no array items contain spaces.
use_array() {  eval echo  '(' \"\${${1}\[\@\]}\" ')';  }
use_array() {  local _array="${1}[@]"; echo '(' "${!_array}" ')';  }

解决方案

然后我发现了 Dennis Williamson 的回答。我将他的方法合并到以下函数中,这样它们可以 a)接受任意数组,b)用于传递、复制和追加数组。

# Print array definition to use with assignments, for loops, etc.
#   varname: the name of an array variable.
use_array() {
local r=$( declare -p $1 )
r=${r#declare\ -a\ *=}
# Strip keys so printed definition will be a simple list (like when using
# "${array[@]}").  One side effect of having keys in the definition is
# that when appending arrays (i.e. `a1+=$( use_array a2 )`), values at
# matching indices merge instead of pushing all items onto array.
echo ${r//\[[0-9]\]=}
}
# Same as use_array() but preserves keys.
use_array_assoc() {
local r=$( declare -p $1 )
echo ${r#declare\ -a\ *=}
}

然后,其他函数可以使用可捕获的输出或间接参数返回数组。

# catchable output
return_array_by_printing() {
local returnme=( "one" "two" "two and a half" )
use_array returnme
}
eval test1=$( return_array_by_printing )


# indirect argument
return_array_to_referenced_variable() {
local returnme=( "one" "two" "two and a half" )
eval $1=$( use_array returnme )
}
return_array_to_referenced_variable test2


# Now both test1 and test2 are arrays with three elements

Use the technique developed by Matt McClure: Http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html

避免全局变量意味着可以在管道中使用函数:

#!/bin/bash


makeJunk()
{
echo 'this is junk'
echo '#more junk and "b@d" characters!'
echo '!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'"'"
}


processJunk()
{
local -a arr=()
# read each input and add it to arr
while read -r line
do
arr+=('"'"$line"'" is junk')
done;


# output the array as a string in the "declare" representation
declare -p arr | sed -e 's/^declare -a [^=]*=//'
}


# processJunk returns the array in a flattened string ready for "declare"
# Note that because of the pipe processJunk cannot return anything using
# a global variable
returned_string="$(makeJunk | processJunk)"


# convert the returned string to an array named returned_array
# declare correctly manages spaces and bad characters
eval "declare -a returned_array=${returned_string}"


for junk in "${returned_array[@]}"
do
echo "$junk"
done

产出为:

"this is junk" is junk
"#more junk and "b@d" characters!" is junk
"!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'" is junk

如果你的源数据是用单独的一行上的每个列表元素格式化的,那么 mapfile内置是一个简单而优雅的将列表读入数组的方法:

$ list=$(ls -1 /usr/local)           # one item per line


$ mapfile -t arrayVar <<<"$list"     # -t trims trailing newlines


$ declare -p arrayVar | sed 's#\[#\n[#g'
declare -a arrayVar='(
[0]="bin"
[1]="etc"
[2]="games"
[3]="include"
[4]="lib"
[5]="man"
[6]="sbin"
[7]="share"
[8]="src")'

注意,与使用 read内置函数一样,您通常不会 * 在管道(或子 shell)中使用 mapfile,因为分配的数组变量对后续语句不可用(* 除非禁用 bash 作业控制并设置了 shopt -s lastpipe)。

$ help mapfile
mapfile: mapfile [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
Read lines from the standard input into an indexed array variable.


Read lines from the standard input into the indexed array variable ARRAY, or
from file descriptor FD if the -u option is supplied.  The variable MAPFILE
is the default ARRAY.


Options:
-n count  Copy at most COUNT lines.  If COUNT is 0, all lines are copied.
-O origin Begin assigning to ARRAY at index ORIGIN.  The default index is 0.
-s count  Discard the first COUNT lines read.
-t                Remove a trailing newline from each line read.
-u fd             Read lines from file descriptor FD instead of the standard input.
-C callback       Evaluate CALLBACK each time QUANTUM lines are read.
-c quantum        Specify the number of lines read between each call to CALLBACK.


Arguments:
ARRAY             Array variable name to use for file data.


If -C is supplied without -c, the default quantum is 5000.  When
CALLBACK is evaluated, it is supplied the index of the next array
element to be assigned and the line to be assigned to that element
as additional arguments.


If not supplied with an explicit origin, mapfile will clear ARRAY before
assigning to it.


Exit Status:
Returns success unless an invalid option is given or ARRAY is readonly or
not an indexed array.

A pure bash, minimal and robust solution based on the 'declare -p' builtin — without insane global variables

这种方法包括以下三个步骤:

  1. Convert the array with 'declare -p' and save the output in a variable.
    myVar="$( declare -p myArray )"
    declare -p语句的输出可用于重新创建数组。 For instance the output of declare -p myVar might look like this:
    declare -a myVar='([0]="1st field" [1]="2nd field" [2]="3rd field")'
  2. Use the echo builtin to pass the variable to a function or to pass it back from there.
    • 为了在回显变量时保留数组字段中的空格,IFS 被临时设置为控制字符(例如垂直制表符)。
    • 只有变量中声明语句的右边要回显——这可以通过形式为 ${ Parameter # word }的参数展开来实现。至于上面的例子: ${myVar#*=}
  3. 最后,使用 eval 和“ Declaration-a”内置函数重新创建传递给它的数组。

例1-从函数返回一个数组

#!/bin/bash


# Example 1 - return an array from a function


function my-fun () {
# set up a new array with 3 fields - note the whitespaces in the
# 2nd (2 spaces) and 3rd (2 tabs) field
local myFunArray=( "1st field" "2nd  field" "3rd       field" )


# show its contents on stderr (must not be output to stdout!)
echo "now in $FUNCNAME () - showing contents of myFunArray" >&2
echo "by the help of the 'declare -p' builtin:" >&2
declare -p myFunArray >&2


# return the array
local myVar="$( declare -p myFunArray )"
local IFS=$'\v';
echo "${myVar#*=}"


# if the function would continue at this point, then IFS should be
# restored to its default value: <space><tab><newline>
IFS=' '$'\t'$'\n';
}


# main


# call the function and recreate the array that was originally
# set up in the function
eval declare -a myMainArray="$( my-fun )"


# show the array contents
echo ""
echo "now in main part of the script - showing contents of myMainArray"
echo "by the help of the 'declare -p' builtin:"
declare -p myMainArray


# end-of-file

例一的输出:

now in my-fun () - showing contents of myFunArray
by the help of the 'declare -p' builtin:
declare -a myFunArray='([0]="1st field" [1]="2nd  field" [2]="3rd       field")'


now in main part of the script - showing contents of myMainArray
by the help of the 'declare -p' builtin:
declare -a myMainArray='([0]="1st field" [1]="2nd  field" [2]="3rd      field")'

Example 2 - pass an array to a function

#!/bin/bash


# Example 2 - pass an array to a function


function my-fun () {
# recreate the array that was originally set up in the main part of
# the script
eval declare -a myFunArray="$( echo "$1" )"


# note that myFunArray is local - from the bash(1) man page: when used
# in a function, declare makes each name local, as with the local
# command, unless the ‘-g’ option is used.


# IFS has been changed in the main part of this script - now that we
# have recreated the array it's better to restore it to the its (local)
# default value: <space><tab><newline>
local IFS=' '$'\t'$'\n';


# show contents of the array
echo ""
echo "now in $FUNCNAME () - showing contents of myFunArray"
echo "by the help of the 'declare -p' builtin:"
declare -p myFunArray
}


# main


# set up a new array with 3 fields - note the whitespaces in the
# 2nd (2 spaces) and 3rd (2 tabs) field
myMainArray=( "1st field" "2nd  field" "3rd     field" )


# show the array contents
echo "now in the main part of the script - showing contents of myMainArray"
echo "by the help of the 'declare -p' builtin:"
declare -p myMainArray


# call the function and pass the array to it
myVar="$( declare -p myMainArray )"
IFS=$'\v';
my-fun $( echo "${myVar#*=}" )


# if the script would continue at this point, then IFS should be restored
# to its default value: <space><tab><newline>
IFS=' '$'\t'$'\n';


# end-of-file

例子2的输出:

now in the main part of the script - showing contents of myMainArray
by the help of the 'declare -p' builtin:
declare -a myMainArray='([0]="1st field" [1]="2nd  field" [2]="3rd      field")'


now in my-fun () - showing contents of myFunArray
by the help of the 'declare -p' builtin:
declare -a myFunArray='([0]="1st field" [1]="2nd  field" [2]="3rd       field")'

[ 注:以下内容作为 这个答案对我来说毫无意义的理由的编辑被拒绝(因为该编辑是 没有,意在解决文章的作者!)所以我采纳了这个建议,把它作为一个独立的答案。]

史蒂夫 · 佐贝尔对马特 · 麦克卢尔技术的改编的一个更简单的实现使用内置的 bash (因为 Version = = 4) readarray 就像 RastaMatt 建议的那样来创建一个可以在运行时转换为数组的数组表示形式。(请注意,readarraymapfile的名称是相同的代码。)它仍然避免全局变量(允许在管道中使用函数) ,并且仍然处理讨厌的字符。

对于一些更完全开发(例如,更多模块化)但仍然是玩具的示例,请参见 Bash _ pass _ array _ between _ function。下面是一些易于执行的示例,这里提供的示例是为了避免版主对外部链接进行修改。

将下面的代码块剪切并粘贴到 bash 终端以创建 /tmp/source.sh/tmp/junk1.sh:

FP='/tmp/source.sh'     # path to file to be created for `source`ing
cat << 'EOF' > "${FP}"  # suppress interpretation of variables in heredoc
function make_junk {
echo 'this is junk'
echo '#more junk and "b@d" characters!'
echo '!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'"'"
}


### Use 'readarray' (aka 'mapfile', bash built-in) to read lines into an array.
### Handles blank lines, whitespace and even nastier characters.
function lines_to_array_representation {
local -a arr=()
readarray -t arr
# output array as string using 'declare's representation (minus header)
declare -p arr | sed -e 's/^declare -a [^=]*=//'
}
EOF


FP1='/tmp/junk1.sh'      # path to script to run
cat << 'EOF' > "${FP1}"  # suppress interpretation of variables in heredoc
#!/usr/bin/env bash


source '/tmp/source.sh'  # to reuse its functions


returned_string="$(make_junk | lines_to_array_representation)"
eval "declare -a returned_array=${returned_string}"
for elem in "${returned_array[@]}" ; do
echo "${elem}"
done
EOF
chmod u+x "${FP1}"
# newline here ... just hit Enter ...

运行 /tmp/junk1.sh: 输出应该是

this is junk
#more junk and "b@d" characters!
!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'

注意,lines_to_array_representation也处理空行。尝试将下面的代码块粘贴到 bash 终端:

FP2='/tmp/junk2.sh'      # path to script to run
cat << 'EOF' > "${FP2}"  # suppress interpretation of variables in heredoc
#!/usr/bin/env bash


source '/tmp/source.sh'  # to reuse its functions


echo '`bash --version` the normal way:'
echo '--------------------------------'
bash --version
echo # newline


echo '`bash --version` via `lines_to_array_representation`:'
echo '-----------------------------------------------------'
bash_version="$(bash --version | lines_to_array_representation)"
eval "declare -a returned_array=${bash_version}"
for elem in "${returned_array[@]}" ; do
echo "${elem}"
done
echo # newline


echo 'But are they *really* the same? Ask `diff`:'
echo '-------------------------------------------'


echo 'You already know how to capture normal output (from `bash --version`):'
declare -r PATH_TO_NORMAL_OUTPUT="$(mktemp)"
bash --version > "${PATH_TO_NORMAL_OUTPUT}"
echo "normal output captured to file @ ${PATH_TO_NORMAL_OUTPUT}"
ls -al "${PATH_TO_NORMAL_OUTPUT}"
echo # newline


echo 'Capturing L2AR takes a bit more work, but is not onerous.'
echo "Look @ contents of the file you're about to run to see how it's done."


declare -r RAW_L2AR_OUTPUT="$(bash --version | lines_to_array_representation)"
declare -r PATH_TO_COOKED_L2AR_OUTPUT="$(mktemp)"
eval "declare -a returned_array=${RAW_L2AR_OUTPUT}"
for elem in "${returned_array[@]}" ; do
echo "${elem}" >> "${PATH_TO_COOKED_L2AR_OUTPUT}"
done
echo "output from lines_to_array_representation captured to file @ ${PATH_TO_COOKED_L2AR_OUTPUT}"
ls -al "${PATH_TO_COOKED_L2AR_OUTPUT}"
echo # newline


echo 'So are they really the same? Per'
echo "\`diff -uwB "${PATH_TO_NORMAL_OUTPUT}" "${PATH_TO_COOKED_L2AR_OUTPUT}" | wc -l\`"
diff -uwB "${PATH_TO_NORMAL_OUTPUT}" "${PATH_TO_COOKED_L2AR_OUTPUT}" | wc -l
echo '... they are the same!'
EOF
chmod u+x "${FP2}"
# newline here ... just hit Enter ...

运行 /tmp/junk2.sh@commandline。您的输出应该与我的类似:

`bash --version` the normal way:
--------------------------------
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>


This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


`bash --version` via `lines_to_array_representation`:
-----------------------------------------------------
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>


This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


But are they *really* the same? Ask `diff`:
-------------------------------------------
You already know how to capture normal output (from `bash --version`):
normal output captured to file @ /tmp/tmp.Ni1bgyPPEw
-rw------- 1 me me 308 Jun 18 16:27 /tmp/tmp.Ni1bgyPPEw


Capturing L2AR takes a bit more work, but is not onerous.
Look @ contents of the file you're about to run to see how it's done.
output from lines_to_array_representation captured to file @ /tmp/tmp.1D6O2vckGz
-rw------- 1 me me 308 Jun 18 16:27 /tmp/tmp.1D6O2vckGz


So are they really the same? Per
`diff -uwB /tmp/tmp.Ni1bgyPPEw /tmp/tmp.1D6O2vckGz | wc -l`
0
... they are the same!

我最近需要一个类似的功能,所以下面是由 RashaMattSteve Zobell提出的建议的组合。

  1. Echo 将每个数组/列表元素作为函数内的独立行
  2. 使用 mapfile读取函数回显的所有数组/列表元素。

据我所知,字符串保持完整,空格保持不变。

#!bin/bash


function create-array() {
local somearray=("aaa" "bbb ccc" "d" "e f g h")
for elem in "${somearray[@]}"
do
echo "${elem}"
done
}


mapfile -t resa <<< "$(create-array)"


# quick output check
declare -p resa

还有一些变化。

#!/bin/bash


function create-array-from-ls() {
local somearray=("$(ls -1)")
for elem in "${somearray[@]}"
do
echo "${elem}"
done
}


function create-array-from-args() {
local somearray=("$@")
for elem in "${somearray[@]}"
do
echo "${elem}"
done
}




mapfile -t resb <<< "$(create-array-from-ls)"
mapfile -t resc <<< "$(create-array-from-args 'xxx' 'yy zz' 't s u' )"


sentenceA="create array from this sentence"
sentenceB="keep this sentence"


mapfile -t resd <<< "$(create-array-from-args ${sentenceA} )"
mapfile -t rese <<< "$(create-array-from-args "$sentenceB" )"
mapfile -t resf <<< "$(create-array-from-args "$sentenceB" "and" "this words" )"


# quick output check
declare -p resb
declare -p resc
declare -p resd
declare -p rese
declare -p resf

你可以试试这个

my_algorithm() {
create_array list
for element in "${list[@]}"
do
echo "${element}"
done
}


create_array() {
local my_list=("1st one" "2nd two" "3rd three")


eval "${1}=()"
for element in "${my_list[@]}"
do
eval "${1}+=(\"${element}\")"
done
}


my_algorithm

输出是

1st one
2nd two
3rd three

在 Bash 4.3及以上版本中,您可以使用 nameref,以便调用方可以传入数组名称,被调用方可以使用 nameref 填充命名数组 间接地

#!/usr/bin/env bash


create_array() {
local -n arr=$1             # use nameref for indirection
arr=(one "two three" four)
}


use_array() {
local my_array
create_array my_array       # call function to populate the array
echo "inside use_array"
declare -p my_array         # test the array
}


use_array                       # call the main function

Produces the output:

inside use_array
declare -a my_array=([0]="one" [1]="two three" [2]="four")

You could make the function update an existing array as well:

update_array() {
local -n arr=$1             # use nameref for indirection
arr+=("two three" four)     # update the array
}


use_array() {
local my_array=(one)
update_array my_array       # call function to update the array
}

这是自 we don't need command substitution $()以来获取被调用函数的标准输出的一种更优雅、更有效的方法。如果函数返回多个输出,也会有所帮助——我们可以简单地使用与输出数量同样多的 nameref。


以下是 Bash 手册对 nameref 的解释:

可以使用-n 选项为变量分配 nameref 属性 声明或本地内建命令(请参见 Bash 内建)来创建 一个 nameref 或对另一个变量的引用。这允许变量 每当 nameref 变量为 引用、分配、取消设置或修改其属性(其他 than using or changing the nameref attribute itself), the operation is 实际上是对 nameref 变量指定的变量执行的 Nameref 通常在 shell 函数中用于引用 变量的名称作为参数传递给函数 实例,如果将变量名作为其 第一次争吵,跑步

在函数内部声明-n ref = $1创建一个 nameref 变量 ref 其值是作为第一个参数传递的变量名。 对 ref 的引用和赋值以及对其属性的更改如下 对象的引用、赋值和属性修改 变量的名称传递为 $1。

我最近在 BASH 中发现了一个奇怪的现象,函数可以直接访问调用堆栈中较高的函数中声明的变量。我刚刚开始考虑如何利用这个特性(它承诺了好处和危险) ,但是一个显而易见的应用是解决这个问题的精神。

在委托创建数组时,我还希望获得一个返回值,而不是使用全局变量。我倾向于这样做的原因有几个,其中之一是为了避免可能干扰先前存在的值,并避免留下一个在以后访问时可能无效的值。虽然有解决这些问题的方法,但最简单的方法是在代码使用完变量后让它超出作用域。

我的解决方案确保数组在需要时可用,在函数返回时丢弃,并保留具有相同名称的全局变量不受干扰。

#!/bin/bash


myarr=(global array elements)


get_an_array()
{
myarr=( $( date +"%Y %m %d" ) )
}


request_array()
{
declare -a myarr
get_an_array "myarr"
echo "New contents of local variable myarr:"
printf "%s\n" "${myarr[@]}"
}


echo "Original contents of global variable myarr:"
printf "%s\n" "${myarr[@]}"
echo


request_array


echo
echo "Confirm the global myarr was not touched:"
printf "%s\n" "${myarr[@]}"

下面是这段代码的输出: program output

当函数 Request _ array 请求 _ 数组调用 获取 _ an _ array时,获取 _ an _ array可以直接设置 Request _ array 请求 _ 数组的本地 Myarr变量。由于 Myarr是用 declare创建的,因此它是 Request _ array 请求 _ 数组的本地数据,因此当 Request _ array 请求 _ 数组返回时就超出了作用域。

尽管这个解决方案并不真正返回值,但我建议从整体上看,它满足了真正的函数返回值的承诺。

这也可以通过简单地将数组变量传递给函数,并将数组值赋给这个变量,然后在函数之外使用这个变量来实现。比如说。

create_array() {
local  __resultArgArray=$1
local my_list=("a" "b" "c")
eval $__resultArgArray="("${my_list[@]}")"
}


my_algorithm() {
create_array result
echo "Total elements in the array: ${#result[@]}"
for i in "${result[@]}"
do
echo $i
done
}


my_algorithm

这是一个没有外部数组引用和 IFS 操作的解决方案:

# add one level of single quotes to args, eval to remove
squote () {
local a=("$@")
a=("${a[@]//\'/\'\\\'\'}")   # "'" => "'\''"
a=("${a[@]/#/\'}")           # add "'" prefix to each word
a=("${a[@]/%/\'}")           # add "'" suffix to each word
echo "${a[@]}"
}


create_array () {
local my_list=(a "b 'c'" "\\\"d
")
squote "${my_list[@]}"
}


my_algorithm () {
eval "local result=($(create_array))"
# result=([0]="a" [1]="b 'c'" [2]=$'\\"d\n')
}

The easest way y found

my_function()
{
array=(one two three)
echo ${array[@]}
}


result=($(my_function))


echo ${result[0]}
echo ${result[1]}
echo ${result[2]}

我建议通过管道连接到代码块来设置数组的值。这种策略与 POSIX 兼容,因此您可以同时使用 Bash 和 Zsh,并且不会像发布的解决方案那样存在副作用的风险。

i=0                   # index for our new array
declare -a arr        # our new array


# pipe from a function that produces output by line
ls -l | { while read data; do i=$i+1; arr[$i]="$data"; done }


# example of reading that new array
for row in "${arr[@]}"; do echo "$row"; done

这将为 zshbash工作,不会受到空格或特殊字符的影响。在 OP 的情况下,输出通过 echo 进行转换,因此它实际上并不输出数组,而是打印数组(正如其他提到的 shell 函数返回的是状态而不是值)。我们可以将其改为管道准备机制:

create_array() {
local my_list=("a", "b", "c")
for row in "${my_list[@]}"; do
echo "$row"
done
}


my_algorithm() {
i=0
declare -a result
create_array | { while read data; do i=$i+1; result[$i]="$data"; done }
}

如果愿意,可以从 my_algorithm中删除 create_array流水线进程,并将这两个功能链接在一起

create_array | my_algorithm

当值为字符串(字符串外没有真括号)时,还可以利用 declare -a的双计算,从而更容易地使用 declare -p方法:

# return_array_value returns the value of array whose name is passed in.
#   It turns the array into a declaration statement, then echos the value
#   part of that statement with parentheses intact.  You can use that
#   result in a "declare -a" statement to create your own array with the
#   same value.  Also works for associative arrays with "declare -A".
return_array_value () {
declare Array_name=$1  # namespace locals with caps to prevent name collision
declare Result


Result=$(declare -p $Array_name)  # dehydrate the array into a declaration
echo "${Result#*=}"               # trim "declare -a ...=" from the front
}


# now use it.  test for robustness by skipping an index and putting a
# space in an entry.
declare -a src=([0]=one [2]="two three")
declare -a dst="$(return_array_value src)"    # rehydrate with double-eval


declare -p dst
> declare -a dst=([0]="one" [2]="two three")  # result matches original

验证结果后,declare -p dst生成 declare -a dst=([0]="one" [2]="two three")",证明该方法正确地处理了稀疏数组和具有 IFS 字符(空格)的条目。

第一件事是使用 declare -p生成源数组的有效 bash 声明,从而使其脱水。因为声明是一个完整的语句,包括“ Declaration”和变量名,所以我们用 ${Result#*=}从前面去掉该部分,留下括号,其中包含索引和值: ([0]="one" [2]="two three")

然后,通过将该值提供给您自己的声明语句(您可以在其中选择数组名称)来重新水合数组。它依赖于这样一个事实,即 dst数组声明的右边是一个包含括号的字符串,而不是声明本身中的真括号,例如 not declare -a dst=( "true parens outside string" )。这会触发 declare对字符串求值两次,一次为带括号的有效语句(保留值中有引号) ,另一次为实际赋值。也就是说,它首先计算 declare -a dst=([0]="one" [2]="two three"),然后计算 那个作为一个语句。

Note that this double evaluation behavior is specific to the -a and -A options of declare.

哦,这个方法也适用于关联数组,只需将 -a改为 -A

因为这个方法依赖于 stdout,所以它可以像管道一样跨子 shell 边界工作。

我将在 博客文章中更详细地讨论这种方法

不需要使用 eval或者将 IFS改为 \n。至少有两种方法可以做到这一点。

1)使用 echomapfile

您可以简单地回显函数中数组的每个项,然后使用 mapfile将其转换为数组:

outputArray()
{
for i
{
echo "$i"
}
}


declare -a arr=( 'qq' 'www' 'ee rr' )
mapfile -t array < <(outputArray "${arr[@]}")
for i in "${array[@]}"
do
echo "i=$i"
done

要使用管道工作,请在输出数组的顶部添加 (( $# == 0 )) && readarray -t temp && set "${temp[@]}" && unset temp,它将标准输入转换为参数。

2)使用 declare -psed

这也可以用 declare -psed代替 mapfile来完成。

outputArray()
{
(( $# == 0 )) && readarray -t temp && set "${temp[@]}" && unset temp
for i; { echo "$i"; }
}


returnArray()
{
local -a arr=()
(( $# == 0 )) && readarray -t arr || for i; { arr+=("$i"); }
declare -p arr | sed -e 's/^declare -a [^=]*=//'
}


declare -a arr=( 'qq' 'www' 'ee rr' )


declare -a array=$(returnArray "${arr[@]}")
for i in "${array[@]}"
do
echo "i=$i"
done


declare -a array=$(outputArray "${arr[@]}" | returnArray)
echo
for i in "${array[@]}"
do
echo "i=$i"
done


declare -a array < <(outputArray "${arr[@]}" | returnArray)
echo
for i in "${array[@]}"
do
echo "i=$i"
done

虽然 declare -p方法确实很优雅,但是你仍然可以使用 declare -g 内心函数创建一个全局数组,并且可以看到 在外面函数的作用域:

create_array() {
declare -ag result=("a", "b", "c")
}


my_algorithm() {
create_array
echo "${result[@]}"
}