ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

String variable = getSomeString();

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

function getSomeString {
   echo "tadaa"
}

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

P
Philipp

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


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

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

#!/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 它应该被投票给正确答案
@XichenLi:感谢您对您的投反对票发表评论;请看我的编辑。您可以使用 \$$1 获取函数中变量的初始值。如果您正在寻找不同的东西,请告诉我。
我认为这个答案很整洁。不幸的是,我的返回字符串包含括号和单引号,eval 试图解释何时应将其视为文字。 :(
@timiscoding 这可以用 printf '%q' "$var" 修复。 %q 是 shell 转义的格式字符串。然后直接通过它。
V
Vicky Ronnen

上面的所有答案都忽略了 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你错了,这完全相关。这是进入全局范围的函数范围值的简单方法,有些人会认为这比重新定义全局变量的 eval 方法更好/更简单,如 bstpierre 所述。
local 不能移植到非 bash 脚本,这是一些人避免使用它的原因之一。
问题:循环中的变量呢?
在 mac ($ bash --version GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.) 上,匹配的全局变量是正确的已初始化,但是当我尝试在另一个函数 f2 中对同一变量产生副作用时,该副作用不会持续存在。因此,它似乎非常不一致,因此不适合我的使用。
z
zenaan

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

并且:

参数 可以使用 -n 选项为 declare 或 local 内置命令(请参阅下面的 declare 和 local 的描述)为变量分配 nameref 属性,以创建 nameref 或对另一个变量的引用。这允许间接地操纵变量。每当 nameref 变量被引用或赋值时,操作实际上是在由 nameref 变量的值指定的变量上执行的。 nameref 通常在 shell 函数中用于引用一个变量,该变量的名称作为参数传递给函数。例如,如果将变量名作为第一个参数传递给 shell 函数,则在函数内部运行 declare -n ref=$1 会创建一个 nameref 变量 ref,其值是作为第一个参数传递的变量名。对 ref 的引用和赋值被视为对名称作为 $1 传递的变量的引用和赋值。如果 for 循环中的控制变量具有 nameref 属性,则单词列表可以是 shell 变量列表,并在执行循环时依次为列表中的每个单词建立名称引用。不能为数组变量赋予 -n 属性。但是,nameref 变量可以引用数组变量和下标数组变量。 Namerefs 可以使用 unset 内置函数的 -n 选项取消设置。否则,如果使用 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
}

并测试这个例子:

$ 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 应该是最后的手段。值得一提的是,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。
M
Markarian451

与上面的 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 和命令替换!
c
chiborg

您还可以捕获函数输出:

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

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

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


除了替代语法注释之外,这不是操作员在他自己的问题中已经写过的完全相同的东西吗?
进程替换会不必要地消耗 CPU,因为 forkstdio 比进程内存中的字符串分配成本高得多。
T
Tomasz Żuk

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

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 这样的单个专用变量名称的解决方法似乎是唯一不需要函数知道所有调用者以避免冲突的解决方案。
A
Adam Gordon Bell

如前所述,从函数返回字符串的“正确”方法是使用命令替换。如果函数还需要输出到控制台(如上面@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 来解决这个问题。丑,不过。
F
Fritz G. Mehner

您可以使用全局变量:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

这给

'some other string'

j
jmb

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

不过还是很恶心。


D
Daenyth

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


C
Community

考虑以下代码,解决 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 正确指出的那样,您可以在其他语言中找到它。


R
Ron Burk

我认为所有选项都已列举。选择一种可能归结为适合您的特定应用程序的最佳样式,因此,我想提供一种我发现有用的特定样式。在 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”的观点相反,我的理念是约定对于管理任何语言的复杂性总是很重要的。


C
Community

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

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

但是,可以让函数在内部使用固定的输出变量,然后在顶部添加一些糖以向调用者隐藏这个事实,就像我已经完成的那样以下示例中的 call 函数。将其视为概念证明,但关键点是

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

从调用者的角度来看,返回值可以分配给任何变量(本地或全局),包括 REPLY(参见包装器示例)。函数的退出代码是通过的,因此在 if 或 while 或类似结构中使用它们可以按预期工作。

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

之所以可行,是因为 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)

E
Evi1M4chine

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

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

echo

tadaa

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

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

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


在此处提到的不同解决方案之间来回切换后:“回复”解决方案是要走的路。没有样板文件,没有额外的变量,适应已经存在的东西——很简单。为此+1!
W
Will

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

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


麻烦的是管道右边的东西是一个子shell。所以 myfunc | read OUTPUT ; echo $OUTPUT 不会产生任何结果。 myfunc | ( read OUTPUT; echo $OUTPUT ) 确实获得了预期值并阐明了右侧发生的情况。但是当然 OUTPUT 在您需要的地方不可用...
A
Andrei Pozolotin

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
}

T
TimFinnegan

尽管有很多好的答案,但它们都没有按照我想要的方式工作。所以这是我的解决方案,其中包含以下关键点:

帮助健忘的程序员

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

l
loop

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

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}

a
agtsoft
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=