这是bash中的错误吗?如果从管道调用`return`不会退出功能


16

最近我在使用bash时遇到了一些奇怪的问题。在尝试简化脚本时,我想到了以下一小段代码:

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

return应该没有打印就退出了功能$?,不是吗?好吧,然后我检查了是否可以单独从管道返回:

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

没有while循环也会发生相同的情况:

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

我在这里想念什么吗?Google搜索对此一无所获!我的bash版本是Debian Wheezy上的4.2.37(1)-发行版。


我在答复中建议的设置有什么问题,该设置允许脚本以您期望的直观方式运行?
jlliagre

@jlliagre这是成千上万行的相当复杂的脚本。考虑到破坏其他事物,我宁愿避免在函数中运行管道,因此我用流程替换代替了它。谢谢!
Teresa e Junior

如果while不需要复制前两个示例,为什么不删除它们呢?它分散了重点。
与莫妮卡(Monica)进行的种族竞速赛

@LightnessRacesinOrbit while对于带有的管道,循环是非常常见的用法return。第二个例子更直接了,但这是我不相信任何人都会使用的东西
Teresa e Junior

1
不幸的是,我的正确答案已被删除...您处于灰色区域,因为您执行未指定的操作。行为取决于外壳如何解释管道,即使ksh源自sh源,Bourne Shell和Korn Shell之间的差异也是如此。在Bourne Shell中,while循环位于子外壳中,因此您可以看到回声与bash相同。在ksh中,while循环是前台进程,因此ksh不会在示例中调用echo。
schily

Answers:


10

相关:https : //stackoverflow.com/a/7804208/4937930

您无法通过子Shell exitreturn在子Shell中退出脚本或从函数返回不是错误。它们在另一个进程中执行,并且不影响主进程。

除此之外,我想您在(可能)未定义的规范上看到了bash的未记录的行为。在函数中,不会return在子shell命令的顶层声明任何错误,并且其行为类似于exit

恕我直言,这是一个return依赖于主语句是否在函数中的不一致行为的bash错误。

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

输出:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23

缺少错误详细程度可能没有记录。但是return,现有文档已经使我期望一个事实,该事实不适用于子Shell中的顶级命令序列,尤其是不能退出该子Shell。OP可以exit 1 || return 1在他们尝试使用的地方使用return,然后应该获得预期的行为。编辑:@herbert的答案表明return子外壳中的顶级功能是exit(但仅来自子外壳)。
dubiousjim 2015年

1
@dubiousjim更新了我的脚本。我的意思是return任何情况下都应在一个简单的subshel​​l序列中将其断言为运行时错误,但实际上并非在功能发生时才断言。gnu.bash.bug中也讨论了这个问题,但是没有结论。
yaegashi

1
您的答案不正确,因为未指定while循环是在子shell中还是前台进程中。不管实际shell的实现如何,该return语句都具有功能,因此是合法的。但是,所得行为是不确定的。
2015年

当bash手册页中记录了管道组件位于子外壳中时,您不应该将其写为未记录的行为。当POSIX指定允许的行为时,您不应该写这种行为可能是基于未定义的规范。当bash遵循POSIX标准时,您不应该怀疑bash错误,因为它允许返回一个函数,但不允许在外部返回。
jlliagre 2015年

17

它不是错误,bash而是其记录的行为

管道中的每个命令都在其自己的子shell中执行

return指令在函数定义内有效,但也位于子外壳中,它不会影响其父外壳,因此echo无论如何都将执行下一条指令。但是,它是一种非便携式的shell结构,因为POSIX标准允许在子shell(默认)或顶层(允许的扩展名)中执行组成管道的命令。

另外,多命令管道中的每个命令都在子shell环境中。但是,作为扩展,流水线中的任何或所有命令都可以在当前环境中执行。所有其他命令应在当前的外壳环境中执行。

希望您可以bash通过以下两种选择来告诉您期望的行为:

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here

