首页 文章

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

提问于
浏览
388

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

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

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

String variable = getSomeString();

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

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

18 回答

  • 35
    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=
    
  • 1

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

  • 3

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

    function getSomeString {
      REPLY="tadaa"
    }
    
    getSomeString
    echo $REPLY
    

    这个 echo es

    tadaa
    

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

    declare result
    
    function getSomeString {
      result="tadaa"
    }
    
    getSomeString
    echo $result
    

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

  • 2

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

    但仍然很讨厌 .

  • 92

    它们是密钥问题的任何'named output variable'方案,其中调用者可以传入变量名称(无论是使用 eval 还是 declare -n )是无意的别名,即名称冲突:从封装的角度来看,很难无法添加或重命名本地函数中的变量没有检查 ALL 函数's callers first to make sure they'不想传递与输出参数相同的名称 . (或者在另一个方向上,我只是为了确保我打算使用的输出参数不是该函数中的本地 . )

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

    但是,函数可以使用固定的输出变量 internally ,然后在顶部添加一些糖到 hide this fact from the caller ,就像我在下面的例子中使用 call 函数一样 . 认为这是一个概念证明,但关键点是

    • 该函数始终将返回值分配给 REPLY ,并且还可以像往常一样返回退出代码

    • 从调用者的角度来看,返回值可以分配给任何变量(本地或全局),包括 REPLY (参见 wrapper 示例) . 函数的退出代码被传递,因此在例如它们中使用它们 . ifwhile 或类似的结构按预期工作 .

    • 从语法上讲,函数调用仍然是一个简单的语句 .

    这样做的原因是因为 call 函数本身没有本地,并且不使用 REPLY 以外的任何变量,从而避免了任何名称冲突的可能性 . 在分配调用者定义的输出变量名称时,我们使用're effectively in the caller'范围(技术上与 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)
    
  • 19

    考虑到以下代码,解决了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

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

  • 1

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

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

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

  • 6

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

  • 241

    您可以使用全局变量:

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

    这给了

    'some other string'
    
  • -2

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

  • 2

    我认为,所有选项都已列举 . 选择一个可能归结为您的特定应用的最佳风格问题,并且在这种情况下,我想提供一种我认为有用的特定风格 . 在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
    

    如您所见,返回状态可供您在需要时使用,如果不需要则可忽略 . 同样可以使用或忽略"returned"变量,但当然仅在调用函数之后 .

    当然,这只是一个惯例 . 您可以自由地在返回之前设置关联值(因此我的约定总是在函数开始时将其置零)或者通过再次调用函数(可能是间接的)来践踏其值 . 不过,如果我发现自己大量使用bash函数,这是一个非常有用的约定 .

    与情绪相反,这是一个标志,例如“转向perl”,我的理念是,约定对于管理任何语言的复杂性始终是重要的 .

  • 1

    您还可以捕获函数输出:

    #!/bin/bash
    function getSomeString() {
         echo "tadaa!"
    }
    
    return_var=$(getSomeString)
    echo $return_var
    # Alternative syntax:
    return_var=`getSomeString`
    echo $return_var
    

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

  • 31

    你可以 echo 一个字符串,但通过管道( | )该函数来捕获它 .

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

  • 3

    您可以让函数将变量作为第一个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” .

    Edit :在适当的地方添加引用以允许字符串中的空格来解决@Luca Borrione的评论 .

    Edit :作为演示,请参阅以下程序 . 这是一个通用解决方案:它甚至允许您将字符串接收到本地变量中 .

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

    Edit :证明原始变量的值在函数中可用,正如@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
    
  • 8

    与上面的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特殊字符 . 我发现这是一个 order of magnitude faster 而不是捕获回声的 result=$(some_func "arg1") 成语 . 使用MSYS上的bash速度差异显得更加显着,其中从函数调用捕获的stdout几乎是灾难性的 .

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

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

    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...
    

    并且:

    PARAMETERS可以使用声明或本地内置命令的-n选项为变量分配nameref属性(请参阅下面的declare和local的说明)以创建nameref或对另一个变量的引用 . 这允许间接操纵变量 . 每当引用或赋值给nameref变量时,操作实际上是对nameref变量的值指定的变量执行的 . 在shell函数中通常使用nameref来引用其名称作为参数传递给函数的变量 . 例如,如果将变量名称作为第一个参数传递给shell函数,则运行declare -n ref = $ 1
    函数内部创建一个nameref变量ref,其值是作为第一个参数传递的变量名 . ref的引用和赋值被视为对名称传递为$ $ 1的变量的引用和赋值 . 如果for循环中的控制变量有nameref属性,单词列表可以是shell变量列表,并且当执行循环时,将为列表中的每个单词 Build 名称引用 . 数组变量不能给出-n属性 . 但是,nameref变量可以引用数组变量和下标数组变量 . 可以使用unset内置的-n选项取消设置Namerefs . 否则,如果使用nameref变量的名称作为参数执行unset,则将取消设置nameref变量引用的变量 .

    例如( EDIT 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”builtin在函数中使用时,默认情况下声明变量为“local”,“ - n”也可以与“local”一起使用 .

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

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

    $ return_a_string ret; echo $ret
    The date is 20170104
    

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

  • 10

    bash 模式返回 scalararray 值对象:

    定义

    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
    }
    

相关问题