如何从Bash函数返回一个string值

我想从Bash函数返回一个string。

我将用java编写示例来显示我想要做的事情:

public String getSomeString() { return "tadaa"; } String variable = getSomeString(); 

下面的例子在bash中工作,但有没有更好的方法来做到这一点?

 function getSomeString { echo "tadaa" } VARIABLE=$(getSomeString) 

没有更好的方法我知道。 Bash只知道写入标准输出的状态码(整数)和string。

你可以让这个函数把一个variables作为第一个参数,并用你想返回的string修改这个variables。

 #!/bin/bash set -x function pass_back_a_string() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_string return_var echo $return_var 

打印“foo bar rab oof”。

编辑 :在适当的位置添加引用,以允许string中的空格来解决@Luca Borrione的评论。

编辑 :作为演示,请参阅以下程序。 这是一个通用的解决scheme:它甚至允许你接收一个string到一个局部variables。

 #!/bin/bash set -x function pass_back_a_string() { eval "$1='foo bar rab oof'" } return_var='' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar='' pass_back_a_string lvar echo "lvar='$lvar' locally" } call_a_string_func echo "lvar='$lvar' globally" 

这打印:

 + return_var= + pass_back_a_string return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local lvar= + pass_back_a_string lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally 

编辑 :certificate原始variables的值在函数中可用,正如@Xichen Li在评论中的错误批评。

 #!/bin/bash set -x function pass_back_a_string() { eval "echo in pass_back_a_string, original $1 is \$$1" eval "$1='foo bar rab oof'" } return_var='original return_var' pass_back_a_string return_var echo $return_var function call_a_string_func() { local lvar='original lvar' pass_back_a_string lvar echo "lvar='$lvar' locally" } call_a_string_func echo "lvar='$lvar' globally" 

这给出了输出:

 + return_var='original return_var' + pass_back_a_string return_var + eval 'echo in pass_back_a_string, original return_var is $return_var' ++ echo in pass_back_a_string, original return_var is original return_var in pass_back_a_string, original return_var is original return_var + eval 'return_var='\''foo bar rab oof'\''' ++ return_var='foo bar rab oof' + echo foo bar rab oof foo bar rab oof + call_a_string_func + local 'lvar=original lvar' + pass_back_a_string lvar + eval 'echo in pass_back_a_string, original lvar is $lvar' ++ echo in pass_back_a_string, original lvar is original lvar in pass_back_a_string, original lvar is original lvar + eval 'lvar='\''foo bar rab oof'\''' ++ lvar='foo bar rab oof' + echo 'lvar='\''foo bar rab oof'\'' locally' lvar='foo bar rab oof' locally + echo 'lvar='\'''\'' globally' lvar='' globally 

上面的所有答案都忽略了在bash的man page中所陈述的内容。

  • 在函数内声明的所有variables都将与调用环境共享。
  • 所有声明为本地的variables将不被共享。

示例代码

 #!/bin/bash f() { echo function starts local WillNotExists="It still does!" DoesNotExists="It still does!" echo function ends } echo $DoesNotExists #Should print empty line echo $WillNotExists #Should print empty line f #Call the function echo $DoesNotExists #Should print It still does! echo $WillNotExists #Should print empty line 

并输出

 $ sh -x ./x.sh + echo + echo + f + echo function starts function starts + local 'WillNotExists=It still does!' + DoesNotExists='It still does!' + echo function ends function ends + echo It still 'does!' It still does! + echo 

同样在pdksh和ksh下,这个脚本也是一样的!

像上面的bstpierre一样,我使用并build议使用明确的命名输出variables:

 function some_func() # OUTVAR ARG1 { local _outvar=$1 local _result # Use some naming convention to avoid OUTVARs to clash ... some processing .... eval $_outvar=\$_result # Instead of just =$_result } 

注意使用引用$。 这将避免将$result内容解释为shell特殊字符。 我发现这比捕获回声的result=$(some_func "arg1")成语了一个数量级 。 速度差异似乎更加显着使用bash的MSYS从function调用捕获的标准输出几乎是灾难性的。

可以发送一个局部variables,因为局部variables在bash中是dynamic的:

 function another_func() # ARG { local result some_func result "$1" echo result is $result } 

