我在 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 不再填充。这里发生了什么?
管道是一种进程间通信机制。
在
A | B
管道中,A
并与通过管道连接到标准输入的B
标准输出并行运行。A
B
因此它们必须在单独的进程中运行。在大多数 shell 中,它们都在子进程中运行;在 zsh 中,就像 ksh 的原始实现一样,仅
A
在子进程中运行(尽管如果B
是外部命令,当然它仍然会在子进程中执行)。因此
assigner | cat
,由于assigner
在子 shell 进程中运行,因此它仅修改该子 shell 的变量,而不是父 shell 进程的变量。当管道终止时,对变量的更改将丢失。在这里,如果您想
assigner
在父进程中运行,但仍将其输出通过管道传输到cat
(在单独的进程中运行),您可以将assigner
的输出重定向到正在运行的进程替换cat
:cat
或者作为协进程运行:协进程将其 stdin 和 stdout 重定向到管道,因此主进程既可以向它们提供数据并从它们获取日期,但您可以通过使用额外的文件描述符保存和恢复原始 stdout 来取消 stdout 部分。
(有关如何使用协进程的更多详细信息,请参阅如何在各种 shell 中使用 coproc 命令? )。
或者,您可以利用
assigner
在运行它的同一子 shell 中设置的变量,再次使用单独的 fd 使原始 stdout 可用: