How to return a string value from a Bash function
我想从Bash函数返回一个字符串。
我将在java中编写示例以显示我想要做的事情:
1 2 3 4 5 | public String getSomeString() { return"tadaa"; } String variable = getSomeString(); |
以下示例适用于bash,但有更好的方法吗?
1 2 3 4 5 | function getSomeString { echo"tadaa" } VARIABLE=$(getSomeString) |
我知道没有更好的方法。 Bash只知道写入stdout的状态代码(整数)和字符串。
您可以让函数将变量作为第一个arg,并使用要返回的字符串修改变量。
1 2 3 4 5 6 7 8 9 | #!/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"。
编辑:在适当的地方添加引用,以允许字符串中的空格来解决@Luca Borrione的评论。
编辑:作为演示,请参阅以下程序。这是一个通用解决方案:它甚至允许您将字符串接收到局部变量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #!/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" |
这打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | + 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 |
编辑:证明原始变量的值在函数中可用,正如@Xichen Li在评论中错误批评的那样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!/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" |
这给出了输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | + 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手册页中所述的内容。
- 函数内声明的所有变量都将与调用环境共享。
- 声明为local的所有变量都不会被共享。
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/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 |
并输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ 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下这个脚本也是一样的!
Bash,自版本4.3,2014年2月(?),明确支持引用变量或名称引用(namerefs),超出"eval",具有相同的有益性能和间接效果,并且在脚本中可能更清晰,也更难"忘记'eval'并且必须修复此错误":
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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... |
并且:
PARAMETERS
A variable can be assigned the nameref attribute using the -n option to the
declare or local builtin commands (see the descriptions of declare and local
below) to create a nameref, or a reference to another variable. This allows
variables to be manipulated indirectly. Whenever the nameref variable is?
referenced or assigned to, the operation is actually performed on the variable
specified by the nameref variable's value. A nameref is commonly used within
shell functions to refer to a variable whose name is passed as an argument to?
the function. For instance, if a variable name is passed to a shell function
as its first argument, running
1 declare -n ref=$1inside the function creates a nameref variable ref whose value is the variable
name passed as the first argument. References and assignments to ref are
treated as references and assignments to the variable whose name was passed as?
$1. If the control variable in a for loop has the nameref attribute, the list
of words can be a list of shell variables, and a name reference will be?
established for each word in the list, in turn, when the loop is executed.
Array variables cannot be given the -n attribute. However, nameref variables
can reference array variables and subscripted array variables. Namerefs can be?
unset using the -n option to the unset builtin. Otherwise, if unset is executed
with the name of a nameref variable as an argument, the variable referenced by?
the nameref variable will be unset.
例如(编辑2 :(谢谢Ron)命名空间(前缀)函数内部变量名称,以最小化外部变量冲突,最终应该正确回答,Karsten在评论中提出的问题):
1 2 3 4 5 6 7 | # $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 } |
并测试此示例:
1 2 | $ return_a_string result; echo $result The date is 20160817 |
请注意,bash"declare"builtin在函数中使用时,默认使声明变量为"local"," - n"也可以与"local"一起使用。
我更喜欢将"重要声明"变量与"无聊的本地"变量区分开来,因此以这种方式使用"declare"和"local"作为文档。
编辑1 - (回应Karsten的评论) - 我不能再在下面添加评论了,但是Karsten的评论让我思考,所以我做了以下测试,哪个工作很精细,AFAICT - Karsten如果你读到这个,请提供一套确切的设置来自命令行的测试步骤,显示您认为存在的问题,因为以下步骤可以正常工作:
1 2 | $ return_a_string ret; echo $ret The date is 20170104 |
(在将上述函数粘贴到bash术语之后,我刚刚运行了这个 - 正如您所看到的,结果运行得很好。)
像上面的bstpierre一样,我使用并建议使用显式命名输出变量:
1 2 3 4 5 6 7 | 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 } |
注意使用引用$。这将避免将
发送局部变量是可以的,因为本地变量在bash中是动态范围的:
1 2 3 4 5 6 | function another_func() # ARG { local result some_func result"$1" echo result is $result } |
您还可以捕获函数输出:
1 2 3 4 5 6 7 8 9 10 | #!/bin/bash function getSomeString() { echo"tadaa!" } return_var=$(getSomeString) echo $return_var # Alternative syntax: return_var=`getSomeString` echo $return_var |
看起来很奇怪,但比使用全局变量恕我直言更好。传递参数像往常一样工作,只需将它们放在大括号或反引号中。
如前所述,从函数返回字符串的"正确"方法是使用命令替换。如果函数还需要输出到控制台(如上面提到的@Mani),请在函数的开头创建一个临时fd并重定向到控制台。在返回字符串之前关闭临时fd。
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/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]" |
执行没有参数的脚本会产生......
1 2 3 | # ./func_return_test.sh writing directly to console my_string: [some default string] |
希望这有助于人们
-Andy
其他人写道:最直接,最强大的解决方案是使用命令替换。
1 2 3 4 5 6 7 8 | assign() { local x x="Test" echo"$x" } x=$(assign) # This assigns string"Test" to x |
缺点是性能,因为这需要一个单独的过程。
本主题中提出的另一种技术,即传递一个变量的名称作为参数赋值,有副作用,我不推荐它的基本形式。问题是您可能需要函数中的一些变量来计算返回值,并且可能会发生用于存储返回值的变量的名称将干扰其中一个:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
当然,您可能不会将函数的内部变量声明为本地变量,但实际上您应该始终这样做,否则,如果存在具有相同名称的变量,您可能会意外地覆盖父作用域中的不相关变量。 。
一种可能的解决方法是将传递的变量显式声明为全局变量:
1 2 3 4 5 6 7 | assign() { local x eval declare -g $1 x="Test" eval"$1=\$x" } |
如果名称"x"作为参数传递,则函数体的第二行将覆盖先前的本地声明。但是名称本身可能仍会干扰,因此如果您打算在将返回值写入之前使用先前存储在传递变量中的值,请注意必须在最开始时将其复制到另一个局部变量中;否则结果将无法预测!
此外,这只适用于最新版本的BASH,即4.2。更多可移植代码可能会使用具有相同效果的显式条件结构:
1 2 3 4 5 6 7 8 | assign() { if [[ $1 != x ]]; then local x fi x="Test" eval"$1=\$x" } |
也许最优雅的解决方案就是为函数返回值保留一个全局名称
在您编写的每个函数中始终使用它。
您可以使用全局变量:
1 2 3 4 5 6 7 8 9 10 | declare globalvar='some string' string () { eval "$1='some other string'" } # ---------- end of function string ---------- string globalvar echo"'${globalvar}'" |
这给了
1 | 'some other string' |
为了说明我对Andy的答案的评论,使用额外的文件描述符操作来避免使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #!/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]" |
但仍然很讨厌。
考虑到以下代码,请向Vicky Ronnen致敬:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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 |
会给
1 2 3 4 5 6 7 8 9 10 | 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 |
也许正常的情况是使用
您拥有它的方式是在不破坏范围的情况下实现此目的的唯一方法。 Bash没有返回类型的概念,只有退出代码和文件描述符(stdin / out / err等)
我认为,所有选项都已列举。选择一个可能归结为特定应用的最佳风格问题,在这种情况下,我想提供一种我发现有用的特定风格。在bash中,变量和函数不在同一名称空间中。因此,处理与函数值相同名称的变量是一种惯例,我发现如果我严格应用它,可以最大限度地减少名称冲突并增强可读性。现实生活中的一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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" } |
并且,使用这些功能的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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" != $' ' ]]; do GetToken+="$GetChar" GetChar done UnGetChar"$GetChar" # if start of quoted string elif ["$GetChar" == '"' ]; then # ... et cetera |
如您所见,返回状态可供您在需要时使用,如果不需要则可忽略。同样可以使用或忽略"返回"变量,但当然只有在调用函数之后。
当然,这只是一个惯例。您可以自由地在返回之前设置关联值(因此我的约定总是在函数的开头将其置零)或者通过再次调用函数(可能是间接的)来践踏其值。不过,如果我发现自己大量使用bash函数,这是一个非常有用的约定。
与情绪相反,这是一个标志,例如"转向perl",我的理念是,约定对于管理任何语言的复杂性始终是重要的。
它们是任何"命名输出变量"方案的关键问题,其中调用者可以传入变量名称(无论是使用
唯一的方法是使用单个专用输出变量,如
但是,函数可以在内部使用固定的输出变量,然后在顶部添加一些糖来隐藏调用者的这个事实,就像我在下面的例子中使用
-
该函数始终将返回值分配给
REPLY ,并且还可以像往常一样返回退出代码 -
从调用者的角度来看,返回值可以分配给任何变量(本地或全局),包括
REPLY (参见wrapper 示例)。函数的退出代码被传递,因此在例如它们中使用它们。if 或while 或类似结构按预期工作。 - 从语法上讲,函数调用仍然是一个简单的语句。
这样做的原因是因为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/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 a b c d 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 |
输出:
1 2 3 4 | a='hello' (0) b='kia ora' (0) c='' (123) d='hello' (0) |
bash模式返回标量和数组值对象:
定义
1 2 3 4 5 | 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 } |
调用
1 2 3 4 | 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 } |
你可以
您可以使用
在我的程序中,按照惯例,这是预先存在的
1 2 3 4 5 6 | function getSomeString { REPLY="tadaa" } getSomeString echo $REPLY |
这个
1 | tadaa |
但是为了避免冲突,任何其他全局变量都可以。
1 2 3 4 5 6 7 8 | declare result function getSomeString { result="tadaa" } getSomeString echo $result |
如果这还不够,我推荐Markarian451的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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= |