Bash从4.3版本开始,2014年2月(?),明确支持引用variables或名称引用(namerefs),超越“eval”,具有相同的有益性能和间接效果,可能在脚本中更清晰,以“忘记”评估“,并解决这个错误”:

 declare [-aAfFgilnrtux] [-p] [name[=value] ...] typeset [-aAfFgilnrtux] [-p] [name[=value] ...] Declare variables and/or give them attributes ... -n Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references and assignments to name, except for⋅ changing the -n attribute itself, are performed on the variable referenced by name's value. The -n attribute cannot be applied to array variables. ... When used in a function, declare and typeset make each name local, as with the local command, unless the -g option is supplied... 

并且:

参数

可以使用-n选项为declare或local内置命令(请参阅下面的declare和local的描述)创buildnameref或对另一个variables的引用,为variables分配nameref属性。 这允许variables间接操作。 每当namerefvariables被引用或赋值时,操作实际上都是在由namerefvariables的值指定的variables上执行的。 在shell函数中通常使用nameref来引用一个variables,该variables的名称作为parameter passing给函数。 例如,如果一个variables名作为第一个parameter passing给一个shell函数,则运行

  declare -n ref=$1 

在函数内部创build一个namerefvariablesref,其值是作为第一个parameter passing的variables名。 ref的引用和赋值被视为引用和赋值给variables的名字被传递为$ $ 1。 如果for循环中的控制variables具有nameref属性,则单词列表可以是一个shellvariables列表,并且在执行循环时,将为列表中的每个单词build立一个名称引用。 数组variables不能被赋予-n属性。 但是,namerefvariables可以引用数组variables和下标数组variables。 Namerefs可以使用未设置内build的-n选项取消设定。 否则,如果使用namerefvariables的名称作为参数执行unset,则由namerefvariables引用的variables将被取消设置。

例如( 编辑2 :(感谢罗恩)namespaced(前缀)的函数内部variables的名称,以最大限度地减less外部variables冲突,最终应该正确回答,在Karsten的评论中提出的问题):

 # $1 : string; your variable to contain the return value function return_a_string () { declare -n ret=$1 local MYLIB_return_a_string_message="The date is " MYLIB_return_a_string_message+=$(date) ret=$MYLIB_return_a_string_message } 

并testing这个例子:

 $ return_a_string result; echo $result The date is 20160817 

请注意,bash中的“声明”内置函数在函数中使用时,默认声明variables为“local”,“-n”也可以与“local”一起使用。

我更喜欢将“重要声明”variables与“无聊的本地”variables区分开来,所以使用“声明”和“本地”作为文档。

编辑1 – (对Karsten下面的评论的回应) – 我不能再添加评论,但Karsten的评论让我想到了,所以我做了以下testing哪些工作优良,AFAICT – 卡尔斯滕如果你读了这个,请提供一个确切的设置从命令行执行testing步骤,显示您认为存在的问题,因为以下步骤工作得很好:

 $ return_a_string ret; echo $ret The date is 20170104 

(我刚才把这个函数粘贴到了bash中,就像你看到的那样,结果很好。)

您也可以捕获function输出:

 #!/bin/bash function getSomeString() { echo "tadaa!" } return_var=$(getSomeString) echo $return_var # Alternative syntax: return_var=`getSomeString` echo $return_var 

看起来很奇怪,但比使用全局variables恕我直言。 传递参数像往常一样工作,只是把它们放在括号或反引号内。

如前所述,从函数返回string的“正确”方式是使用命令replace。 如果函数也需要输出到控制台(如@Mani所述),则在函数的开头创build一个临时fd并redirect到控制台。 在返回string之前closures临时fd。

 #!/bin/bash # file: func_return_test.sh returnString() { exec 3>&1 >/dev/tty local s=$1 s=${s:="some default string"} echo "writing directly to console" exec 3>&- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]" 

执行没有参数的脚本产生…

 # ./func_return_test.sh writing directly to console my_string: [some default string] 

希望这有助于人们

-Andy

你可以使用一个全局variables:

 declare globalvar='some string' string () { eval "$1='some other string'" } # ---------- end of function string ---------- string globalvar echo "'${globalvar}'" 

这给了

 'some other string' 

最直接和最健壮的解决scheme是使用命令replace,正如其他人写道:

 assign() { local x x="Test" echo "$x" } x=$(assign) # This assigns string "Test" to x 

不利的一面是性能,因为这需要一个单独的过程。

