如何从Bash函数返回字符串值

我想从Bash函数返回一个字符串。

我将用java写这个例子来说明我想做什么:

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


String variable = getSomeString();

下面的示例可以在bash中工作,但是是否有更好的方法来做到这一点?

function getSomeString {
echo "tadaa"
}


VARIABLE=$(getSomeString)
389637 次浏览

没有比这更好的方法了。Bash只知道写入标准输出的状态码(整数)和字符串。

你可以使用一个全局变量:

declare globalvar='some string'


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


string globalvar


echo "'${globalvar}'"

这给了

'some other string'

你可以让这个函数把一个变量作为第一个参数,然后用你想要返回的字符串修改变量。

#!/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

你拥有它的方式是在不破坏范围的情况下做到这一点的唯一方法。Bash没有返回类型的概念,只有退出码和文件描述符(stdin/out/err等)。

您还可以捕获函数输出:

#!/bin/bash
function getSomeString() {
echo "tadaa!"
}


return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var

看起来很奇怪,但比使用全局变量更好。传递参数和往常一样,只是把它们放在大括号或反勾号内。

以上所有答案都忽略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下,这个脚本也做同样的事情!

寻址Vicky罗内恩的头部,考虑以下代码:

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
< p > < br > < br > 将< / p >
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正确指出的那样。

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=
像上面的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
}

最直接和健壮的解决方案是使用命令替换,就像其他人写的那样:

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"
}

如果name "x"作为参数传递,函数体的第二行将覆盖前面的局部声明。但是名称本身可能仍然会产生干扰,因此如果您打算在写入返回值之前使用先前存储在传递变量中的值,请注意必须在一开始就将其复制到另一个局部变量中;否则结果将不可预测! 此外,这只适用于BASH的最新版本,即4.2。更可移植的代码可能会使用具有相同效果的显式条件构造:

assign()
{
if [[ $1 != x ]]; then
local x
fi
x="Test"
eval "$1=\$x"
}
也许最优雅的解决方案是为函数返回值和保留一个全局名称

.

.

如前所述,从函数返回字符串的“正确”方法是使用命令替换。如果函数也需要输出到控制台(如@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]

希望这能帮助到人们

安迪

您可以echo一个字符串,但是通过将函数(|)连接到其他东西来捕获它。

您可以使用expr来实现,尽管ShellCheck报告这种用法已弃用。

为了说明我对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]"

不过还是很恶心。

我想,所有的选择都已经列举出来了。选择一种可以归结为最适合您的特定应用程序的样式,因此,我想提供一种我认为有用的特定样式。在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”,我的哲学是,对于管理任何语言的复杂性,约定总是很重要的。

在我的程序中,按照惯例,这就是预先存在的$REPLY变量的用途,而read正是为了这个目的而使用它。

function getSomeString {
REPLY="tadaa"
}


getSomeString
echo $REPLY

这# EYZ0es

tadaa

但是为了避免冲突,任何其他全局变量都可以。

declare result


function getSomeString {
result="tadaa"
}


getSomeString
echo $result

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

Bash自2014年2月4.3版(?)起,除了“eval”之外,还明确支持引用变量或名称引用(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选项将nameref属性赋值给 声明或本地内置命令(参见Declare和local .命令的描述) )来创建一个nameref,或者对另一个变量的引用。这允许 被间接操纵的变量。当变量nameref为⋅时 被引用或赋值时,操作实际上是在变量上执行的 由变量nameref的值指定。nameref通常用于 Shell函数引用一个变量,其名称作为参数传递给⋅ 这个函数。例如,如果将变量名传递给shell函数 作为它的第一个参数,运行

      declare -n ref=$1

在函数内创建一个nameref变量ref,其值就是该变量 Name作为第一个参数传递。引用和赋值是 作为变量的引用和赋值,变量名传为⋅ 1美元。如果for循环中的控制变量具有nameref属性,则list Of words可以是shell变量列表,而名称引用则是⋅ 在执行循环时依次为列表中的每个单词建立。 数组变量不能被赋予-n属性。然而,nameref变量 可以引用数组变量和下标数组变量。Namerefs可以是⋅ 使用-n选项取消内置的取消设置。否则,执行unset 以nameref变量的名称为参数,⋅

. 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术语中后运行了这个程序——正如您所看到的,结果运行得很好。)

任何“命名输出变量”方案的关键问题是,调用者可以传递变量名(无论是使用eval还是declare -n)是无意的别名,即名称冲突:从封装的角度来看,如果不先检查函数的调用者所有,以确保他们不想传递与输出参数相同的名称,就不能在函数中添加或重命名局部变量,这是非常糟糕的。(或者在另一个方向上,我不想读取我正在调用的函数的源,只是为了确保我打算使用的输出参数不是该函数中的局部参数。)

解决这个问题的唯一方法是使用单个专用输出变量,如REPLY(如Evi1M4chine所建议的)或像罗恩·伯克所建议的那样的约定。

但是,也可以让函数使用固定的输出变量在内部,然后在向调用者隐藏这一事实上添加一些糖,就像我在下面的示例中对call函数所做的那样。把这看作是概念的证明,但关键是

  • 该函数总是将返回值赋给REPLY,也可以像往常一样返回一个退出码
  • 从调用者的角度来看,返回值可以赋给任何变量(本地或全局),包括REPLY(参见wrapper示例)。函数的退出码是传递的,所以在ifwhile或类似结构中使用它们可以正常工作。
  • 从语法上讲,函数调用仍然是一条简单的语句。

这是因为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)

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.

就是这样:

# EYZ0:

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}