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

时间:2010-07-13 11:55:37

标签: string bash function return-value

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

我将在java中编写示例以显示我想要做的事情:

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

String variable = getSomeString();

以下示例适用于bash,但有更好的方法吗?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

20 个答案:

答案 0 :(得分:266)

我知道没有更好的方法。 Bash只知道写入stdout的状态代码(整数)和字符串。

答案 1 :(得分:186)

你可以让函数将变量作为第一个arg,并使用你想要返回的字符串修改变量。

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

答案 2 :(得分:94)

上面的所有答案都忽略了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下,这个脚本也是一样的!

答案 3 :(得分:42)

Bash,自版本4.3,2014年2月(?),明确支持引用变量或名称引用(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作为第一个参数传递。 ref的引用和赋值是      作为引用和赋值给名称传递为as的变量      $ 1如果for循环中的控制变量具有nameref属性,则列表      单词可以是shell变量列表,名称引用将是      反过来,当循环执行时,为列表中的每个单词建立。      数组变量不能给出-n属性。但是,nameref变量      可以引用数组变量和下标数组变量。 Namerefs可以是      使用unset内置的-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"声明" builtin,当在函数中使用时,生成声明的变量" local"默认情况下," -n"也可以与" local"。

一起使用

我更喜欢区分"重要声明"来自"无聊的本地"变量,所以使用"声明"和#34;本地"以这种方式充当文档。

编辑1 - (对Karsten的评论作出回应) - 我不能再在下面添加评论,但Karsten的评论让我思考,所以我做了以下测试,哪个工作精细,AFAICT - Karsten如果您阅读此内容,请从命令行提供一组精确的测试步骤,显示您认为存在的问题,因为以下步骤可以正常工作:

$ return_a_string ret; echo $ret
The date is 20170104

(我把这上面的函数粘贴到bash术语中之后就开始运行了 - 正如你所看到的,结果运行得很好。)

答案 4 :(得分:34)

与上面的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,速度差异显得更加显着,其中从函数调用捕获的stdout几乎是灾难性的。

可以发送局部变量,因为本地变量是以bash为动态范围的:

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

答案 5 :(得分:20)

您还可以捕获函数输出:

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

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

看起来很奇怪,但比使用全局变量恕我直言更好。传递参数照常工作,只需将它们放在大括号或反引号中即可。

答案 6 :(得分:12)

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

希望这有助于人们

-Andy

答案 7 :(得分:10)

最直接和最强大的解决方案是使用命令替换,正如其他人所写:

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

当然,您可能不会将函数的内部变量声明为本地变量,但是您确实应该始终这样做,否则您可能会意外地从父作用域覆盖不相关的变量(如果有的话)同名。

一种可能的解决方法是将传递的变量显式声明为global:

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

也许最优雅的解决方案就是为函数返回值保留一个全局名称 在你编写的每个函数中都使用它。

答案 8 :(得分:8)

您可以使用全局变量:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

这给出了

'some other string'

答案 9 :(得分:6)

为了说明我对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]"
但是,仍然很讨厌。

答案 10 :(得分:3)

考虑到以下代码,解决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正确指出的其他语言中找到的函数返回值。

答案 11 :(得分:3)

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

答案 12 :(得分:2)

我认为,所有选项都已列举。选择一个可能归结为特定应用的最佳风格问题,在这种情况下,我想提供一种我发现有用的特定风格。在bash中,变量和函数不在同一名称空间中。因此,处理与函数值相同名称的变量是一种惯例,我发现如果我严格应用它,可以最大限度地减少名称冲突并增强可读性。现实生活中的一个例子: UnGetChar = function GetChar(){     #假设失败     getchar函数=     #如果某人先前“取消”了一个字符     如果! [-z“$ UnGetChar”];然后         的getchar = “$ UnGetChar”         UnGetChar =         返回0#成功     #else,如果不是在EOF     elif IFS =读取-N1 GetChar;然后         返回0#成功     其他         返回1#EOF     科幻 } function UnGetChar(){     UnGetChar = “$ 1” } 并且,使用这些功能的示例: function GetToken(){     #假设失败     为gettoken =     #if在文件末尾     如果! getchar函数;然后         返回1#EOF     #如果开始评论     elif [[“$ GetChar”==“#”]];然后         而[[“$ GetChar”!= $'\ n']];做             为gettoken + = “$的getchar”             的getchar         DONE         UnGetChar“$ GetChar”     #如果引用字符串的开头     elif [“$ GetChar”=='“'];然后 #...等等 如您所见,返回状态可供您在需要时使用,如果不需要则可忽略。同样可以使用或忽略“返回”变量,但当然只有在调用函数之后。 当然,这只是一个惯例。您可以自由地在返回之前设置关联值(因此我的约定总是在函数的开头将其置零)或者通过再次调用函数(可能是间接的)来践踏其值。不过,如果我发现自己大量使用bash函数,这是一个非常有用的约定。 与情绪相反,这是一个标志,例如“转向perl”,我的理念是,约定对于管理任何语言的复杂性始终是重要的。

答案 13 :(得分:2)

它们是任何“命名输出变量”方案的关键问题,其中调用者可以传入变量名称(无论是使用eval还是declare -n)是无意的别名,即名称冲突:来自封装点但是,如果不首先检查 ALL 函数的调用者以确保他们不想传递与输出参数相同的名称,那么在函数中无法添加或重命名局部变量是很糟糕的。 (或者在另一个方向,我不想阅读我正在调用的函数的源代码,以确保我打算使用的输出参数不是该函数中的局部函数。)

唯一的方法是使用单个专用输出变量,如REPLY(由Evi1M4chine建议)或类似Ron Burk建议的约定。

然而,可以让函数使用固定的输出变量内部,然后在顶部添加一些糖来隐藏来自调用者的这个事实,因为我'完成了以下示例中的call函数。考虑这是一个概念证明,但关键点是

  • 该函数始终将返回值分配给REPLY,并且还可以照常返回退出代码
  • 从调用者的角度来看,可以将返回值分配给任何变量(本地或全局),包括REPLY(请参阅wrapper示例)。函数的退出代码被传递,因此在例如它们中使用它们。 ifwhile或类似结构按预期工作。
  • 从语法上讲,函数调用仍然是一个简单的语句。

这样做的原因是因为call函数本身没有本地,并且不使用除REPLY之外的其他变量,因此避免了任何名称冲突的可能性。在分配调用者定义的输出变量名称时,我们实际上在调用者的范围内(技术上在call函数的相同范围内),而不是在被调用函数的范围内。 / p>

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

答案 14 :(得分:1)

你可以echo一个字符串,但可以通过管道(|)将该函数捕获到其他东西。

您可以使用expr执行此操作,但ShellCheck会将此用法报告为已弃用。

答案 15 :(得分:1)

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
}

答案 16 :(得分:0)

在我的程序中,按照惯例,这是预先存在的$REPLY变量的用途,read用于此目的。

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

echo es

tadaa

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

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

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

答案 17 :(得分:0)

尽管有很多好的答案,但它们都无法按照我希望的方式工作。因此,这是我针对这些关键点的解决方案:

帮助健忘的程序员

至少,我会一直想记住类似以下内容的错误检查: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()

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
# ===========

答案 18 :(得分:0)

#为函数实现一个通用的返回栈:

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}

答案 19 :(得分:-2)

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=