本主题中提出的另一种技术,即传递一个variables的名称作为参数,具有副作用,我不会推荐它的基本forms。 问题是,你可能需要函数中的一些variables来计算返回值,并且可能发生意图存储返回值的variables的名称会干扰其中的一个:

 assign() { local x x="Test" eval "$1=\$x" } assign y # This assigns string "Test" to y, as expected assign x # This will NOT assign anything to x in this scope # because the name "x" is declared as local inside the function 

当然,您可能不会将函数的内部variables声明为本地variables,但您应该始终这样做,否则,如果有一个名称相同的variables,则可能会意外地覆盖父范围中的无关variables。

一种可能的解决方法是将所传递的variables显式声明为全局variables:

 assign() { local x eval declare -g $1 x="Test" eval "$1=\$x" } 

如果名称“x”作为parameter passing,函数体的第二行将覆盖以前的本地声明。 但是名称本身可能仍然会受到干扰,所以如果您打算在写入返回值之前使用之前存储在传递variables中的值,请注意您必须在最初将其复制到另一个局部variables中; 否则结果将是不可预知的! 此外,这只适用于最新版本的BASH,即4.2。 更便携的代码可能会使用显式的条件结构,具有相同的效果:

 assign() { if [[ $1 != x ]]; then local x fi x="Test" eval "$1=\$x" } 

也许最优雅的解决scheme只是为函数返回值保留一个全局名称,并在您编写的每个函数中一致地使用它。

为了说明我对Andy的回答的评论,使用额外的文件描述符操作来避免使用/dev/tty

 #!/bin/bash exec 3>&1 returnString() { exec 4>&1 >&3 local s=$1 s=${s:="some default string"} echo "writing to stdout" echo "writing to stderr" >&2 exec >&4- echo "$s" } my_string=$(returnString "$*") echo "my_string: [$my_string]" 

尽pipe如此,仍然令人讨厌。

你有它的方式是唯一的方法来做到这一点,而不会打破范围。 Bash没有返回types的概念,只是退出代码和文件描述符(stdin / out / err等)

解决维基Ronnen的头,考虑下面的代码:

 function use_global { eval "$1='changed using a global var'" } function capture_output { echo "always changed" } function test_inside_a_func { local _myvar='local starting value' echo "3. $_myvar" use_global '_myvar' echo "4. $_myvar" _myvar=$( capture_output ) echo "5. $_myvar" } function only_difference { local _myvar='local starting value' echo "7. $_myvar" local use_global '_myvar' echo "8. $_myvar" local _myvar=$( capture_output ) echo "9. $_myvar" } declare myvar='global starting value' echo "0. $myvar" use_global 'myvar' echo "1. $myvar" myvar=$( capture_output ) echo "2. $myvar" test_inside_a_func echo "6. $_myvar" # this was local inside the above function only_difference 

会给

 0. global starting value 1. changed using a global var 2. always changed 3. local starting value 4. changed using a global var 5. always changed 6. 7. local starting value 8. local starting value 9. always changed 

也许正常情况下是使用test_inside_a_func函数中使用的语法,因此在大多数情况下,您可以使用这两种方法,但捕获输出是更安全的方法总是在任何情况下工作,模仿函数的返回值可以在其他语言中find,正如Vicky Ronnen正确指出的那样。

