在 shell 脚本中返回多个值的习惯用法
Posted
技术标签:
【中文标题】在 shell 脚本中返回多个值的习惯用法【英文标题】:idioms for returning multiple values in shell scripting 【发布时间】:2011-01-30 03:07:34 【问题描述】:是否有从脚本中的 bash 函数返回多个值的习惯用法?
http://tldp.org/LDP/abs/html/assortedtips.html 描述了如何回显多个值并处理结果(例如,示例 35-17),但如果某些返回值是包含空格的字符串,这将变得很棘手。
一种更结构化的返回方式是分配给全局变量,例如
foo ()
FOO_RV1="bob"
FOO_RV2="bill"
foo
echo "foo returned $FOO_RV1 and $FOO_RV2"
我意识到,如果我需要在 shell 脚本中重新进入,我可能做错了,但我仍然感到非常不舒服,只是为了保存返回值而抛出全局变量。
有没有更好的方法?我更喜欢可移植性,但如果我必须指定 #!/bin/bash
,这可能不是真正的限制。
【问题讨论】:
【参考方案1】:Shell 脚本函数只能返回执行的最后一个命令的退出状态或由 return 语句明确指定的该函数的退出状态。
返回一些字符串的一种方式可能是:
function fun()
echo "a+b"
var=`fun` # Invoke the function in a new child shell and capture the results
echo $var # use the stored result
这可能会减轻您的不适,尽管它会增加创建新外壳的开销,因此会稍微慢一些。
【讨论】:
【参考方案2】:你可以使用关联数组,例如 bash 4
declare -A ARR
function foo()
...
ARR["foo_return_value_1"]="VAR1"
ARR["foo_return_value_2"]="VAR2"
您可以将它们组合为字符串。
function foo()
...
echo "$var1|$var2|$var3"
那么当你需要使用这些返回值时,
ret="$(foo)"
IFS="|"
set -- $ret
echo "var1 one is: $1"
echo "var2 one is: $2"
echo "var3 one is: $3"
【讨论】:
就在昨天,我阅读了另一位用户的评论,与另一个问题的类似答案有关。她指出像set -- $ret
这样的东西是危险的,因为$ret
在这样的结构中经历了文件名扩展/模式匹配,至少在bash 中(我不知道其他shell)。如果$ret
恰好包含相应的通配符,并且如果有匹配的路径或文件名,这可能会导致错误的结果和非常讨厌且难以发现的问题。【参考方案3】:
尽管我很喜欢 shell,但可能是这样的情况,一旦你到处乱扔任意结构化数据,Unix bourne/posix shell 就不是正确的选择。
如果字段中没有出现的字符,则用其中之一分隔。经典的例子是/etc/passwd
、/etc/group
和其他各种使用冒号作为字段分隔符的文件。
如果使用可以处理字符串中的 NUL 字符的 shell,则加入 NUL 并在其上分离(通过 $IFS 或其他方式)可以很好地工作。但是包括 bash 在内的几种常见的 shell 会在 NUL 上中断。测试将是我的旧 .sig:
foo=$'a\0b'; [ $#foo -eq 3 ] && echo "$0 rocks"
即使这对你有用,你也只是遇到了一个警告信号,表明是时候改用一种更结构化的语言(Python、Perl、Ruby、Lua、javascript ......选择你喜欢的毒药)。您的代码可能会变得难以维护;即使可以,也有一小部分人能够很好地理解它来维护它。
【讨论】:
【参考方案4】:我会选择我suggested here 的解决方案,但改用数组变量。旧的 bash:es 不支持关联数组。 例如。,
function some_func() # ARRVAR args...
local _retvar=$1 # I use underscore to avoid ***es with return variable names
local -a _out
# ... some processing ... (_out[2]=xxx etc.)
eval $_retvar='("$_out[@]")'
调用站点:
function caller()
local -a tuple_ret # Do not use leading '_' here.
# ...
some_func tuple_ret "arg1"
printf " %s\n" "$tuple_ret[@]" # Print tuple members on separate lines
【讨论】:
【参考方案5】:这个问题是 5 年前发布的,但我有一些有趣的答案要发布。我刚开始学习bash,我也遇到了和你一样的问题。我认为这个技巧可能会有所帮助:
#!/bin/sh
foo=""
bar=""
my_func()
echo 'foo="a"; bar="b"'
eval $(my_func)
echo $foo $bar
# result: a b
当子进程无法将值发送回其父进程时,此技巧对于解决问题也很有用。
【讨论】:
虽然不完美,但我喜欢它普遍适用。【参考方案6】:更高版本的 Bash 支持 nameref。使用declare -n var_name
为var_name
赋予nameref 属性。 nameref 使您的函数能够“按引用传递”,这在 C++ 函数中通常用于返回多个值。根据 Bash 手册页:
可以使用 -n 选项将 nameref 属性分配给 declare 或 local 内置变量创建 nameref 的命令,或对另一个变量的引用。这允许间接地操纵变量。每当 nameref 变量被引用或赋值时,操作实际上是在 nameref 变量的值指定的变量上执行的。 nameref 通常在 shell 函数中用于引用变量,该变量的名称作为参数传递给函数。
以下是一些交互式命令行示例。
示例 1:
$ unset xx yy
$ xx=16
$ yy=xx
$ echo "[$xx] [$yy]"
[16] [xx]
$ declare -n yy
$ echo "[$xx] [$yy]"
[16] [16]
$ xx=80
$ echo "[$xx] [$yy]"
[80] [80]
$ yy=2016
$ echo "[$xx] [$yy]"
[2016] [2016]
$ declare +n yy # Use -n to add and +n to remove nameref attribute.
$ echo "[$xx] [$yy]"
[2016] [xx]
示例 2:
$ func()
>
> local arg1="$1" arg2="$2"
> local -n arg3ref="$3" arg4ref="$4"
>
> echo ''
> echo 'Local variables:'
> echo " arg1='$arg1'"
> echo " arg2='$arg2'"
> echo " arg3ref='$arg3ref'"
> echo " arg4ref='$arg4ref'"
> echo ''
>
> arg1='1st value of local assignment'
> arg2='2st value of local assignment'
> arg3ref='1st return value'
> arg4ref='2nd return value'
>
$
$ unset foo bar baz qux
$
$ foo='value of foo'
$ bar='value of bar'
$ baz='value of baz'
$ qux='value of qux'
$
$ func foo bar baz qux
Local variables:
arg1='foo'
arg2='bar'
arg3ref='value of baz'
arg4ref='value of qux'
$
$
> echo ''
> echo '2 values are returned after the function call:'
> echo " foo='$foo'"
> echo " bar='$bar'"
> echo " baz='$baz'"
> echo " qux='$qux'"
>
2 values are returned after the function call:
foo='value of foo'
bar='value of bar'
baz='1st return value'
qux='2nd return value'
【讨论】:
【参考方案7】:为了不支持 nameref 的 Bash 版本(在 Bash 4.3-alpha 中引入),我可以定义帮助函数,其中将返回值分配给给定的变量。这有点像使用eval
进行相同类型的变量赋值。
示例 1
## Add two complex numbers and returns it.
## re: real part, im: imaginary part.
##
## Helper function named by the 5th positional parameter
## have to have been defined before the function is called.
complexAdd()
local re1="$1" im1="$2" re2="$3" im2="$4" fnName="$5" sumRe sumIm
sumRe=$(($re1 + $re2))
sumIm=$(($im1 + $im2))
## Call the function and return 2 values.
"$fnName" "$sumRe" "$sumIm"
main()
local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
## Define the function to receive mutiple return values
## before calling complexAdd().
retValAssign() bazRe="$1"; bazIm="$2";
## Call comlexAdd() for the first time.
complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm" 'retValAssign'
## Redefine the function to receive mutiple return values.
retValAssign() quxRe="$1"; quxIm="$2";
## Call comlexAdd() for the second time.
complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm" 'retValAssign'
echo "foo = $fooRe + $fooIm i"
echo "bar = $barRe + $barIm i"
echo "baz = foo + bar = $bazRe + $bazIm i"
echo "qux = bar + baz = $quxRe + $quxIm i"
main
示例 2
## Add two complex numbers and returns it.
## re: real part, im: imaginary part.
##
## Helper functions
## getRetRe(), getRetIm(), setRetRe() and setRetIm()
## have to have been defined before the function is called.
complexAdd()
local re1="$1" im1="$2" re2="$3" im2="$4"
setRetRe "$re1"
setRetRe $(($(getRetRe) + $re2))
setRetIm $(($im1 + $im2))
main()
local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
## Define getter and setter functions before calling complexAdd().
getRetRe() echo "$bazRe";
getRetIm() echo "$bazIm";
setRetRe() bazRe="$1";
setRetIm() bazIm="$1";
## Call comlexAdd() for the first time.
complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm"
## Redefine getter and setter functions.
getRetRe() echo "$quxRe";
getRetIm() echo "$quxIm";
setRetRe() quxRe="$1";
setRetIm() quxIm="$1";
## Call comlexAdd() for the second time.
complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm"
echo "foo = $fooRe + $fooIm i"
echo "bar = $barRe + $barIm i"
echo "baz = foo + bar = $bazRe + $bazIm i"
echo "qux = bar + baz = $quxRe + $quxIm i"
main
【讨论】:
【参考方案8】:在你的值从不包含空格的特殊情况下,这个read
技巧可以是一个简单的解决方案:
get_vars ()
#...
echo "value1" "value2"
read var1 var2 < <(get_vars)
echo "var1='$var1', var2='$var2'"
当然,只要其中一个值中有空格,它就会中断。您可以修改 IFS
并在函数的 echo
中使用特殊分隔符,但结果并不比其他建议的解决方案简单。
【讨论】:
【参考方案9】:另一种方式:
function get_tuple()
echo -e "Value1\nValue2"
IFS=$'\n' read -d '' -ra VALUES < <(get_tuple)
echo "$VALUES[0]" # Value1
echo "$VALUES[1]" # Value2
【讨论】:
【参考方案10】:我是 bash 新手,但发现此代码有帮助。
function return_multiple_values()
eval "$1='What is your name'"
eval "$2='my name is: BASH'"
return_var=''
res2=''
return_multiple_values return_var res2
echo $return_var
echo $res2
【讨论】:
以上是关于在 shell 脚本中返回多个值的习惯用法的主要内容,如果未能解决你的问题,请参考以下文章
Shell脚本三剑客(awksortuniq)内含多个小Demo