1
由于return不会退出该函数,如果仅打印外壳程序bash: return: can only `return' from a function or sourced script,不是给用户一种错误的感觉,那就是该函数可能已经返回,这是否更有意义?
Teresa e Junior

2
我没有在文档中看到任何地方说subshel​​l内的返回是有效的。我敢打赌,此功能是从ksh复制而来,函数或源脚本外部的return语句的行为类似于 exit。我不确定原始的Bourne外壳。
cuonglm

1
@jlliagre:也许Teresa对她所要求的术语感到困惑,但是我不明白为什么如果return从子shell 执行a,bash发出诊断消息将是“棘手的” 。毕竟,它确实知道它在子外壳中,正如$BASH_SUBSHELL变量所证明的那样。最大的问题是,这可能导致误报。一个了解子外壳如何工作的用户可以编写脚本来return代替exit终止子外壳。(当然,在某些情况下,可能需要设置变量或cd在子shell中执行a 。)
Scott Scott

1
@斯科特,我想我对情况很了解。管道创建一个子外壳,并且return从子外壳返回而不是失败,因为它在实际函数中。问题是help return具体指出:Causes a function or sourced script to exit with the return value specified by N.从阅读文档开始,任何用户都希望它至少会失败或打印警告,但永远不会像那样运行exit
Teresa e Junior

1
在我看来,任何希望函数return 的子外壳中的内容从函数返回(在主壳过程中)的人都不怎么了解子外壳。相反,我希望读者谁明白子shell期待return 在子shell中的函数终止子shell,就像exit会。
斯科特

6

根据POSIX文档,未指定在函数或源脚本之外使用return。因此,这取决于您的外壳来处理。

在以下情况下,SystemV Shell将报告错误: kshreturn函数或源脚本之外,行为类似于exit。其他大多数POSIX外壳和schily的osh的行为也一样:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

ksh并且zsh没有输出,因为这些shell中管道的最后一部分是在当前shell中而不是subshel​​l中执行的。return语句影响了当前调用该函数的shell环境,导致该函数立即返回而不打印任何内容。

在交互式会话中,bash仅报告错误但没有终止外壳,没有schily's osh报告错误并终止外壳:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

zsh在交互式会话中,输出不会终止终端bashyash并且schily's osh报告了错误但未终止外壳)


1
可以说return函数内部使用的。
jlliagre

1
@jlliagre:不确定您的意思return是在功能内的subshel​​l中使用,除了和。kshzsh
cuonglm

2
我的意思是说,位于本身在函数内部的子外壳中并不一定意味着就在该函数外部,即,在标准状态下,管道组件中的任何内容都不应视为位于它们所在的函数之外。开放小组应澄清这一点。
jlliagre 2015年

3
我想不是。那是功能之外的。调用函数的外壳和执行返回的子外壳是不同的。
cuonglm

我理解您的合理解释问题的理由,我的观点是根据POSIX标准中描述的shell语法,管道是复合列表的一部分,而复合列表是复合命令的一部分,复合命令是函数的主体。没有任何地方指出在功能之外要考虑管道组件。就像我在开车,那辆车停在车库一样,我也可以假设我也在那个车库里;-)
jlliagre

4

我认为您得到了预期的效果,在bash中,管道中的每个命令都在子shell中执行。您可以通过尝试修改函数的全局变量来征服自己:

foo(){ x=42; : | x=3; echo "x==$x";}

顺便说一句,返回是有效的,但它是从子shell返回的。同样,您可以检查以下内容:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

将输出以下内容:

1
This should not be printed.

所以return语句正确退出了子外壳


2
因此,要退出该功能,请使用foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?,您将获得的结果2。但是为了清楚起见,我将return 1成为exit 1
dubiousjim 2015年

顺便说一句,管道的所有成员(除了一个以外的所有成员)都在子shell中执行,这一事实是否有根据?
Incnis Mrsi 2015年

@IncnisMrsi:参见jlliagre的答案
斯科特

1

更为笼统的答案是,bash和其他一些shell通常会将管道的所有元素放入单独的进程中。当命令行为

程序1 | 程序2 | 程序3

因为程序通常无论如何都在单独的进程中运行(除非您说)。但这可能会让您感到意外exec program

命令1 | 命令2 | 命令3

其中部分或全部命令是内置命令。简单的例子包括:

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

一个更现实的例子是

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

在整个while... do... done循环被放入一个子进程,所以它的变化t是不循环结束后,主壳可见。这正是您正在执行的工作– while循环到一个循环中,使该循环作为子Shell运行,然后尝试从子Shell中返回。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.