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

hel*_*hod 68 arrays bash global-variables parameter-passing

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

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

my_algorithm() {
  local result=$(create_array)
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我只得到一个扩展的字符串.如何在不使用全局的情况下"返回"my_list?

Pau*_*ce. 37

全局变量有什么问题?

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

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

$ 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • @OliverWeiler:例如,cdarke的答案中的技术会使数组变平.`f(){local a =($(g)); 宣告-pa; }; g(){local a =(a'bc'd); echo"$ {a [@]}"; }; f`输出"declare -aa ='([0] ="a"[1] ="b"[2] ="c"[3] ="d")'".您会注意到,现在有4个元素而不是3个元素. (6认同)
  • +1答案很好,但是在返回阵列时你提到的那些陷阱是什么?cdarke的回答看起来非常合理. (5认同)
  • 请参阅[此](/sf/answers/3497984941/)使用namedrefs且无全局变量的解决方案。需要 Bash 版本 4.3 或更高版本。 (2认同)

cod*_*ter 26

使用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
Run Code Online (Sandbox Code Playgroud)

产生输出:

inside use_array
declare -a my_array=([0]="one" [1]="two three" [2]="four")
Run Code Online (Sandbox Code Playgroud)

您可以使该函数更新现有数组:

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
}
Run Code Online (Sandbox Code Playgroud)

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


以下是Bash手册中关于nameref的内容:

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

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

  • 赞成。请注意,有可能发生 [名称冲突](https://mywiki.wooledge.org/BashProgramming?highlight=%28nameref%29#Functions)。另外,请注意 _referenced_ 数组是 _still_ 全局的。 (2认同)
  • 你对本地变量的看法是对的。抱歉,我错过了您对两者都使用了一个函数。 (2认同)

Tod*_*obs 15

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

恕我直言,这很难得到麻烦.如果必须在Bash中传递数据结构,请使用全局变量 - 这就是它们的用途.但是,如果您出于某种原因不想这样做,请考虑位置参数.

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

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

create_array () {
    local array=("a" "b" "c")
    use_array "${array[@]}"
}
Run Code Online (Sandbox Code Playgroud)

但这都会产生一定程度的不必要的复杂性.当您将它们视为具有副作用的程序时,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
Run Code Online (Sandbox Code Playgroud)

如果你担心的是污染你的全局命名空间,你也可以使用unset builtin在你完成它之后删除一个全局变量.使用原始示例,让my_list成为全局(通过删除local关键字)并添加unset my_listmy_algorithm的末尾以自行清理.

  • 仅当生产者(`create_array`)可以调用消费者(`use_array`)时,您的第一个结构才有效,反之则不然。 (2认同)

Ste*_*ell 10

使用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[${#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
Run Code Online (Sandbox Code Playgroud)

输出是:

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

  • 使用`arr + =("value")`而不是用`$ {#arr [@]}`索引.请参阅[this](http://stackoverflow.com/a/1952025/26428).反引号已被弃用,它们难以阅读且难以嵌套.请改用$()`.如果`makeJunk`中的字符串包含换行符,则您的函数不起作用. (2认同)

cda*_*rke 9

你原来的解决方案并不是那么遥远.您遇到了一些问题,您使用逗号作为分隔符,并且无法将返回的项目捕获到列表中,请尝试以下操作:

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

create_array() {
  local my_list=("a" "b" "c")  
  echo "${my_list[@]}" 
}
Run Code Online (Sandbox Code Playgroud)

考虑到有关嵌入式空间的评论,使用一些调整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[*]}" 
}
Run Code Online (Sandbox Code Playgroud)

  • 这不返回数组,它返回一个字符串,使用空格作为分隔符.此解决方案错误地处理数组元素中的空格,因此它不能用于处理例如路径数组. (4认同)
  • @AndreyTarantsov:`create_array`将回显列表,因为我使用`[@]`,如果我使用`[*]`那么它将是一个单独的字符串(除了0-之间的数字之外它不能返回任何内容) 255).在`my_algorithm`中,通过将函数调用括在括号中来创建数组.所以在`my_algorithm`中,变量`result`是一个数组.我对值中的嵌入空间采取了重点,这些因素总会导致问题. (2认同)

F.M*_*.M. 9

一个纯粹的bash,基于'declare -p'内置的最小和强大的解决方案 - 没有疯狂的全局变量

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

  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 builtin将变量传递给函数或从那里传回.
    • 为了在回显变量时保留数组字段中的空格,IFS临时设置为控制字符(例如垂直制表符).
    • 只有变量中声明语句的右侧才能被回显 - 这可以通过$ {parameter#word}形式的参数扩展来实现.至于上面的例子:${myVar#*=}
  3. 最后,使用eval和'declare -a'buinins重新创建传递给它的数组.

示例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
Run Code Online (Sandbox Code Playgroud)

示例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")'
Run Code Online (Sandbox Code Playgroud)

示例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
Run Code Online (Sandbox Code Playgroud)

例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")'
Run Code Online (Sandbox Code Playgroud)