链接命令和命令替换中的命令的标准

  • 本文关键字:命令 标准 替换 链接 bash zsh
  • 更新时间 :
  • 英文 :


让我先介绍我的发现,把我的问题放在最后。(1( 仅适用于zsh,(2(、(3( 适用于zshbash

1. 命令替换的标准

ls | echo $(cat)
ls | { echo $(cat) }

第一个打印cat: -: Input/output error;而第二个产生ls的输出。

2. 管道后的链接命令

ls | { head -n1; cat}
ls | { read a; cat}

第一个命令无法正常工作。cat遇到EOF,直接退出。但是第二种形式有效:第一行读入acat得到其余的。

3. 混合标准丁

ls | { python -c 'import sys; print(sys.argv)' $(head -n1) }
ls | { python -c 'import sys; print(sys.argv); print(input())' $(head -n1) }

在第一行的{}内,该命令用于打印cmdline参数;在第二种形式中,该命令还从stdin读取一行。

第一个命令可以成功运行,而第二个表单会因为读取EOF而抛出input()

我的问题是:

  1. (如第1节(有{}和没有的表格有什么区别?
  2. (如第2节(headcat是否可以按顺序读取相同的stdin?第二种形式如何成功,而第一种形式失败?
  3. (如第 3 节(命令替换中命令的stdin如何连接到原始命令的 stdin(此处echo(。谁先读书?以及如何使stdin保持打开状态,以便两个命令(pythonhead(可以按顺序读取相同的stdin

您没有考虑输入缓冲,它解释了您的大部分观察结果。

head每次需要数据时都会读取几千字节的输入,这使得它的效率大大提高。因此,它很可能会在任何其他进程有机会之前读取所有 stdin。这在案例 2 中很明显,其中执行顺序可能更清晰。

如果输入来自常规文件,head可以在终止之前查找到它使用的行的末尾。但是,由于管道是不可寻的,因此它无法做到这一点。如果你使用"here-strings"——<<<语法,那么stdin将是可搜索的,因为here-strings是使用临时文件实现的。不过,我不知道你是否可以依靠这个事实。

read不会缓冲输入,至少不会超出当前行(即使这样,也只有在命令行上没有指定其他行结束分隔符的情况下(。它只仔细读取它需要的内容,因为它通常用于其输入来自管道并且无法进行搜索的上下文中。这是非常有用的 - 以至于它工作的事实几乎是看不见的 - 但这也是shell脚本可能非常缓慢的原因之一。

你可以通过向管道发送足够的数据来满足head的初始读取,可以更清楚地看到这一点。试试这个,例如:

seq 1 10000 | { head -n1; head -n2; }

(我将第二个head更改为head -n2,因为第一个head恰好使stdin正好位于行尾,因此第二个head看到一个空行作为第一行。

您需要了解的另一件事是命令替换的作用以及何时执行。命令替换读取命令的整个输出并将其插入命令行。这甚至在命令被识别之前就会发生,更不用说开始执行了。

考虑以下小片段:

$(printf %cc%co e h) hello, world

应该很清楚,命令替换是在 echo 实用程序(或内置(启动之前完全执行的。

你的第一个场景触发了一个奇怪的zsh,Stéphane Chazelas在 Unix.SE 的这个答案中对此进行了解释。实际上,zsh在设置管道之前进行命令替换,因此cat从主 zsh 的标准输入中读取。(Stéphane 解释了为什么会这样以及它如何导致 EIO 错误。虽然我认为它取决于精确的 zsh 配置和选项设置,因为在我默认的 zsh 安装中,它只会锁定我的终端。在某些时候,我必须弄清楚为什么。如果使用大括号,则会在执行命令替换之前设置重定向。

最新更新