我想从 Bash 函数返回一个字符串。
我将在 java 中编写示例以显示我想做的事情:
public String getSomeString() {
return "tadaa";
}
String variable = getSomeString();
下面的示例在 bash 中有效,但有更好的方法吗?
function getSomeString {
echo "tadaa"
}
VARIABLE=$(getSomeString)
function funcName {
是从早期 ksh 继承的 pre-POSIX 遗留语法(它具有 bash 不尊重的语义差异)。应该使用没有 function
的 funcName() {
;见wiki.bash-hackers.org/scripting/obsolete
function myFunction { blah; }
很好; function myFunction() { blah }
不好,即使用括号和关键字函数。
NAME()
作为 function NAME
的替代品,从而最终导致 @Charles Duffy 在他的评论中写道。
我知道没有更好的方法。 Bash 只知道状态码(整数)和写入标准输出的字符串。
您可以让函数将变量作为第一个参数,并使用要返回的字符串修改变量。
#!/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 的评论。
编辑:作为演示,请参阅以下程序。这是一个通用的解决方案:它甚至允许您将字符串接收到局部变量中。
#!/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
编辑:证明原始变量的值在函数中可用,正如@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
\$$1
获取函数中变量的初始值。如果您正在寻找不同的东西,请告诉我。
printf '%q' "$var"
修复。 %q 是 shell 转义的格式字符串。然后直接通过它。
上面的所有答案都忽略了 bash 手册页中的说明。
函数内声明的所有变量都将与调用环境共享。
所有声明为本地的变量都不会被共享。
示例代码
#!/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 下,这个脚本也是一样的!
Bash,自 2014 年 2 月 4.3 版(?)以来,明确支持引用变量或名称引用(namerefs),超越“eval”,具有相同的有益性能和间接效果,并且在您的脚本中可能更清晰,也更难“忘记'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 的描述)为变量分配 nameref 属性,以创建 nameref 或对另一个变量的引用。这允许间接地操纵变量。每当 nameref 变量被引用或赋值时,操作实际上是在由 nameref 变量的值指定的变量上执行的。 nameref 通常在 shell 函数中用于引用一个变量,该变量的名称作为参数传递给函数。例如,如果将变量名作为第一个参数传递给 shell 函数,则在函数内部运行 declare -n ref=$1 会创建一个 nameref 变量 ref,其值是作为第一个参数传递的变量名。对 ref 的引用和赋值被视为对名称作为 $1 传递的变量的引用和赋值。如果 for 循环中的控制变量具有 nameref 属性,则单词列表可以是 shell 变量列表,并在执行循环时依次为列表中的每个单词建立名称引用。不能为数组变量赋予 -n 属性。但是,nameref 变量可以引用数组变量和下标数组变量。 Namerefs 可以使用 unset 内置函数的 -n 选项取消设置。否则,如果使用 nameref 变量的名称作为参数执行 unset,则 nameref 变量引用的变量将被取消设置。
例如(编辑2:(谢谢Ron)命名空间(前缀)函数内部变量名称,以尽量减少外部变量冲突,最终应该正确回答,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
}
并测试这个例子:
$ return_a_string result; echo $result
The date is 20160817
请注意,bash“declare”内置函数在函数中使用时,默认情况下使声明的变量为“local”,“-n”也可以与“local”一起使用。
我更喜欢将“重要声明”变量与“无聊的局部”变量区分开来,因此以这种方式使用“声明”和“本地”充当文档。
编辑 1 - (对 Karsten 下面的评论的回应) - 我不能再在下面添加评论,但 Karsten 的评论让我思考,所以我做了以下测试,它工作得很好,AFAICT - Karsten 如果你读到这个,请提供一个确切的集合来自命令行的测试步骤,显示您假设存在的问题,因为以下步骤可以正常工作:
$ return_a_string ret; echo $ret
The date is 20170104
(我刚刚运行了这个,在将上面的函数粘贴到一个 bash 术语之后 - 正如你所看到的,结果很好。)
declare
在函数内部创建局部变量(该信息不是由 help declare
提供的):“...当在函数中使用时,声明和排版使每个名称都是本地的,与本地命令,除非提供了 -g 选项..."
K=$1; V=$2; eval "$A='$V'";
的事情,但是如果有一个错误(例如一个空的或省略的参数),那就更危险了。 @zenaan 如果您选择“message”作为返回变量名,而不是“ret”,则@Karsten 提出的问题适用。
与上面的 bstpierre 一样,我使用并推荐使用显式命名输出变量:
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")
习语要快一个数量级。在 MSYS 上使用 bash 时速度差异似乎更加显着,其中从函数调用中捕获的标准输出几乎是灾难性的。
可以发送局部变量,因为局部变量在 bash 中是动态范围的:
function another_func() # ARG
{
local result
some_func result "$1"
echo result is $result
}
echo
和命令替换!
您还可以捕获函数输出:
#!/bin/bash
function getSomeString() {
echo "tadaa!"
}
return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var
看起来很奇怪,但比使用全局变量恕我直言要好。传递参数照常工作,只需将它们放在大括号或反引号内。
fork
和 stdio
比进程内存中的字符串分配成本高得多。
正如其他人所写,最直接和最强大的解决方案是使用命令替换:
assign()
{
local x
x="Test"
echo "$x"
}
x=$(assign) # This assigns string "Test" to x
缺点是性能,因为这需要一个单独的过程。
本主题中建议的另一种技术,即传递要分配的变量名称作为参数,有副作用,我不推荐它的基本形式。问题是您可能需要函数中的一些变量来计算返回值,并且可能会发生旨在存储返回值的变量的名称会干扰其中之一:
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
当然,您可能不会将函数的内部变量声明为本地变量,但您确实应该始终这样做,否则如果存在同名变量,您可能会意外覆盖父范围中的不相关变量.
一种可能的解决方法是将传递的变量显式声明为全局变量:
assign()
{
local x
eval declare -g $1
x="Test"
eval "$1=\$x"
}
如果名称“x”作为参数传递,函数体的第二行将覆盖之前的局部声明。但是名称本身可能仍然会干扰,因此如果您打算在将返回值写入其中之前使用先前存储在传递变量中的值,请注意您必须在一开始就将其复制到另一个局部变量中;否则结果将不可预测!此外,这只适用于最新版本的 BASH,即 4.2。更可移植的代码可能会使用具有相同效果的显式条件构造:
assign()
{
if [[ $1 != x ]]; then
local x
fi
x="Test"
eval "$1=\$x"
}
也许最优雅的解决方案就是为函数返回值保留一个全局名称,并在您编写的每个函数中始终如一地使用它。
eval
和 declare -n
解决方案的大问题。为所有输出参数使用像 result
这样的单个专用变量名称的解决方法似乎是唯一不需要函数知道所有调用者以避免冲突的解决方案。
如前所述,从函数返回字符串的“正确”方法是使用命令替换。如果函数还需要输出到控制台(如上面@Mani 所述),请在函数开头创建一个临时 fd 并重定向到控制台。在返回字符串之前关闭临时 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]
希望这可以帮助人们
-安迪
3>&1
,然后在函数中操作 &1
&3
和另一个占位符 &4
来解决这个问题。丑,不过。
您可以使用全局变量:
declare globalvar='some string'
string ()
{
eval "$1='some other string'"
} # ---------- end of function string ----------
string globalvar
echo "'${globalvar}'"
这给
'some other string'
为了说明我对安迪的回答的评论,使用额外的文件描述符操作以避免使用 /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]"
不过还是很恶心。
你拥有它的方式是在不破坏范围的情况下做到这一点的唯一方法。 Bash 没有返回类型的概念,只有退出代码和文件描述符(stdin/out/err 等)
考虑以下代码,解决 Vicky 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
函数中使用的语法,因此您可以在大多数情况下使用这两种方法,尽管捕获输出是更安全的方法,在任何情况下始终有效,模仿函数的返回值正如 Vicky Ronnen
正确指出的那样,您可以在其他语言中找到它。
我认为所有选项都已列举。选择一种可能归结为适合您的特定应用程序的最佳样式,因此,我想提供一种我发现有用的特定样式。在 bash 中,变量和函数不在同一个命名空间中。因此,将同名的变量视为函数的值是一种约定,如果我严格应用它,我发现它可以最大限度地减少名称冲突并提高可读性。现实生活中的一个例子:
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 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
如您所见,返回状态可供您在需要时使用,如果不需要则忽略。 “返回”变量同样可以使用或忽略,但当然只能在调用函数之后使用。
当然,这只是一个约定。您可以在返回之前没有设置关联的值(因此我的约定总是在函数开始时将其归零)或通过再次调用函数(可能是间接地)来践踏其值。尽管如此,如果我发现自己大量使用 bash 函数,它仍然是一个非常有用的约定。
与认为这是一个标志应该“转向 perl”的观点相反,我的理念是约定对于管理任何语言的复杂性总是很重要的。
它们的关键问题是调用者可以传入变量名(无论是使用 eval
还是 declare -n
)的任何“命名输出变量”方案都是无意的别名,即名称冲突:从封装的角度来看,不这样做是很糟糕的能够在函数中添加或重命名局部变量,而无需先检查 ALL 函数的调用者以确保他们不想传递与输出参数相同的名称。 (或者在另一个方向上,我不想阅读我正在调用的函数的源代码,只是为了确保我打算使用的输出参数不是该函数中的本地参数。)
解决这个问题的唯一方法是使用像 REPLY
(如 Evi1M4chine 所建议的)这样的单个专用输出变量或像 Ron Burk 所建议的那样的约定。
但是,可以让函数在内部使用固定的输出变量,然后在顶部添加一些糖以向调用者隐藏这个事实,就像我已经完成的那样以下示例中的 call
函数。将其视为概念证明,但关键点是
该函数始终将返回值分配给 REPLY,也可以像往常一样返回退出代码
从调用者的角度来看,返回值可以分配给任何变量(本地或全局),包括 REPLY(参见包装器示例)。函数的退出代码是通过的,因此在 if 或 while 或类似结构中使用它们可以按预期工作。
从语法上讲,函数调用仍然是一个简单的语句。
之所以可行,是因为 call
函数本身没有局部变量,并且不使用除 REPLY
之外的变量,从而避免了任何可能的名称冲突。在分配调用者定义的输出变量名称的地方,我们实际上是在调用者的范围内(技术上在 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 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
输出:
a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)
在我的程序中,按照惯例,这是预先存在的 $REPLY
变量的用途,而 read
正是用于该目的。
function getSomeString {
REPLY="tadaa"
}
getSomeString
echo $REPLY
这echo
tadaa
但是为了避免冲突,任何其他全局变量都可以。
declare result
function getSomeString {
result="tadaa"
}
getSomeString
echo $result
如果这还不够,我推荐 Markarian451 的解决方案。
您可以echo
一个字符串,但通过管道 (|
) 将函数传递给其他内容来捕获它。
您可以使用 expr
执行此操作,但 ShellCheck 报告此用法已弃用。
myfunc | read OUTPUT ; echo $OUTPUT
不会产生任何结果。 myfunc | ( read OUTPUT; echo $OUTPUT )
确实获得了预期值并阐明了右侧发生的情况。但是当然 OUTPUT 在您需要的地方不可用...
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
}
尽管有很多好的答案,但它们都没有按照我想要的方式工作。所以这是我的解决方案,其中包含以下关键点:
帮助健忘的程序员
至少在这样的事情之后我很难记住错误检查:var=$(myFunction)
允许使用换行符分配值 \n
有些解决方案不允许这样做,因为有些人忘记了要分配的值周围的单引号。正确方法:eval "${returnVariable}='${value}'"
甚至更好:请参阅下面的下一点。
使用 printf 代替 eval
只需尝试对此处的一些假定解决方案使用类似 myFunction "date && var2"
的内容即可。 eval
将执行赋予它的任何内容。我只想分配值,所以我使用 printf -v "${returnVariable}" "%s" "${value}"
代替。
封装和防止变量名冲突
如果其他用户或至少对函数了解较少的人(这可能是几个月后的我)正在使用 myFunction
,我不希望他们知道他必须使用全局返回值名称或某些变量名称是禁止使用。这就是我在 myFunction
顶部添加名称检查的原因:
if [[ "${1}" = "returnVariable" ]]; then
echo "Cannot give the ouput to \"returnVariable\" as a variable with the same name is used in myFunction()!"
echo "If that is still what you want to do please do that outside of myFunction()!"
return 1
fi
请注意,如果您必须检查很多变量,这也可以放入函数本身。如果我仍想使用相同的名称(此处:returnVariable
),我只需创建一个缓冲区变量,将其提供给 myFunction
,然后复制值 returnVariable
。
所以这里是:
我的函数():
myFunction() {
if [[ "${1}" = "returnVariable" ]]; then
echo "Cannot give the ouput to \"returnVariable\" as a variable with the same name is used in myFunction()!"
echo "If that is still what you want to do please do that outside of myFunction()!"
return 1
fi
if [[ "${1}" = "value" ]]; then
echo "Cannot give the ouput to \"value\" as a variable with the same name is used in myFunction()!"
echo "If that is still what you want to do please do that outside of myFunction()!"
return 1
fi
local returnVariable="${1}"
local value=$'===========\nHello World\n==========='
echo "setting the returnVariable now..."
printf -v "${returnVariable}" "%s" "${value}"
}
测试用例:
var1="I'm not greeting!"
myFunction var1
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "var1:\n%s\n" "${var1}"
# Output:
# setting the returnVariable now...
# myFunction(): SUCCESS
# var1:
# ===========
# Hello World
# ===========
returnVariable="I'm not greeting!"
myFunction returnVariable
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "returnVariable:\n%s\n" "${returnVariable}"
# Output
# Cannot give the ouput to "returnVariable" as a variable with the same name is used in myFunction()!
# If that is still what you want to do please do that outside of myFunction()!
# myFunction(): FAILURE
# returnVariable:
# I'm not greeting!
var2="I'm not greeting!"
myFunction "date && var2"
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "var2:\n%s\n" "${var2}"
# Output
# setting the returnVariable now...
# ...myFunction: line ..: printf: `date && var2': not a valid identifier
# myFunction(): FAILURE
# var2:
# I'm not greeting!
myFunction var3
[[ $? -eq 0 ]] && echo "myFunction(): SUCCESS" || echo "myFunction(): FAILURE"
printf "var3:\n%s\n" "${var3}"
# Output
# setting the returnVariable now...
# myFunction(): SUCCESS
# var3:
# ===========
# Hello World
# ===========
#实现函数的通用返回栈:
STACK=()
push() {
STACK+=( "${1}" )
}
pop() {
export $1="${STACK[${#STACK[@]}-1]}"
unset 'STACK[${#STACK[@]}-1]';
}
#用法:
my_func() {
push "Hello world!"
push "Hello world2!"
}
my_func ; pop MESSAGE2 ; pop MESSAGE1
echo ${MESSAGE1} ${MESSAGE2}
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=
不定期副业成功案例分享
somefunction && echo 'success'
之类的操作)。如果你把一个函数想象成另一个命令,那是有道理的;除了状态码之外,命令不会在退出时“返回”任何内容,但它们可能会在您可以捕获的同时输出内容。