如何在bash中返回一个数组而不使用全局variables?

假设你已经写了一些函数来收集数组中的一些值。

my_algorithm() { create_array # call create_array somehow # here, work with the array "returned" from my_list } create_array() { local my_list=("a", "b", "c") } 

我怎样才能“返回”my_list而不使用任何东西全球?

编辑

我可以做类似的事情

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

但是,我只能得到一个扩展的string(或者这是要走的路?)。

全局variables有什么问题?

返回数组实际上是不实际的。 有很多陷阱。

这就是说,如果可以的话,这个variables有相同的名字:

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

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

如果你需要这个数组有不同的名字,你可以这样做:

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

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

恕我直言,这是不值得的麻烦。 如果你必须在Bash中传递数据结构,使用全局variables – 这就是他们的目的。 如果你不想出于某种原因这么做,那么就要考虑位置参数。

你的例子很容易被重写为使用位置参数而不是全局variables:

 use_array () { for idx in "$@"; do echo "$idx" done } create_array () { local array=("a" "b" "c") use_array "${array[@]}" } 

尽pipe如此,这一切都会造成一些不必要的复杂性。 当你把它们看作更像程序的副作用时,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 

如果所有你担心的是污染你的全局名称空间,你也可以使用未设置的内build来删除一个全局variables。 使用原始示例,让my_list成为全局(通过删除本地关键字),并将unset my_list的未unset my_list添加到my_algorithm的末尾以自行清理。

使用Matt McClure开发的技术: http : //notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html

避免全局variables意味着你可以在pipe道中使用这个函数。 这里是一个例子:

 #!/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 

输出是:

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

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

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

考虑到关于embedded式空间的意见,使用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=("ab" "cd" "ef") echo "${my_list[*]}" } 

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

Retornar els难民营咨询SQL一个vector

 function Query() { local _tmp=`echo -n "$*" | mysql 2>> zz.err`; #consulta a la base de dades echo -e "$_tmp"; #retorna múltiples files } 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 

纯粹的bash,基于“declare -p”内build的最小且健壮的解决scheme – 没有疯狂的全局variables

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

  1. 使用'declare -p'转换数组并将输出保存在一个variables中。
    myVar="$( declare -p myArray )"
    declare -p语句的输出可以用来重新创build数组。 例如declare -p myVar的输出可能如下所示:
    declare -a myVar='([0]="1st field" [1]="2nd field" [2]="3rd field")'
  2. 使用echo内置函数将variables传递给一个函数或从那里传回。
    • 为了在回显variables时保存数组字段中的空格,IFS被临时设置为一个控制字符(例如垂直标签)。
    • 只有variables中的声明语句的右边才会被回显 – 这可以通过$ {parameter#word}forms的参数扩展来实现。 至于上面的例子: ${myVar#*=}
  3. 最后,使用eval和“declare -a”构build的数组重新创build传递给它的数组。

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

我尝试了各种实现,并没有保留具有空格元素的数组…因为他们都必须使用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 (ie `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 

[ 注:以下内容因为对我没有任何意义的理由而被拒绝,因为编辑的目的不是为了解决这篇文章的作者!),所以我build议将它作为一个单独的回答。]

Steve Zobell改编的Matt McClure技术的一个简单实现使用了readarray build议的内置bash(自版本> = 4?) readarray创build一个可以在运行时转换为数组的数组的表示forms。 (请注意, readarraymapfile都是相同的代码。)它仍然避免了全局variables(允许在pipe道中使用该函数),并仍然处理令人讨厌的字符。 剪下下面的块并粘贴到bashterminal来创build/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也处理空行。 尝试将以下代码块粘贴到您的bashterminal中:

 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 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. 

即,未处理和处理的命令的输出是相同的。

我最近需要一个类似的function,所以下面是RashaMatt和Steve Zobell提出的一些build议。

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

据我所知,string保持不变,空格被保留。

 #!bin/bash function create-array() { local somearray=("aaa" "bbb ccc" "d" "efgh") 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' 'tsu' )" 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 

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

 $ 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内build一样,您通常不会在pipe道(或子shell)中使用mapfile ,因为分配的数组variables对后续语句不可用(*除非禁用了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.