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

Tom*_*s F 438 string bash function return-value

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

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

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

String variable = getSomeString();
Run Code Online (Sandbox Code Playgroud)

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

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
Run Code Online (Sandbox Code Playgroud)

Phi*_*ipp 272

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

  • @ Evi1M4chine,嗯......不,你不能.您可以设置一个全局变量并将其命名为"return",就像我在脚本中看到的那样.但那是按照惯例,实际上并不是以编程方式绑定代码的执行."显然是一种更好的方式"?不.命令替换更加明确和模块化. (30认同)
  • +1 @ tomas-f:你必须非常小心你在这个函数"getSomeString()"中拥有的东西,因为任何最终回显的代码都意味着你得到不正确的返回字符串. (14认同)
  • **这是完全错误的.**您可以在返回变量中返回任意数据.这显然是一种更好的方式. (11认同)
  • 如果问题是关于命令的话,"命令替换更加明确和模块化"将是相关的; 这个问题是如何从bash函数返回一个字符串!自Bash 4.3(2014?)以来,可以使用内置的方式来执行OP提出的问题 - 请参阅下面的答案. (6认同)
  • 原始问题包含最简单的方法,并且在大多数情况下效果很好。Bash 返回值可能应该被称为“返回代码”,因为它们不像脚本中的标准返回值,而更像是数字 shell 命令退出代码(您可以执行诸如 `somefunction && echo 'success'` 之类的操作)。如果你认为一个函数只是另一个命令,那是有道理的;除了状态代码之外,命令在退出时不会“返回”任何内容,但它们可能会在此期间输出您可以捕获的内容。 (4认同)
  • 我相信这是一个很好的答案。如果您试图像使用其他语言一样返回值,那么您将会遇到麻烦。要么接受Bash的全局变量方式,要么使用另一种脚本语言。如果您选择第三种方法,是时候退后两步并重新评估您是否在使用正确的工具来完成工作。 (2认同)

bst*_*rre 191

您可以让函数将变量作为第一个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
Run Code Online (Sandbox Code Playgroud)

打印"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"
Run Code Online (Sandbox Code Playgroud)

这打印:

+ 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
Run Code Online (Sandbox Code Playgroud)

编辑:这表明原始变量的值在功能可用,进行了错误@Xichen李在评论批评.

#!/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"
Run Code Online (Sandbox Code Playgroud)

这给出了输出:

+ 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
Run Code Online (Sandbox Code Playgroud)

  • @XichenLi:谢谢你用你的downvote发表评论; 请看我的编辑.您可以使用`\ $$ 1`获取函数中变量的初始值.如果您正在寻找不同的东西,请告诉我. (4认同)
  • 收到专家对该答案的回复会很高兴.我从来没有见过用于脚本的东西,也许是有充分理由的.无论如何:那是+1它应该被投票给正确答案 (3认同)

小智 96

上面的所有答案都忽略了bash手册页中所述的内容.

  • 函数内声明的所有变量都将与调用环境共享.
  • 声明为local的所有变量都不会被共享.

示例代码

#!/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
Run Code Online (Sandbox Code Playgroud)

并输出

$ 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
Run Code Online (Sandbox Code Playgroud)

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

  • 这个答案确实有其优点.我来到这里以为我想从函数中返回一个字符串.这个答案让我意识到那只是我的C# - 哈维斯说话.我怀疑其他人可能有相同的经历. (10认同)
  • @ElmarZander你错了,这完全相关.这是进入全局范围的函数范围值的简单方法,有些人会认为这比eval方法更好/更简单,以重新定义bstpierre概述的全局变量. (4认同)

zen*_*aan 44

Bash,自版本4.3,2014年2月(?),明确支持引用变量或名称引用(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...
Run Code Online (Sandbox Code Playgroud)

并且:

参数

可以使用声明或本地内置命令的-n选项为变量分配nameref属性(请参阅下面的declare和local的描述)以创建nameref或对另一个变量的引用.这允许间接操纵变量.每当引用或赋值给nameref变量时,操作实际上是对nameref变量的值指定的变量执行的.在shell函数中通常使用nameref来引用其名称作为参数传递给函数的变量.例如,如果变量名称作为第一个参数传递给shell函数,则运行

      declare -n ref=$1
Run Code Online (Sandbox Code Playgroud)

函数内部创建一个nameref变量ref,其值是作为第一个参数传递的变量名.ref的引用和赋值被视为对名称传递为$ $ 1的变量的引用和赋值.如果for循环中的控制变量具有nameref属性,则单词列表可以是shell变量列表,并且当执行循环时,将依次为列表中的每个单词建立名称引用.数组变量不能给出-n属性.但是,nameref变量可以引用数组变量和下标数组变量.可以使用unset内置的-n选项取消设置Namerefs.否则,如果使用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
}
Run Code Online (Sandbox Code Playgroud)

并测试此示例:

$ return_a_string result; echo $result
The date is 20160817
Run Code Online (Sandbox Code Playgroud)

请注意,bash"declare"builtin在函数中使用时,默认使声明变量为"local"," - n"也可以与"local"一起使用.

