如何在不使用全局变量的情况下在 bash 中返回一个数组?

Posted

技术标签:

【中文标题】如何在不使用全局变量的情况下在 bash 中返回一个数组?【英文标题】:How to return an array in bash without using globals? 【发布时间】:2012-05-21 21:41:39 【问题描述】:

我有一个创建数组的函数,我想将数组返回给调用者:

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


my_algorithm() 
  local result=$(create_array)

有了这个,我只得到一个扩展的字符串。如何在不使用任何全局内容的情况下“返回” my_list?

【问题讨论】:

【参考方案1】:

使用 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

产生输出:

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

您也可以让函数更新现有数组:

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

这是一种更优雅、更有效的方法,因为我们不需要命令替换 $() 来获取被调用函数的标准输出。如果函数要返回多个输出,这也会有所帮助 - 我们可以简单地使用与输出数量一样多的 namerefs。


Bash Manual 对 nameref 的评价如下:

可以使用 -n 选项为变量分配 nameref 属性 到声明或本地内置命令(请参阅 Bash Builtins)来创建 nameref,或对另一个变量的引用。这允许变量 被间接操纵。每当 nameref 变量是 引用、分配给、取消设置或修改其属性(其他 而不是使用或更改 nameref 属性本身),操作是 实际对 nameref 变量指定的变量执行 价值。 nameref 通常在 shell 函数中用于引用 名称作为参数传递给函数的变量。为了 例如,如果将变量名作为其传递给 shell 函数 第一个参数,运行

declare -n ref=$1 在函数内部创建一个nameref变量ref 其值是作为第一个参数传递的变量名。 对 ref 的引用和赋值,以及对其属性的更改,是 被视为对 名称作为 $1 传递的变量。

【讨论】:

赞成。请注意,name collision 有可能。另外,请注意 referenced 数组是 still 全局的。 您的意思是arrmy_array?两者都是各自功能的本地,因此在外部不可见。 您对本地变量的看法是正确的。抱歉,我错过了你对两者都使用了一个函数。【参考方案2】:

全局变量有什么问题?

返回数组确实不切实际。有很多陷阱。

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

$ 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

【讨论】:

+1 很好的答案,但是您在返回数组时提到的那些陷阱是什么? cdarke 的回答似乎完全合理。 @OliverWeiler:例如,cdarke 的答案中的技术使数组变平。 f () local a=($(g)); declare -p a; ; g () local a=(a 'b c' d); echo "$a[@]"; ; f 输出“声明 -a a='([0]="a" [1]="b" [2]="c" [3]="d")'”。您会注意到,现在有 4 个,而不是 3 个元素。 经过多次反复试验,我终于明白丹尼斯在他的评论中所说的“cdarke 的答案扁平化数组”是什么意思。 "$array[@]" 语法 适当地引用数组项 --- 但 echo 不打印未转义的引号。因此,任何使用echo 的解决方案只有在没有包含空格的数组项时才能正常工作。我抽象了 Dennis 的示例,并使其获得 practical, reusable implementation 的过程更加健壮。 使用不同变量名的替代方法:f () local __resultvar=$1; local _local_; _local_=(abc def); declare -p _local_ | sed "s/_local_/$__resultvar/"; 参见this 使用namedrefs 且没有全局变量的解决方案。需要 Bash 4.3 或更高版本。【参考方案3】:

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 builtin 删除全局变量。使用您的原始示例,让 my_list 是全局的(通过删除 local 关键字)并将 unset my_list 添加到 my_algorithm 的末尾以进行清理追随自己。

【讨论】:

您的第一个结构只有在生产者 (create_array) 可以调用 消费者 (use_array) 时才有效,反之则不行。【参考方案4】:

您最初的解决方案并没有走得太远。您遇到了几个问题,您使用逗号作为分隔符,并且未能将返回的项目捕获到列表中,试试这个:

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


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

考虑到关于嵌入空间的 cmets,使用 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[*]" 

【讨论】:

这不返回一个数组,它返回一个使用空格作为分隔符的字符串。此解决方案错误地处理数组元素中的空格,因此它不能用于处理例如路径数组。 @AndreyTarantsov: create_array 将回显一个列表,因为我使用了[@],如果我使用了[*],那么它将是一个字符串(除了0-255 之间的数字)。在my_algorithm 中,数组是通过将函数调用括在括号中来创建的。所以在my_algorithm 中,变量result 是一个数组。我认为值中的嵌入空间总是会导致问题。 这很好用,我建议将 oldIFS 移动到全局外部,并为 arrayIFS 创建一个全局,最好设置为不可打印。例如。 arrayIFS=$'\001'.【参考方案5】:

使用 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

【讨论】:

使用arr+=("value") 而不是使用$#arr[@] 进行索引。原因见this。反引号已被弃用,它们难以阅读且难以嵌套。请改用$()。如果makeJunk 中的字符串包含换行符,您的函数将不起作用。 My variation(下)确实适用于多行字符串。 修复了建议的改进。谢谢【参考方案6】:

基于“declare -p”内置的纯 bash、最小且强大的解决方案 — 没有疯狂的全局变量

这种方法涉及以下三个步骤:

    使用“declare -p”转换数组并将输出保存在变量中。myVar="$( declare -p myArray )"declare -p 语句的输出可用于重新创建数组。 例如,declare -p myVar 的输出可能如下所示:declare -a myVar='([0]="1st field" [1]="2nd field" [2]="3rd field")' 使用内置的 echo 将变量传递给函数或从那里传回。 为了在回显变量时保留数组字段中的空白,IFS 临时设置为控制字符(例如垂直制表符)。 只有变量中声明语句的右侧要回显 - 这可以通过 $parameter#word 形式的参数扩展来实现。如上例:$myVar#*= 最后,使用 eval 和“declare -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

示例 1 的输出:

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")'

示例 2 - 将数组传递给函数

#!/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")'

【讨论】:

在示例 2 中,我认为我们应该尽可能使用位置参数。 我真的很喜欢这种方法——考虑到 bash 的限制,它很优雅。但是,对于示例 1,更改 IFS 真的 有什么影响吗?一切都是双引号,所以我看不出它有什么帮助。当我设置 IFS=$'\n' 并发出一个包含换行符的字符串数组时,一切都很好。也许我在这里错过了一个边缘案例?【参考方案7】:

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

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

【讨论】:

【参考方案8】:

我尝试了各种实现,但没有一个保留包含空格元素的数组......因为它们都必须使用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's answer。我将他的方法合并到以下函数中,以便它们可以 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

【讨论】:

如果你想避免使用外部的sed,你可以使用Bash的正则表达式匹配操作符=~$BASH_REMATCH来代替它。 @DennisWilliamson 我不知道使用=~$BASH_REMATCH 进行全局替换的任何方法。但是匹配模式很简单,甚至不需要正则表达式;我更新了函数以使用变量替换而不是sed 我无法获取此代码来重现数组。我复制了所有代码并在最后添加了这个:echo "$test1[0]"。答案是(“一个”“两个”“两个半”)。一切都在第零个元素中,索引 1 和 2 为空。 test2 的结果相同。 变量替换 $r//\[[0-9]\]= 不适用于具有超过 9 个元素的数组(它不会替换 [10]=)。您可以启用 extglob 并改用$r//\[+([0-9])\]= 这对于具有多个连续空格的数组元素有奇怪的副作用。试试:B=(" a " "" " " "b" " c " " d ") eval A=$(use_array B)【参考方案9】:

我最近在 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[@]"

这是这段代码的输出:

当函数request_array调用get_an_array时,get_an_array可以直接设置myarr变量,该变量是本地的>request_array。由于 myarr 是使用 declare 创建的,因此它是 request_array 的本地对象,因此在 request_array 返回时超出范围。

虽然这个解决方案并没有真正返回一个值,但我建议作为一个整体,它满足了一个真正的函数返回值的承诺。

【讨论】:

【参考方案10】:

这是一个没有外部数组引用和 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')

【讨论】:

【参考方案11】:

[注意:以下内容作为this answer 对reasons that make no sense to me 的编辑被拒绝(因为编辑不是旨在解决帖子的作者! ),所以我接受建议,将其作为单独的答案。]

