我最近在使用 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.
我在这里缺少什么吗?谷歌搜索没有找到任何结果!我的 bash 版本是4.2.37(1)-发布在 Debian Wheezy 上。
答案1
这不是一个错误bash
,但它记录的行为:
管道中的每个命令都在其自己的子 shell 中执行
该return
指令在函数定义内有效,但在子 shell 中也有效,它不会影响其父 shell,因此echo
无论如何都会执行下一条指令 。然而,它是一种非便携式外壳结构,因为POSIX标准允许组成管道的命令在子 shell(默认)或顶层(允许的扩展)中执行。
此外,多命令管道中的每个命令都位于子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
答案2
有关的:https://stackoverflow.com/a/7804208/4937930
exit
您无法通过子 shell 退出脚本或从函数返回,这并不是一个错误return
。它们在另一个进程中执行,不影响主进程。
除此之外,我想您会在(可能)未定义的规范上看到 bash 的未记录行为。在函数中,return
子 shell 命令的顶层不会断言任何错误,它的行为就像exit
.
恕我直言,这是一个 bash 错误,它的行为不一致,return
取决于主语句是否在函数中。
#!/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
答案3
根据 POSIX 文档,未指定使用return
函数外部或源脚本。所以,这取决于你的外壳来处理。
SystemV shell 将报告错误,而在函数内ksh
、return
函数外或源脚本的行为类似于exit
.大多数其他 POSIX shell 和希利的奥什也有这样的行为:
$ 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 而不是子 shell 中执行的。 return 语句影响了调用该函数的当前 shell 环境,导致函数立即返回而不打印任何内容。
在交互式会话中,bash
只报告错误但不终止shell,schily's osh
报告错误并终止shell:
$ 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
在交互式会话中,输出是终端不终止,bash
并yash
报告schily's osh
错误但没有终止 shell)
答案4
更普遍的答案是,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
循环被放入子进程中,因此循环结束后主 shell 无法看到其更改。这正是您正在做的事情 - 管道进入循环,使循环作为子 shell 运行,然后尝试从子 shell 返回。do
done
t
while