假设以下脚本:
#!/bin/sh
func1() {
eval $1'=$(cat)'
eval echo "Value$2 inside function : \$$1"
}
func1 x 1 <<'HEREDOC'
Hello World
HEREDOC
echo "Value1 outside function: $x"
x=""
echo "Hello World" | func1 x 2
echo "Value2 outside function: $x"
在 bash 4.3.43-4.fc25 上,输出为:
Value1 inside function : Hello World
Value1 outside function: Hello World
Value2 inside function : Hello World
Value2 outside function:
使用 bashismshopt -s lastpipe
使最后一行也显示“Hello World”,但直觉上我不明白为什么这不会自动发生。这是预期的吗?
POSIX 标准似乎并没有真正讨论这个主题:http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_02
答案1
从历史上看,伯恩壳和科恩壳表现不同。由于管道的两侧在不同的进程中并行执行,因此不可能在脚本的其余部分中保留两侧的变量分配。给定unset a; a=b | a=c
,变量a
最终可能未设置,或等于b
,或等于c
;它不能同时等于b
和c
。
在 Bourne shell 中,管道运算符的每一侧都在子 shell 中运行(即在单独的 shell 进程中),因此不会保留变量赋值。在 Korn shell 中,管道的右侧在原始 shell 进程中执行。
正如在许多现有行为不同的情况下一样,POSIX 允许这两种行为。如本节所述“Shell执行环境”:
多命令管道中的每个命令都位于子shell环境中;然而,作为扩展,管道中的任何或所有命令都可以在当前环境中执行。所有其他命令将在当前 shell 环境中执行。
一些系统已经在当前环境中实现了管道的最后阶段,以便诸如以下命令:
command | read foo
foo
在当前环境中设置变量。此扩展是允许的,但不是必需的;因此,shell 程序员应该考虑管道位于子 shell 环境中,但不依赖于它。
实际上,除最后一个命令之外的其他命令始终在子 shell 中运行,除非您仔细查看创建的进程数,否则行为相同的某些优化除外。也就是说,没有 shell 有unset a; a=b | true; echo $a
print b
。
大多数其他类似 Bourne 的 shell,例如 ash、dash、bash、pdksh 和 mksh,其行为与 Bourne shell 类似。 Zsh 的行为类似于 Korn shell。最新版本的 bash 可以使用 切换到 ksh 行为shopt -s lastpipe
。
答案2
是的,POSIX 确实提到了这个话题。从这里:
子 shell 环境应创建为 shell 环境的副本,但未被忽略的信号陷阱应设置为默认操作。对子 shell 环境所做的更改不应影响 shell 环境。命令替换、用括号分组的命令以及异步列表应在子 shell 环境中执行。此外,多命令管道中的每个命令都位于子shell环境中;然而,作为扩展,管道中的任何或所有命令都可以在当前环境中执行。所有其他命令应在当前 shell 环境中执行。
它取决于 shell 选择其默认行为。在:
echo 1 | read x
echo "$x"
onlyzsh
和ksh
output 1
,其他现代的类似 Bourne 的 shell 输出空字符串。
答案3
多命令管道的每个命令都位于子 shell 环境中
然后
对子 shell 环境所做的更改不应影响 shell 环境
如果您考虑实现管道的最直接方法,这是有道理的 - 调用pipe()
,分叉一个子 shell,并交换一侧的标准输出/输入,然后在子 shell 中执行命令。这也是Bash 的定义行为。
你不能依靠不过,在这种情况发生时,因为
然而,作为扩展,管道中的任何或所有命令都可以在当前环境中执行
这就是lastpipe
该案例的作用。在其他一些 shell 中,这是某些情况下的默认行为。