Steve Zobell's adaptation of Matt McClure's technique 的更简单实现使用 bash 内置(自 version == 4 起)readarray as suggested by RastaMatt 来创建可以在运行时转换为数组的数组表示。 (请注意,readarraymapfile 都使用相同的代码。)它仍然避免全局变量(允许在管道中使用函数),并且仍然处理讨厌的字符。

对于一些更完全开发(例如,更多模块化)但仍然有点玩具的示例,请参阅bash_pass_arrays_between_functions。以下是一些易于执行的示例,在此提供以避免版主对外部链接不屑一顾。

剪切以下块并将其粘贴到 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@命令行。你的输出应该和我的类似:

`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!

【讨论】:

【参考方案12】:

我最近需要一个类似的功能,所以下面是RashaMatt和Steve Zobell提出的建议的混合。

    echo 每个数组/列表元素在函数中作为单独的行 使用 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

【讨论】:

【参考方案13】:

这也可以通过简单地将数组变量传递给函数并将数组值分配给这个 var 然后在函数之外使用这个 var 来完成。例如。

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

【讨论】:

FYI 虽然这非常适合与 Bash namerefs(即 declare -n __resultArgArray="$1"),但您的解决方案是缺少对包含空格的数组条目的支持做你的eval 行只在括号周围加上引号。只需将该行从 eval $__resultArgArray="("$my_list[@]")" 更改为 eval $__resultArgArray='("$my_list[@]")' 即可解决此问题【参考方案14】:

你发现的最简单的方法

my_function()

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


result=($(my_function))

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

【讨论】:

这是运行通过/bin/bash 而不是/bin/sh【参考方案15】:

您还可以更轻松地使用declare -p 方法,当值是字符串时利用declare -a 的双重评估(字符串外没有真正的括号):

# 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 声明。因为声明是一个完整的声明,包括“declare”和变量名,我们用$Result#*=从前面去掉那部分,留下带有索引和值的括号:([0]="one" [2]="two three")

然后它通过将该值提供给您自己的声明语句来重新水化数组,您可以在其中选择数组名称。它依赖于这样一个事实:dst 数组声明的右侧是一个带括号的字符串,该括号位于字符串内部,而不是声明本身中的真正括号,例如不是 declare -a dst=( "true parens outside string" )。这会触发declare 对字符串求值两次,一次是带括号的有效语句(并在保留的值中加上引号),另一次用于实际赋值。 IE。它首先评估为 declare -a dst=([0]="one" [2]="two three"),然后评估 that 作为语句。

请注意,这种双重评估行为特定于 declare 的 -a-A 选项。

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

由于此方法依赖于标准输出,因此它可以像管道一样跨子外壳边界工作,正如其他人所指出的那样。

我在blog post 中更详细地讨论了这种方法

【讨论】:

【参考方案16】:

无需使用eval 或将IFS 更改为\n。至少有 2 种好方法可以做到这一点。

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 )) &amp;&amp; readarray -t temp &amp;&amp; set "$temp[@]" &amp;&amp; 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

【讨论】:

警告:mapfile(别名:readarray)功能需要 bash v4,即在 macOS 中不可用,除非您自己安装 (brew-)。 来自ss64:“mapfile 不能做任何使用 read 和 loop 无法完成的事情。read loop 更便携,但比 mapfile 慢得多。” Bash 3 垫片:***.com/questions/41475261/…【参考方案17】:

如果您的源数据被格式化为每个列表元素位于单独的一行,那么 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.

【讨论】:

这是一个非常出色的答案......针对不同的问题。不幸的是,这个问题明确询问“如何返回数组”(添加了emph)而不是“如何将列表读入数组”。也许这实际上回答了另一个问题?【参考方案18】:

你可以试试这个

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

【讨论】:

【参考方案19】:

我建议通过管道连接到代码块来设置数组的值。该策略与 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

【讨论】:

以上是关于如何在不使用全局变量的情况下在 bash 中返回一个数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 Segue 的情况下在单独的 UIViewController 中选择实例变量

如何在不使用存储过程的情况下在表函数中返回值 exec?

如何在不使用“|”的情况下在一行中连接两个集合

在不使用 boost::promise 的情况下在 boost 线程中返回值

如何在不默认创建新范围的情况下在 tensorflow 中重用变量范围?

如何在不自动舍入的情况下在 SQL Server 中返回具有两位小数的数字