Bash函数中'return'语句的行为

27

我对Bash内置的return命令的行为感到困惑。这是一个示例脚本。

#!/bin/bash

dostuff() {
    date | while true; do
        echo returning 0
        return 0
        echo really-notreached
    done

    echo notreached
    return 3
}

dostuff
echo returncode: $?
这个脚本的输出是:
returning 0
notreached
returncode: 3

然而,如果将第4行中的date | 删除,输出结果将与我预期的相同:

returning 0
returncode: 0

看起来像上面使用的return语句的作用方式,正是我认为break语句应该表现的方式,但仅当循环在管道的右侧时才是如此。为什么会这样?我在Bash man page或网络上找不到任何解释这种行为的内容。该脚本在Bash 4.1.5和Dash 0.5.5中的行为相同。


有趣。是的,似乎return的行为有点像break;同样,如果语句中有返回值,你可以看到这种情况发生。除非if表达式非常简单。 - gaoithe
5个回答

33

date | while ...的情况下,由于存在管道符,while循环是在子shell中执行的。因此,return语句会中断循环,导致子shell结束,函数继续执行。

您需要重构代码以去除管道以避免创建子shell:

dostuff() {
    # redirect from a process substitution instead of a pipeline
    while true; do
        echo returning 0
        return 0
        echo really-notreached
    done < <(date)

    echo notreached
    return 3
}

7

如果你在函数内部使用return语句,那么该函数会停止执行,但整个程序不会退出。

如果你在函数内部使用exit语句,整个程序将会退出。

在Bash脚本的主体中不能使用return语句。你只能在函数或已被引用的脚本中使用return


例如:

#!/usr/bin/env bash

function doSomething {
    echo "a"
    return
    echo "b"  # this will not execute because it is after 'return'
}

function doSomethingElse {
    echo "d"
    exit 0
    echo "e"  # this will not execute because the program has exited
}

doSomething
echo "c"
doSomethingElse
echo "f"  # this will not execute because the program exited in 'doSomethingElse'

运行上述代码将输出:
a
c
d

原始问题确实是在函数内返回的,因此尽管这个答案很有信息量,但它并没有回答这个问题。 - Cigarette Smoking Man

3
但是return应该终止函数调用,而不是子shell。exit旨在终止(子)shell。我认为这是一些未记录的错误/功能。
  • echo | return在命令行中输入会导致错误。这是正确的 - return应该在一个函数中。
  • f(){ echo|return; }在Bash和Dash中被接受,但return不能终止函数调用。
如果return终止子shell,它将在函数外起作用。因此,结论是:return终止函数中的子shell,这很奇怪。

1
这是一个支持结论的演示:function foo { echo start function; ( echo start subshell; return; echo end subshell); echo end function; } - glenn jackman
这似乎是一个很好的解释。在if或while语句中使用简单表达式不会导致子shell运行,因此return将表现得像正常/预期的一样。更复杂的表达式(例如,在表达式中使用变量)将导致子shell,因此return看起来像是一个break;。 - gaoithe

2
为了介绍 Bash 的这个有趣特性...
  • if 语句内的 return(或任何带有 if/while/... 表达式的控制命令)

  • 在 if 语句内使用简单和复杂表达式的 return

子 shell 的解释很好。控制从当前子 shell 中退出。这可能是 Bash 函数,也可能是任何嵌套的控制命令,该命令具有导致调用子 shell 的表达式。

  1. 对于非常简单的表达式,例如 "true" 或 "1 == 1",不会调用子 shell。因此,return 的行为就像正常情况一样。

  2. 对于较不简单的表达式,例如与某些内容进行比较的变量扩展,则 return 的行为类似于 break

简单(无子 shell)示例:

$ rtest () { if true; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if [[ 1 == 1 ]] ; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if [[ 1 =~ 1 ]] ; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if $DO ; then echo one; return 2; echo two; else echo three; return 3; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if [[ $DO ]]; then echo one; return 2; echo two; else echo three; return 3; fi; echo not simple; return 7; }
$ rtest
three
$ echo $?
3

$ rtest () { if [[ $DO == 1 ]] ; then echo one; return 2; echo two; else echo three; return 3; echo four; fi; echo not simple; return 7; }
$ rtest; echo $?
one
2
$ DO=1; rtest; echo $?
one
2
$ DO=0; rtest; echo $?
three
3

表达式不简单且假定子shell被调用,return行为类似于break

不简单(子shell)示例...在[[ ]]中使用=~

$ rtest () { if [[ $DO =~ 1 ]] ; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
not simple
$ echo $?
7

1
事实上,子shell是一个独立的进程。它没有办法告诉父shell:“我因为返回而退出”。在退出状态中也没有这样的东西,这是父shell所获取到的唯一信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接