我想,选项已经被列举了。 select一个可能会归结为您的特定应用程序的最佳风格问题,并在这一方面,我想提供一个我觉得有用的特定风格。 在bash中,variables和函数不在同一个命名空间中。 所以,如果我严格地使用它,那么将与名称相同的variables作为函数的值处理是我发现的一个约定,可以最大限度地减less名称冲突,并提高可读性。 现实生活中的一个例子:

 UnGetChar= function GetChar() { # assume failure GetChar= # if someone previously "ungot" a char if ! [ -z "$UnGetChar" ]; then GetChar="$UnGetChar" UnGetChar= return 0 # success # else, if not at EOF elif IFS= read -N1 GetChar ; then return 0 # success else return 1 # EOF fi } function UnGetChar(){ UnGetChar="$1" } 

而且,使用这样的function的一个例子:

 function GetToken() { # assume failure GetToken= # if at end of file if ! GetChar; then return 1 # EOF # if start of comment elif [[ "$GetChar" == "#" ]]; then while [[ "$GetChar" != $'\n' ]]; do GetToken+="$GetChar" GetChar done UnGetChar "$GetChar" # if start of quoted string elif [ "$GetChar" == '"' ]; then # ... et cetera 

正如你所看到的那样,当你需要时,你可以使用退货状态,或者如果你不需要,可以忽略。 “返回”variables同样可以被使用或忽略,但是当然只有函数被调用之后。

当然,这只是一个惯例。 你可以在返回之前自由地设置相关的值(因此我的约定始终在函数的开始处置空),或者通过再次调用函数(可能间接)来践踏它的值。 不过,如果我发现自己大量使用bash函数,那么这是一个非常有用的约定。

与此相反,这是一个标志,例如“转向perl”,我的理念是,公约对于pipe理任何语言的复杂性总是非常重要的。

你可以echo一个string,但通过pipe道( | )函数来捕获它。

您可以使用expr ,尽pipeShellCheck将此用法报告为不build议使用。

在我的程序中,按照约定,这是预先存在的$REPLYvariables的用途, read用于确切的目的。

 function getSomeString { REPLY="tadaa" } getSomeString echo $REPLY 

echo

 tadaa 

但是为了避免冲突,任何其他的全局variables都可以做到。

 declare result function getSomeString { result="tadaa" } getSomeString echo $result 

如果这还不够,我推荐Markarian451的解决scheme。

他们关键问题的任何“命名输出variables”scheme的调用者可以传递variables名称(无论是使用evaldeclare -n )是无意的别名,即名称冲突:从封装的angular度来看,它是可怕的,不能在函数中添加或重命名局部variables,而不先检查函数的所有调用者,以确保他们不想传递与输出参数相同的名称。 (或者在另一个方向上,我不想读取函数的来源,只是为了确保我打算使用的输出参数不是该函数中的本地函数)。

唯一的方法就是使用一个像REPLY这样的专用输出variables(如Evi1M4chine所build议的那样 ),或者像Ron Burk所build议的那样。

然而,函数可能会在内部使用一个固定的输出variables,然后在顶部添加一些糖来隐藏来自调用者的这个事实 ,就像我在下面的例子中使用call函数所做的那样。 考虑这个概念的certificate,但关键点是

  • 该函数总是将返回值分配给REPLY ,并且也可以像往常一样返回退出代码
  • 从调用者的angular度来看,可以将返回值赋给任何包含REPLYvariables(本地或全局variables)(请参阅wrapper示例)。 该函数的退出代码被传递,因此在例如ifwhile或类似的构造中使用它们就像预期的那样工作。
  • 函数调用在语法上仍然是一个简单的语句。

这个原因是因为call函数本身没有本地化,除REPLY没有其他variables,避免了名称冲突的可能性。 在调用者定义的输出variables名被赋值的地方,我们实际上在调用者的范围内(技术上在call函数的相同范围内),而不是被call函数的范围。

 #!/bin/bash function call() { # var=func [args ...] REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?" } function greet() { case "$1" in us) REPLY="hello";; nz) REPLY="kia ora";; *) return 123;; esac } function wrapper() { call REPLY=greet "$@" } function main() { local abcd call a=greet us echo "a='$a' ($?)" call b=greet nz echo "b='$b' ($?)" call c=greet de echo "c='$c' ($?)" call d=wrapper us echo "d='$d' ($?)" } main 

输出:

 a='hello' (0) b='kia ora' (0) c='' (123) d='hello' (0) 

bash模式返回标量数组值对象:

定义

 url_parse() { # parse 'url' into: 'url_host', 'url_port', ... local "$@" # inject caller 'url' argument in local scope local url_host="..." url_path="..." # calculate 'url_*' components declare -p ${!url_*} # return only 'url_*' object fields to the caller } 

调用

 main() { # invoke url parser and inject 'url_*' results in local scope eval "$(url_parse url=http://host/path)" # parse 'url' echo "host=$url_host path=$url_path" # use 'url_*' components } 
 agt@agtsoft:~/temp$ cat ./fc #!/bin/sh fcall='function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall' function f1 { res=$[($1+$2)*2]; } function f2 { local a; eval ${fcall//fname/f1} a 2 3; echo f2:$a; } a=3; f2; echo after:a=$a, res=$res agt@agtsoft:~/temp$ ./fc f2:10 after:a=3, res=