为什么是“echo foo | 读一个; echo $ a“不按预期工作?

我可以在FreeBSD,GNU / Linux和Solaris下复制这个问题。 这让我头疼了一个多小时,所以我决定在这里提问。

由于管道read在其自己的子shell中执行。

 echo foo | while read a; do echo $a; done 

会做你所期望的。

替代方案:

 echo foo | (read a ; echo $a) 

编辑:

如果你需要在子shell之外$ a,你必须反转这些命令:

 read a < <(echo foo); echo $a 

这样读取在当前进程中执行

我不认为这个已经被给出了:

 a=`echo foo` echo $a 

只是供参考 在ksh它正在按预期工作; 另见http://kornshell.com/doc/faq.html ,第三部分(shell编程问题),Q13:

 Q13. What is $bar after, echo foo | read bar? A13. The is foo. ksh runs the last component of a pipeline in the current process. Some shells run it as a subshell as if you had invoked it as echo foo | (read bar). 

| 是一个进程间通信运营商。 所以,隐含地说,必须创建一个子过程来解析和评估表达式的一方或另一方。 Bash和老的Bourne shell在操作符的右边创建一个子进程(从管道读取),这意味着在那里设置的任何变量只在该进程退出的范围内(在代码示例中以分号表示)。

zsh和更新版本的Korn shell(至少从'93开始,但可能甚至早于ksh'88)将在管道的另一侧(写入到它)中创建子进程。 因此,他们将以这个问题的作者的意图来工作。 (当然,“如预期的”当然是非常主观的,理解管道的本质应该引导人们期望它以具体实现的方式表现)。

我不知道在Posix或Spec 1170或SuS或任何其他公布的标准中是否有任何特定的规定需要其中一个或另一个语义。 但是,在实践中,显然你不能依赖于行为; 尽管您可以在脚本中轻松测试它。

我想出了一个解决方案,它不会隐藏子shell中的变量值,也可以使用多个值。

 set `echo foo bar` A=$1 B=$2 

根据coreighan和Pike的Unix编程环境 (第159页),“shell内置的命令(与控制流原语相对)都可以用>和<”重定向,“这可能被描述作为shell中的一个bug“。 这似乎解释了a)为什么代码像

 ls *.c | while read file do echo $a done 

总是毫无问题地工作,和b)从文件重定向的不一致性。

阅读期望在新的一行输入

同时读一个; 做echo $ a;  DONE
 FOO
酒吧
 ^ d

是或多或少你想要的