我在 mac 中的 zsh 5.9 中观察到一个奇怪的行为。 (以下是重现错误的简化,它并不试图有意义)。如果我在终端中执行
$> function assigner() { OPTIONS=mutated }
$> function main() { typeset OPTIONS; assigner; echo $OPTIONS }
$> main
这输出:
mutated
但是如果我巧妙地更改 main 函数以重定向内部函数调用的输出......
$> function main() { typeset OPTIONS; assigner | cat; echo $OPTIONS }
然后结果就是空白
我的 OPTIONS var 不再填充。这里发生了什么?
答案1
管道是一种进程间通信机制。
在A | B
管道中,A
并与通过管道连接到标准输入的B
标准输出并行运行。A
B
因此它们必须在单独的进程中运行。在大多数 shell 中,它们都在子进程中运行;在 zsh 中,就像 ksh 的原始实现一样,仅A
在子进程中运行(尽管如果B
是外部命令,当然它仍然会在子进程中执行)。
因此assigner | cat
,由于assigner
在子 shell 进程中运行,因此它仅修改该子 shell 的变量,而不是父 shell 进程的变量。当管道终止时,对变量的更改将丢失。
在这里,如果您想assigner
在父进程中运行,但仍将其输出通过管道传输到cat
(在单独的进程中运行),您可以将assigner
的输出重定向到正在运行的进程替换cat
:
assigner > >(cat)
cat
或者作为协进程运行:
协进程将其 stdin 和 stdout 重定向到管道,因此主进程既可以向它们提供数据并从它们获取日期,但您可以通过使用额外的文件描述符保存和恢复原始 stdout 来取消 stdout 部分。
assigner() OPTIONS=mutated
{ coproc cat >&3 3>&-; } 3>&1 # start cat as coproc with stdout restored
cat_pid=$! # record its pid
assigner >&p # run assigner with stdout redirected to coproc
coproc : # dummy coproc to close the pipes to the
# previous one.
wait $cat_pid # make sure cat has returned before running
# the next commands.
echo $OPTIONS
(看如何在各种 shell 中使用 coproc 命令?有关如何使用协同进程的更多详细信息)。
或者,您可以利用assigner
在运行它的同一子 shell 中设置的变量,再次使用单独的 fd 使原始 stdout 可用:
assigner() OPTIONS=mutated
{
{
assigner
echo $OPTIONS >&3
} | cat
} 3>&1