我有一个创建数组的函数,我想将该数组返回给调用者:
create_array() {
local my_list=("a", "b", "c")
echo "${my_list[@]}"
}
my_algorithm() {
local result=$(create_array)
}
通过这种方法,我只能得到一个扩展过的字符串。如何在不使用任何全局变量的情况下“返回”我的列表(my_list)?
我有一个创建数组的函数,我想将该数组返回给调用者:
create_array() {
local my_list=("a", "b", "c")
echo "${my_list[@]}"
}
my_algorithm() {
local result=$(create_array)
}
通过这种方法,我只能得到一个扩展过的字符串。如何在不使用任何全局变量的情况下“返回”我的列表(my_list)?
在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的说明:
使用declare或local内置命令(参见Bash内置命令)并使用-n选项将变量赋予nameref属性,可以创建一个nameref,或引用另一个变量。这允许间接地操作变量。每当引用、赋值、取消设置nameref变量或更改其属性(除了使用或更改nameref属性本身之外),操作实际上是在nameref变量的值所指定的变量上执行的。通常在shell函数中使用nameref来引用作为参数传递给函数的变量的名称。例如,如果将变量名作为其第一个参数传递给shell函数,则在函数内部运行
declare -n ref=$1
会创建一个nameref变量ref,其值为作为第一个参数传递的变量名称。对ref的引用和赋值以及其属性的更改被视为对传递的$1名称的变量的引用、赋值和属性修改。
arr
还是 my_array
?两者都只在各自的函数中是局部变量,因此在函数外不可见。 - codeforester全局变量有什么问题?
返回数组实际上不太实用,存在许多陷阱。
话虽如此,如果变量有相同的名称,则以下技术是可行的:
$ 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
f () { local a=($(g)); declare -p a; }; g () { local a=(a 'b c' d); echo "${a[@]}"; }; f
输出结果为"declare -a a='([0]="a" [1]="b" [2]="c" [3]="d")'"。你会注意到,现在有4个元素而不是3个。 - Dennis Williamson${array[@]}
语法确实会适当地引用数组项,但是 echo
不会打印未转义的引号。因此,任何使用 echo
的解决方案只有在没有数组项包含空格时才能正常工作。我抽象出了Dennis的示例,并使其更加健壮,以获得一个实用的、可重用的实现。 - Stephen M. Harrisuse_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内置命令在使用完全局变量后删除它。使用原始示例中的local关键字来让my_list成为全局变量,并在my_algorithm结尾处添加unset my_list
以清理变量。create_array
)可以“调用”消费者(use_array
)时才能起作用,反过来则不行。 - musiphil你的原始解决方案并不是很遥远。你有几个问题:你使用逗号作为分隔符,并且没有将返回的项捕获到列表中。试试这个:
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[*]}"
}
[@]
,create_array
将会返回一个列表;如果我使用[*]
,它将会返回一个单独的字符串(除了0-255之间的数字外,它不能返回任何东西)。在my_algorithm
中,数组是通过将函数调用括在括号中创建的。因此,在my_algorithm
中,变量result
是一个数组。我明白嵌入值中的空格总是会引起问题。 - cdarkearrayIFS=$'\001'
。 - user4401178使用由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[@]}
进行索引。请参考此链接了解原因。反引号已经过时,难以阅读和嵌套。请使用$()
代替。如果makeJunk
中的字符串包含换行符,则您的函数将无法正常工作。 - Dennis Williamson这种方法包括以下三个步骤:
myVar="$( declare -p myArray )"
declare -p
语句的输出可以用来重新创建数组。例如,declare -p myVar
的输出可能如下所示:declare -a myVar='([0]="1st field" [1]="2nd field" [2]="3rd field")'
${myVar#*=}
示例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")'
#!/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")'
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
当委托创建数组时,我更喜欢获得返回值而不是使用全局变量。我偏爱的原因有几个,其中之一是避免可能干扰预先存在的值,并避免在以后访问时留下无效的值。虽然有解决这些问题的方法,但最简单的方法是在代码完成后使变量超出作用域。
我的解决方案确保数组在需要时可用,并在函数返回时丢弃它,并保留具有相同名称的全局变量。
#!/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[@]}"
declare
创建的,因此它仅在 request_array 中有效,并且在 request_array 返回时超出范围。最近我需要类似的功能,下面是 RashaMatt 和 Steve Zobell 的建议混合而成。
据我所见,字符串被保持完整,空格被保留。
#!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
[注意: 以下内容因为我无法理解的原因而被拒绝作为 此答案 的编辑(因为该编辑并非旨在回应帖子的作者!),因此我接受了这个建议,将其作为一个单独的答案。]
Steve Zobell's adaptation of Matt McClure's technique 的更简单实现使用bash内置函数(自版本==4以来)readarray
,正如RastaMatt所建议的那样,创建一个可以在运行时转换为数组的数组表示形式。(请注意,readarray
和mapfile
都命名为相同的代码。)它仍然避免全局变量(允许在管道中使用该函数),并且仍然处理不良字符。
如果你想查看一些更加完整的例子(例如更好的模块化),但仍然是玩具级别的,请参见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!
result=( $(create_array) )
可以添加数组项,但它无法处理包含空格的项;这才是真正的挑战! - U. Windl