【问题标题】:How to return a string value from a Bash function如何从 Bash 函数返回字符串值
【发布时间】:2011-03-15 07:00:50
【问题描述】:

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

我将用 java 编写示例来展示我想做的事情:

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

String variable = getSomeString();

以下示例在 bash 中有效,但有更好的方法吗?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

【问题讨论】:

  • 顺便说一句,function funcName { 是从早期 ksh 继承的 pre-POSIX 遗留语法(它具有 bash 不尊重的语义差异)。应该使用funcName() {,没有function;见wiki.bash-hackers.org/scripting/obsolete
  • 该链接说要使用 NAME() COMPOUND-CMD 或函数 NAME { CMDS;所以function myFunction { blah; } 很好; function myFunction() { blah } 不好,即在关键字函数中使用括号。
  • 查看这个解释如何在 bash 函数中创建 namerefs 的答案:stackoverflow.com/a/52678279/1583763
  • @Will 查看第二张表,其中建议NAME() 替代function NAME,从而最终导致@Charles Duffy 在他的评论中所写的内容。

标签: string bash function return-value


【解决方案1】:

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

【讨论】:

  • +1 @tomas-f :你必须非常小心你在这个函数“getSomeString()”中的内容,因为任何最终会回显的代码都意味着你得到不正确的返回字符串。
  • 这是完全错误的。 您可以在返回变量中返回任意数据。这显然是更好的方法。
  • @Evi1M4chine,嗯...不,你不能。您可以设置一个全局变量并将其称为“返回”,正如我在脚本中看到的那样。但这是按照惯例,实际上并没有以编程方式与代码的执行相关联。 “显然是更好的方法”?不。命令替换更加明确和模块化。
  • 如果问题是关于命令的,“命令替换更加明确和模块化”将是相关的;这个问题是如何从 bash 函数返回一个字符串!自 Bash 4.3(2014 年?)以来,可以使用内置的方式来执行 OP 所要求的操作 - 请参阅下面的答案。
  • 原始问题包含最简单的方法,并且在大多数情况下效果很好。 Bash 返回值可能应该被称为“返回代码”,因为它们不像脚本中的标准返回值,而更像数字 shell 命令退出代码(您可以执行类似 somefunction && echo 'success' 的操作)。如果你把一个函数想象成另一个命令,那是有道理的;命令不会在退出时“返回”除状态码之外的任何内容,但它们可能会在您可以捕获的同时输出内容。
【解决方案2】:

您可以使用全局变量:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

这给了

'some other string'

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 这个答案很棒!参数可以通过引用传递,类似于C++中的思想。
    • 很高兴收到专家关于该答案的回复。我从未见过在脚本中使用它,也许是有充分理由的。无论如何:那是 +1 它应该被投票给正确答案
    • 这不是和fgm一样简单的答案吗?如果字符串foo 包含空格,这将不起作用,而fgm 的字符串将......正如他所展示的那样。
    • @XichenLi:感谢您的反对意见;请看我的编辑。可以通过\$$1获取函数中变量的初始值。如果您正在寻找不同的东西,请告诉我。
    • @timiscoding 这可以用printf '%q' "$var" 修复。 %q 是 shell 转义的格式字符串。然后直接传递它。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      您还可以捕获函数输出:

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

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

      【讨论】:

      • 除了替代语法注释之外,这不是操作员在他自己的问题中已经写的完全一样的东西吗?
      • 进程替换会不必要地消耗 CPU,因为 forkstdio 比进程内存中的字符串分配成本更高。
      【解决方案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 下这个脚本也是一样的!

      【讨论】:

      • 这个答案确实有其优点。我来到这里以为我想从函数返回一个字符串。这个答案让我意识到这只是我的 C# 习惯。我怀疑其他人可能有同样的经历。
      • @ElmarZander 你错了,这完全相关。这是进入全局范围的一种简单方法,可以将函数范围值纳入其中,有些人会认为这比 bstpierre 概述的重新定义全局变量的 eval 方法更好/更简单。
      • local 不能移植到非 bash 脚本,这是一些人避免使用它的原因之一。
      • 问题:循环中的变量呢?
      • 在 mac ($ bash --version GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin14) 版权所有 (C) 2007 Free Software Foundation, Inc.),它初始化匹配的全局变量是正确的,但是当我尝试在另一个函数 f2 中对同一变量产生副作用时,该副作用不会持续存在。因此,它似乎非常不一致,因此不适合我的使用。
      【解决方案7】:

      地址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 正确指出的那样,来自您可以在其他语言中找到的函数。

      【讨论】:

        【解决方案8】:
        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=
        

        【讨论】:

          【解决方案9】:

          与上面的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 语句来进行调试/记录。捕获回声的习惯用法失败了,因为它捕获了所有回声。谢谢!
          • 这是(次优)合适的解决方案!干净、快速、优雅、明智。
          • +2 保持真实。我正要说。这么多人怎么能忽略在函数内部结合echo,结合命令替换!
          【解决方案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
          

          当然,您可能不会将函数的内部变量声明为本地变量,但您确实应该始终这样做,否则您可能会从父范围中意外覆盖不相关的变量,如果有一个同名。

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

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

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

          【讨论】:

          • 这个^^^。破坏封装的无意别名是evaldeclare -n 解决方案的大问题。为所有输出参数使用一个专用变量名称(如 result)的解决方法似乎是唯一不需要函数知道所有调用者以避免冲突的解决方案。
          【解决方案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]
          

          希望这对人们有所帮助

          -安迪

          【讨论】:

          • 这有其用途,但总的来说,您应该避免显式重定向到控制台;输出可能已经被重定向,或者脚本可能在不存在 tty 的上下文中运行。您可以通过在脚本开头复制3>&1,然后在函数中操作&1 &3 和另一个占位符&4 来解决这个问题。不过,到处都很丑。
          【解决方案12】:

          您可以 echo 一个字符串,但通过管道 (|) 将函数传递给其他东西来捕获它。

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

          【讨论】:

          • 问题是管道右边的东西是一个子shell。所以myfunc | read OUTPUT ; echo $OUTPUT 什么也没产生。 myfunc | ( read OUTPUT; echo $OUTPUT ) 确实获得了预期值并阐明了右侧发生的情况。但是当然 OUTPUT 在您需要的地方不可用...
          【解决方案13】:

          为了说明我对安迪的回答的评论,额外的文件描述符操作以避免使用/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]"
          

          不过还是很讨厌。

          【讨论】:

            【解决方案14】:

            我想,选项已经全部列举出来了。选择一个可能归结为您的特定应用程序的最佳样式问题,在这种情况下,我想提供一种我发现有用的特定样式。在 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”,我的理念是约定对于管理任何语言的复杂性总是很重要的。

            【讨论】:

              【解决方案15】:

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

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

              这个echoes

              tadaa
              

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

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

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

              【讨论】:

                【解决方案16】:

                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 内置命令(参见 declare 和 local 的描述 下面)创建一个nameref,或对另一个变量的引用。这允许 间接操纵的变量。每当 nameref 变量为 引用或赋值给,操作实际上是在变量上执行的 由 nameref 变量的值指定。 nameref 通常用于 shell 函数引用一个变量,该变量的名称作为参数传递给⋅ 功能。例如,如果将变量名称传递给 shell 函数 作为它的第一个参数,运行

                      declare -n ref=$1
                

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

                例如(EDIT 2:(谢谢Ron)命名空间(前缀)函数内部变量名称,以尽量减少外部变量冲突,最终应该正确回答,cmets中提出的问题由卡斯滕):

                # $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 对下方评论的回应)-我无法再在下方添加 cmets,但 Karsten 的评论让我思考,所以我做了以下测试,该测试工作正常,AFAICT - Karsten如果您阅读本文,请从命令行提供一组准确的测试步骤,显示您认为存在的问题,因为以下步骤可以正常工作:

                $ return_a_string ret; echo $ret
                The date is 20170104
                

                (我刚刚运行了这个,将上面的函数粘贴到一个 bash 术语中 - 正如你所看到的,结果很好。)

                【讨论】:

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

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

                解决此问题的唯一方法是使用单个专用输出变量,如 REPLY(如 Evi1M4chine 所建议的那样)或类似 Ron Burk 所建议的约定。

                但是,可以让函数在内部使用固定的输出变量,然后在顶部添加一些糖以向调用者隐藏这一事实,因为我'我们已经完成了以下示例中的 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)
                

                【讨论】:

                  【解决方案18】:

                  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
                  }
                  

                  【讨论】:

                    【解决方案19】:

                    虽然有很多好的答案,但它们都没有按照我希望的方式工作。因此,这是我的解决方案,其中包含以下关键点:

                    帮助健忘的程序员

                    至少我很难在这样的事情之后总是记住错误检查: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
                    # ===========
                    

                    【讨论】:

                      【解决方案20】:

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

                      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}
                      

                      【讨论】:

                        猜你喜欢
                        • 2022-10-14
                        • 1970-01-01
                        • 1970-01-01
                        • 2010-12-23
                        • 2016-11-17
                        • 1970-01-01
                        • 1970-01-01
                        • 2017-05-12
                        相关资源
                        最近更新 更多