Bash 函数中的返回值

Posted

技术标签:

【中文标题】Bash 函数中的返回值【英文标题】:Return value in a Bash function 【发布时间】:2013-06-24 14:04:34 【问题描述】:

我正在使用 bash 脚本,我想执行一个函数来打印返回值:

function fun1()
  return 34

function fun2()
  local res=$(fun1)
  echo $res

当我执行fun2 时,它不会打印“34”。为什么会这样?

【问题讨论】:

return 在您的情况下与exit code 基本相同,范围为0 - 255。按照@septi 的建议使用echo。可以使用$? 捕获退出代码。 在这种情况下,已经在 fun1 中使用 echo 会更加灵活。这是 unix 编程的想法:echo 将结果发送到标准输出,然后可以通过 res=$(fun1) 被其他函数重用 - 或者直接通过管道传递给其他函数:function a() echo 34; function b() while read data; do echo $data ; done ;a | b跨度> 正确的做法是将顶层的东西放在一个函数中,并使用带有 bash 动态范围规则的本地。我将创建一个答案来演示,它不是众所周知的功能,但完全受支持。 另见:***.com/a/8743103/12887 见linuxjournal.com/content/return-values-bash-functions 【参考方案1】:

虽然 Bash 有一个 return 语句,但您唯一可以用它指定的是函数自己的 exit 状态(0255 之间的值,0 表示“成功”)。所以return 不是你想要的。

您可能希望将您的 return 语句转换为 echo 语句 - 这样您的函数输出就可以使用 $() 大括号捕获,这似乎正是您想要的。

这是一个例子:

function fun1()
  echo 34


function fun2()
  local res=$(fun1)
  echo $res

另一种获取返回值的方法(如果你只想返回一个 0-255 的整数)是$?

function fun1()
  return 34


function fun2()
  fun1
  local res=$?
  echo $res

另外,请注意,您可以使用返回值来使用布尔逻辑 - 例如,fun1 || fun2 将仅在fun1 返回非0 值时运行fun2。默认返回值是函数内执行的最后一条语句的退出值。

【讨论】:

需要执行fun1,然后返回值存入$?。虽然我不建议这样做…… 不,我需要该死的返回值。回声见鬼去吧。 fun1 || fun2 will only run fun2 if fun1 returns a 0 value. 不应该是a non-0 value吗? @Blauhirn 在此环境中,使用此 || 构造,退出代码 0 被视为成功,因此为“真”。非零是错误,因此是错误的。将fun1 || fun2 视为“如果 fun1 返回成功或 fun2 返回成功”的简写,而不是实际返回值本身的运算符。 令人讨厌的是,一个应该提供数据的函数也不能将其他内容回显到标准输出,因为使用 $() 的调用者也会收到它并感到困惑或不得不解析输出。全局变量不是很好,因为在两个碰巧嵌套的地方使用相同的全局变量只是时间问题,数据可能会丢失。打印数据和发回数据应该有单独的通道。【参考方案2】:

$(...) 捕获由其中包含的命令发送到标准输出的文本。 return 不输出到标准输出。 $? 包含最后一条命令的结果代码。

fun1 ()
  return 34


fun2 ()
  fun1
  local res=$?
  echo $res

【讨论】:

Yes return 用于设置$?,即exit status。在上面的示例中,fun1exit status 将是 34。另外,请注意$(...) 除了从指定的命令中捕获标准输出之外,还捕获标准错误。【参考方案3】:

Bash 中的函数不同于其他语言中的函数;它们实际上是命令。因此,函数的使用就像它们是从您的路径中获取的二进制文件或脚本一样。从您的程序逻辑的角度来看,应该没有任何区别。

Shell 命令是通过管道(也称为流)连接的,而不是像在“真正的”编程语言中那样的基本或用户定义的数据类型。没有像命令的返回值这样的东西,可能主要是因为没有真正的方法来声明它。它可能出现在手册页或命令的--help 输出中,但两者都是人类可读的,因此被写入风中。

当一个命令想要获取输入时,它会从其输入流或参数列表中读取它。在这两种情况下,都必须解析文本字符串。

当一个命令想要返回一些东西时,它必须echo 它到它的输出流。另一种常用的方法是将返回值存储在专用的全局变量中。写入输出流更清晰、更灵活,因为它也可以接收二进制数据。例如,您可以轻松返回BLOB:

encrypt() 
    gpg -c -o- $1 # Encrypt data in filename to standard output (asks for a passphrase)


encrypt public.dat > private.dat # Write the function result to a file

