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


78

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

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

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

这样,我只会得到一个扩展的字符串。如何不使用全局变量就“返回” my_list?

Answers:


40

全局变量有什么问题?

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

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

$ 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命令(除了in in外,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

7
+1好答案,但是返回数组时您要提到的那些陷阱是什么?cdarke的答案似乎完全合理。
helpermethod 2012年

6
@OliverWeiler:例如,cdarke的答案中的技术使数组变平坦。f () { local a=($(g)); declare -p a; }; g () { local a=(a 'b c' d); echo "${a[@]}"; }; f输出“ declare -aa ='([0] =” a“ [1] =” b“ [2] =” c“ [3] =” d“)'”。你会发现,而不是3个要素,你现在有4
暂停,直至另行通知。

经过反复试验,我终于明白了丹尼斯在他的评论中所说的“ cdarke的答案展平数组”的含义。该"${array[@]}"语法适当地对数组项目加引号---但echo不会打印未转义的引号。因此,任何使用的解决方案echo仅在没有数组项包含空格的情况下才能正常工作。我对Dennis的示例进行了抽象,并使其更加健壮,从而获得了实用,可重用的实现
Stephen M. Harris

使用其他变量名称的替代方法:f () { local __resultvar=$1; local _local_; _local_=(abc def); declare -p _local_ | sed "s/_local_/$__resultvar/"; }
bpedman

由于bash不支持诸如struct-s甚至对象之类的更高级的东西,因此返回一个属于一组项的数组是我想到的唯一不错的解决方案(如果我错了,请纠正我)。想象一下,您想拥有一个函数create_person,该函数必须返回包含该人的姓名地址的数据结构。否则,您将如何以bash方式进行操作?
rbaleksandar

45

在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
}

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


这是Bash手册中有关nameref的内容:

可以使用-n选项为声明或本地内置命令(请参阅Bash Builtins)创建一个变量,为该变量分配nameref属性,以创建一个nameref或对另一个变量的引用。这允许变量被间接操纵。每当对nameref变量进行引用,分配,设置或修改其属性时(除了使用或更改nameref属性本身之外),实际上都会对nameref变量的值所指定的变量执行操作。在外壳函数中,通常使用nameref来引用变量,该变量的名称作为参数传递给该函数。例如,如果将变量名作为第一个参数传递给shell函数,则运行

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


1
已投票。请注意,可能会发生名称冲突。另外,请注意,引用的数组仍然是全局的。
暂停,直到另行通知。

你是说arr还是my_array?两者都是各自功能的局部特征,因此在外部是看不见的。
codeforester

1
您对本地var的看法是正确的。抱歉,我错过了您同时使用两者的功能。
暂停,直到另行通知。

16

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函数更像是具有副作用的过程,并依次调用它们时,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

如果您担心的是污染全局名称空间,则在使用完全局变量后,还可以使用未设置的内置变量来删除全局变量。使用您的原始示例,让my_list为全局变量(通过删除local关键字),然后添加unset my_listmy_algorithm的末尾以自行清理。


1
仅当生产者(create_array)可以调用消费者(use_array)时,您的第一个结构才有效,反之则不行。
musiphil

12

您最初的解决方案还不算太远。您遇到了几个问题,使用逗号作为分隔符,但无法将返回的项目捕获到列表中,请尝试以下操作:

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[*]}" 
}

4
这不返回数组,而是使用空格作为分隔符返回字符串。该解决方案错误地处理了数组元素内的空间,因此不能用于处理例如路径数组。
安德烈·塔兰佐夫

3
@AndreyTarantsov:create_array 因为我使用[@]过,所以将回显一个列表,如果使用过的[*]话,它将是单个字符串(除0-255之间的数字外,它不会返回任何内容)。内my_algorithm阵列由包围在括号中的函数调用创建。因此在my_algorithm变量中result是一个数组。我的观点是关于值内的嵌入空间,这些空间总是会引起问题。
cdarke

这很好,我建议将oldIFS移到全局外部,并为arrayIFS集创建全局(最好是不可打印的)。例如arrayIFS=$'\001'

12

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

2
使用arr+=("value")而不是使用编制索引${#arr[@]}。看到这个原因。反引号已被弃用,难以阅读且难以嵌套。使用$()代替。如果其中的字符串makeJunk包含换行符,则您的函数将不起作用。
暂停,直到另行通知。

我的变化(如下图)确实具有多串的工作。
TomRoche19年

修正了建议的改进。谢谢
史蒂夫·佐贝尔

9

基于内置的“ declare -p”的纯bash,最小且可靠的解决方案-无需疯狂的全局变量

此方法包括以下三个步骤:

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

我真的很喜欢这种方法-考虑到bash的限制,它很优雅。但是,在示例1中,更改IFS是否真的有效?一切都用双引号引起来,所以我看不到它如何提供帮助。当我设置IFS = $'\ n'并发出包含换行符的字符串数组时,一切正常。也许我在这里错过了一个案例?
bland328

4

有用的示例:从函数返回数组

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

3

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

3

[注意:以下内容对该答案的修改被拒绝,原因是对我来说没有意义(因为该修改的目的不是要针对该帖子的作者!),因此,我建议将其分开回答。]

一个更简单的实施马特McClure的技术的史蒂夫Zobell的适应使用内置在bash(因为版本== 4readarray 由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!

2

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

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

然后我遇到了丹尼斯·威廉姆森的答案。我将他的方法合并到以下函数中,因此它们可以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])\]=}改为使用。
kounoupis

对于具有多个连续空格的数组元素,这具有奇怪的副作用。试试: B=(" a " "" " " "b" " c " " d ") eval A=$(use_array B)
kounoupis

2

我最近需要类似的功能,因此以下是RashaMattSteve Zobell提出的建议的混合。

  1. 每个数组/列表元素作为函数中的单独行回显
  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

2

我最近在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可以直接设置在request_array本地的myarr变量。由于myarr是使用创建的,因此它是request_array的局部变量,因此在request_array范围之外declare返回。

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


1

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

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

1

您找到的最简便的方法

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

result=($(my_function))

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

这是运行througt/bin/bash/bin/sh
CallMarl

0

如果您的源数据的格式与每个列表元素放在单独的行上,则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内置函数一样,您通常不会*mapfile在管道(或子外壳)中使用,因为分配的数组变量将不适用于后续语句(*除非禁用并shopt -s lastpipe设置了bash作业控制)。

$ 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),而不是“如何将列表读入数组”。也许还有另一个问题可以真正回答?
TomRoche

0

我建议管道传递给代码块以设置数组的值。该策略与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 }
}

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

create_array | my_algorithm

0

当值是一个字符串(字符串外没有真正的括号)时,您还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数组声明的右侧是一个字符串,在字符串内部带有括号,而不是声明本身中的真实括号,例如not declare -a dst=( "true parens outside string" )。这将触发declare两次评估字符串,一次进入带括号的有效语句(并保留值中的引号),另一次用于实际赋值。即,它首先对求值declare -a dst=([0]="one" [2]="two three"),然后将作为语句求值。

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

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

因为这种方法依赖于stdout,所以它可以跨子外壳边界(如管道)工作,正如其他人指出的那样。

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


-1

你可以试试这个

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.