我更喜欢将"重要声明"变量与"无聊的本地"变量区分开来,因此以这种方式使用"declare"和"local"作为文档.

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

$ return_a_string ret; echo $ret
The date is 20170104
Run Code Online (Sandbox Code Playgroud)

(在将上述函数粘贴到bash术语之后,我刚刚运行了这个 - 正如您所看到的,结果运行得很好.)

  • 我希望这能渗透到顶端.评估应该是最后的手段.值得一提的是,nameref变量仅在bash 4.3之后可用(根据[changelog](http://git.savannah.gnu.org/cgit/bash.git/plain/CHANGES))(于2014年2月发布[ ?]).如果担心可移植性,这一点很重要.请引用bash手册,其中`declare`在函数内部创建局部变量(该信息不是由`help declare`给出的):"......当在函数中使用时,声明和排版使每个名称都是本地的,就像使用本地命令,除非提供-g选项......" (4认同)
  • 这与 eval 解决方案具有相同的别名问题。当您调用一个函数并传入输出变量的名称时,您必须避免传递在您调用的函数中本地使用的变量的名称。这是封装方面的一个主要问题,因为您不能只在函数中添加或重命名新的局部变量,而没有任何函数调用者可能想要使用该名称作为输出参数。 (2认同)
  • @Karsten 同意。在这两种情况下(eval 和 namerefs),您可能必须选择不同的名称。nameref 方法相对于 eval 的优点之一是不必处理转义字符串。当然,你总是可以做类似“K=$1;”的事情。V=2 美元;eval "$A='$V'";`,但是有一个错误(例如参数为空或省略),那就更危险了。@zenaan 如果您选择“message”而不是“ret”作为返回变量名称,则 @Karsten 提出的问题适用。 (2认同)
  • 函数可能必须从一开始就设计为接受一个nameref参数,因此函数作者应该知道名称冲突的可能性,并且可以使用一些典型的约定来避免这种情况.例如,在函数X内部,使用约定"X_LOCAL_name"命名局部变量. (2认同)

Mar*_*451 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
}
Run Code Online (Sandbox Code Playgroud)

注意使用引用$.这将避免以$resultshell特殊字符解释内容.我发现这比捕获回声的成语一个数量级result=$(some_func "arg1").使用MSYS上的bash速度差异显得更加显着,其中从函数调用捕获的stdout几乎是灾难性的.

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

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Run Code Online (Sandbox Code Playgroud)

  • 这对我有帮助,因为我喜欢使用多个echo语句进行调试/记录.捕获回声的习语失败,因为它捕获了所有这些.谢谢! (4认同)

chi*_*org 22

您还可以捕获函数输出:

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

return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var
Run Code Online (Sandbox Code Playgroud)

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

  • 除了替代语法注释,这不是op在他自己的问题中已经写过的完全相同的东西吗? (11认同)

小智 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]"
Run Code Online (Sandbox Code Playgroud)

执行没有参数的脚本会产生......

# ./func_return_test.sh
writing directly to console
my_string:  [some default string]
Run Code Online (Sandbox Code Playgroud)

希望这有助于人们

-Andy

  • 它有它的用途,但总的来说你应该避免显式重定向到控制台; 输出可能已经被重定向,或者脚本可能在没有tty的上下文中运行.您可以通过在脚本的头部复制"3>&1",然后在函数内操作"&1"`&3`和另一个占位符"&4"来解决这个问题.尽管如此丑陋. (6认同)

Tom*_*Żuk 10

其他人写道:最直接,最强大的解决方案是使用命令替换.

assign()
{
    local x
    x="Test"
    echo "$x"
}

x=$(assign) # This assigns string "Test" to x
Run Code Online (Sandbox Code Playgroud)

缺点是性能,因为这需要一个单独的过程.

本主题中提出的另一种技术,即传递一个变量的名称作为参数赋值,有副作用,我不推荐它的基本形式.问题是您可能需要函数中的一些变量来计算返回值,并且可能会发生用于存储返回值的变量的名称将干扰其中一个:

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
Run Code Online (Sandbox Code Playgroud)

当然,您可能不会将函数的内部变量声明为本地变量,但实际上您应该始终这样做,否则,如果存在具有相同名称的变量,您可能会意外地覆盖父作用域中的不相关变量. .

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

assign()
{
    local x
    eval declare -g $1
    x="Test"
    eval "$1=\$x"
}
Run Code Online (Sandbox Code Playgroud)

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

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

也许最优雅的解决方案是为函数返回值保留一个全局名称,并在您编写的每个函数中始终如一地使用它.

  • 这^^^。eval和declare -n解决方案都存在一个大问题,即无意的别名破坏了封装。为所有输出参数使用单个专用变量名(如“结果”)的解决方法似乎是唯一不需要函数知道调用者所有函数来避免冲突的解决方案。 (2认同)

Fri*_*ner 8

您可以使用全局变量:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"
Run Code Online (Sandbox Code Playgroud)

这给了

'some other string'
Run Code Online (Sandbox Code Playgroud)


jmb*_*jmb 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]"
Run Code Online (Sandbox Code Playgroud)

但仍然很讨厌.