正如其他人在此线程中所写,调用者也可以使用命令替换$() 来捕获输出。

并行,该函数将“返回”gpg (GnuPG) 的退出代码。将退出代码视为其他语言没有的奖励,或者根据您的气质,将其视为 shell 函数的“Schmutzeffekt”。按照惯例,此状态为 0 表示成功,或 1-255 范围内的整数表示其他值。为了明确这一点:return(如exit)只能取 0-255 之间的值,而 0 以外的值不一定是错误,正如经常断言的那样。

当您没有为 return 提供显式值时,状态将取自 Bash 语句/函数/命令等中的最后一个命令。所以总是有一个状态,return 只是提供它的一种简单方法。

【讨论】:

+1 用于解释函数与命令以及这如何影响将数据发送回调用者的概念 +1 用于解释 shell 编程是关于通过管道连接命令。其他编程语言通过返回类型组成函数。 Bash 通过文本流编写命令。 如果一个函数必须同时做这两个怎么办?也就是说,从脚本发送一些输出,并产生一些文本作为它的返回值,这不应该被这个函数必须记录到脚本的标准输出的任何东西所干扰。 tldp.org/LDP/abs/html/complexfunct.html Bash:函数返回一个值,称为退出状态。这类似于命令返回的退出状态。退出状态可以通过 return 语句显式指定,否则为函数中最后一个命令的退出状态(成功时为 0,否则为非零错误代码)。此退出状态可以通过将其引用为 $? 来在脚本中使用。这种机制有效地允许脚本函数具有类似于 C 函数的“返回值”。【参考方案4】:

其他答案的问题是它们要么使用全局,当调用链中有多个函数时可以覆盖它,或者echo,这意味着您的函数无法输出诊断信息(您会忘记您的函数执行此操作并且“结果”,即返回值,将包含比调用者预期更多的信息,从而导致奇怪的错误),或者eval,这太沉重和hacky。

正确的做法是将顶层的东西放在一个函数中,并使用local 和 Bash 的动态范围规则。示例:

func1()

    ret_val=hi


func2()

    ret_val=bye


func3()

    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val


func3

这个输出

nothing
hi
bye

动态范围意味着ret_val 指向不同的对象,具体取决于调用者!这与大多数编程语言使用的词法作用域不同。这实际上是a documented feature,只是很容易错过,而且解释得不是很好。这是它的文档(重点是我的):

函数的局部变量可以用局部变量声明 内置。这些变量仅对函数 和 它调用的命令

对于具有 C、C++、Python、Java、C# 或 javascript 背景的人来说,这可能是最大的障碍:bash 中的函数不是函数,它们是命令,并且行为如下:它们可以输出到 @987654331 @/stderr,他们可以通过管道输入/输出,并且可以返回退出代码。基本上,在脚本中定义命令和创建可以从命令行调用的可执行文件之间没有任何区别。

所以不要像这样写你的脚本:

Top-level code
Bunch of functions
More top-level code

这样写:

# Define your main, containing all top-level code
main()
Bunch of functions
# Call main
main

其中main()ret_val 声明为local,所有其他函数通过ret_val 返回值。

另请参阅Unix & Linux 问题Scope of Local Variables in Shell Functions

另一个可能更好的解决方案取决于具体情况,是使用local -n 的posted by ya.teck。

【讨论】:

根据我的阅读,local 与 POSIX 不兼容,而且几乎每个 shell 的实现方式都不同,因此它不是一个可移植的解决方案。 如果你在这个例子中只删除了 local 内置函数,究竟会有什么不同? (因为这几乎就是我有时使用它的方式......)【参考方案5】:

return 语句设置函数的退出代码,与 exit 对整个脚本的设置非常相似。

最后一个命令的退出代码始终在 $? 变量中可用。

function fun1()
  return 34


function fun2()
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34

【讨论】:

res的值是多少? $res 始终为空,因为fun1 没有标准输出【参考方案6】:

实现此目的的另一种方法是name references(需要 Bash 4.3+)。

function example 
  local -n VAR=$1
  VAR=foo


example RESULT
echo $RESULT

【讨论】:

任何人都想知道-n &lt;name&gt;=&lt;reference&gt; 做了什么:使新创建的变量成为对&lt;reference&gt; 指向的另一个变量的引用。对引用的变量执行对&lt;name&gt; 的进一步赋值。 Re "name references":你的意思是"named references"(不是反问句)? @PeterMortensen 文档使用术语“nameref” 请记住 zsh 不理解 -n 标志。【参考方案7】:

作为其他优秀帖子的补充,这里有一篇总结这些技术的文章:

设置一个全局变量 设置一个全局变量,您将其名称传递给函数 设置返回码(用$取回?) 'echo' 一些数据(并使用 MYVAR=$(myfunction) 获取)

Returning Values from Bash Functions

【讨论】:

这是最好的答案,因为文章清楚地讨论了所有选项。 + 写入文件。 json/yaml 如果需要结构【参考方案8】:

如果在定义函数的脚本中运行,我喜欢执行以下操作:

POINTER= # Used for function return values

my_function() 
    # Do stuff
    POINTER="my_function_return"


my_other_function() 
    # Do stuff
    POINTER="my_other_function_return"


my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

我喜欢这个,因为如果我愿意,我可以在我的函数中包含回显语句

my_function() 
    echo "-> my_function()"
    # Do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"

【讨论】:

【参考方案9】:

您可以创建一个使用eval 修改输入参数的函数,而不是使用整个函数输出调用var=$(func)

var1="is there"
var2="anybody"

function modify_args() 
    echo "Modifying first argument"
    eval $1="out"
    
    echo "Modifying second argument"
    eval $2="there?"


modify_args var1 var2
# Prints "Modifying first argument" and "Modifying second argument"
# Sets var1 = out
# Sets var2 = there?

如果您需要,这可能很有用:

    在函数范围内打印到 stdout/stderr(不返回) 返回(设置)多个变量。

【讨论】:

【参考方案10】:

Git Bash 在 Windows 上使用数组作为多个返回值

Bash 代码:

#!/bin/bash

## A 6-element array used for returning
## values from functions:
declare -a RET_ARR
RET_ARR[0]="A"
RET_ARR[1]="B"
RET_ARR[2]="C"
RET_ARR[3]="D"
RET_ARR[4]="E"
RET_ARR[5]="F"


function FN_MULTIPLE_RETURN_VALUES()

   ## Give the positional arguments/inputs
   ## $1 and $2 some sensible names:
   local out_dex_1="$1" ## Output index
   local out_dex_2="$2" ## Output index

   ## Echo for debugging:
   echo "Running: FN_MULTIPLE_RETURN_VALUES"

   ## Here: Calculate output values:
   local op_var_1="Hello"
   local op_var_2="World"

   ## Set the return values:
   RET_ARR[ $out_dex_1 ]=$op_var_1
   RET_ARR[ $out_dex_2 ]=$op_var_2



echo "FN_MULTIPLE_RETURN_VALUES EXAMPLES:"
echo "-------------------------------------------"
fn="FN_MULTIPLE_RETURN_VALUES"
out_dex_a=0
out_dex_b=1
eval $fn $out_dex_a $out_dex_b  ## <-- Call function
a=$RET_ARR[0] && echo "RET_ARR[0]: $a "
b=$RET_ARR[1] && echo "RET_ARR[1]: $b "
echo
## ---------------------------------------------- ##
c="2"
d="3"
FN_MULTIPLE_RETURN_VALUES $c $d ## <--Call function
c_res=$RET_ARR[2] && echo "RET_ARR[2]: $c_res "
d_res=$RET_ARR[3] && echo "RET_ARR[3]: $d_res "
echo
## ---------------------------------------------- ##
FN_MULTIPLE_RETURN_VALUES 4 5  ## <--- Call function
e=$RET_ARR[4] && echo "RET_ARR[4]: $e "
f=$RET_ARR[5] && echo "RET_ARR[5]: $f "
echo
##----------------------------------------------##


read -p "Press Enter To Exit:"

预期输出:

FN_MULTIPLE_RETURN_VALUES EXAMPLES:
-------------------------------------------
Running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[0]: Hello
RET_ARR[1]: World

Running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[2]: Hello
RET_ARR[3]: World

Running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[4]: Hello
RET_ARR[5]: World

Press Enter To Exit:

【讨论】:

为什么 Git Bash 会有所不同?解释是什么? 我不知道。但有时当我在堆栈溢出时,我只想找到一个在我的环境中有效的答案而不去考虑它。

以上是关于Bash 函数中的返回值的主要内容,如果未能解决你的问题,请参考以下文章

Bash条件混合函数返回值和条件检查[重复]

bash脚本返回值应用

在shell脚本中使用函数的返回值

C中的bash条件扩展[重复]

如何避免作为 sql 查询输出的一部分返回的字符串值被拆分为 bash/shell 脚本中数组中的不同字段

bash命令行返回值和展开