【问题标题】:The stdin of chained commands and commands in command substitution链式命令的标准输入和命令替换中的命令
【发布时间】:2018-10-05 08:20:29
【问题描述】:

让我先介绍我的发现,然后把我的问题放在最后。 (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) }

在第一行的{}里面,命令是打印命令行参数;在第二种形式中,该命令还从stdin 中读取一行。

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

我的问题是:

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

【问题讨论】:

  • head -n1; cat 是两个独立的命令。在那里,您在 braced-group (不是命令替换)中使用 head,它在 stdin 上接收 ls 的输出,然后您有效地调用 cat 而不使用任何参数 - 什么你预计会发生吗?
  • head 获取第一行,然后cat 获取其余行。也许chained 这个词让你感到困惑。我以前不知道正确的词。应该是grouped
  • 不,不应该分组。对于管道(例如foo | bar | baz),每个管道将前一个命令的stdout 与下一个命令的stdin 连接起来。在命令之间放置行终止符';' 与在脚本中包含两个单独的行相同。两者之间没有任何联系。如果你想要第一个文件ls -1 | head -n1。如果您想要除第一个以外的所有内容,那么 ls -1 | tail -n+2.
  • @david:在produce_data | { head -n1; cat} 中,我希望head 读取输入的开头,可能是8k 左右,输出第一行并退出。 cat 不带参数将读取标准输入中剩余的内容并将其打印到标准输出。如果我执行一个函数head_plus (){ head -n1; cat; },我会期待同样的结果。从这个意义上说,括号中的命令确实有一个连接,因为它们的读取与输入相同。不幸的是,head 是一个贪婪的虫子,读取的内容不止一行,因此 cat 剩下的输入更少。你预计会发生什么?
  • 我用不同的xxx 尝试了这个seq 1 xxx | { head -n1; cat; }cat 从第 1861 行开始打印,seq 1 1861 | wc 告诉它确实是 8198 字节。但是seq 1 2000 | { head -n1; head -n1; } 只输出一行,第二行head 什么也得不到。

标签: bash zsh


【解决方案1】:

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

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

如果输入来自常规文件,head 可以在终止之前返回到它使用的行的末尾。但是由于管道是不可搜索的,它不能那样做。如果您使用“here-strings”——<<< 语法,那么 stdin 将变成可搜索的,因为 here-strings 是使用临时文件实现的。不过,我不知道你是否可以相信这个事实。

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

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

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

(我将第二个head 更改为head -n2,因为第一个head 恰好将stdin 定位在行尾,因此第二个head 将空行视为第一个行。)

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

考虑以下小sn-p:

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

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

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

【讨论】:

  • 感谢您的回复。我在最后两个问题上得到了你的分数。但是是否有可能实现headcat 的共享标准输入?对于最后一段,我不明白这句话“shell可能会在执行封闭命令期间进行命令行替换”,尽管我知道你在它后面提到的事实。
  • 您在问题 2 和 3 中提到的内容是正确的。但我也发现这个链接INPUT FILES 提到了seekable files 的概念。像head 这样的标准实用程序在处理可搜索文件时应该尊重正确的文件偏移量。所以我尝试了以下{ head -n1; echo "==="; head -n1 } <<< $(ls) 并且成功了!
  • @LiuSha:好的,在Unix & Linux 上的帖子的帮助下,我试图在您的第一部分中提供一些关于 zsh 发生了什么的提示。我还对答案文本做了一些小的改进。希望对您有所帮助。
  • 非常感谢您花时间回答问题。现在一切都解释清楚了。我需要研究一下 zsh 的管道和重定向。从您引用的帖子来看,它的行为与 bash 完全不同。
  • @liuSha:我不是 zsh 用户,这就是为什么它在我当前的机器上配置不当的原因(在升级到 Ubuntu 18.04 之前我有一个工作安装,但我几乎没有使用过它。) Stéphane 说它与 zsh 的重定向多路复用有关,所以我先看看它。
猜你喜欢
  • 2011-12-13
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 2014-05-08
  • 1970-01-01
  • 2014-02-02
  • 1970-01-01
  • 2021-05-11
相关资源
最近更新 更多