【问题标题】:How Consistent is Order-Ambiguous Shell I/O Redirection?顺序不明确的 Shell I/O 重定向的一致性如何?
【发布时间】:2017-08-31 22:19:12
【问题描述】:

这篇引人入胜的帖子:

How is this command legal ? “> file1 < file2 cat”

在看似格式错误的cat 调用“shell”(Linux shell,可能是 BASH)中突出显示了令人惊讶的行为。基本上,shell 似乎能够从一系列字符串中不明确的位置抓取可执行文件,然后使用 I/O 重定向到流/文件描述符。

我理解的基本流程是:

  1. 寻找重定向模式和read them into or out of appropriate streams / file descriptors(例如:1&gt; (stdout))(这发生在命令中启动可执行进程之前!(例如cat 调用))
  2. 在字符串列表中查找可执行进程。
  3. 启动那个可执行进程
  4. 暂停处理完成或继续(根据需要)在步骤 1 中检测到的基于类型的输出。

这导致了一些令人惊讶的逻辑。例如在执行echo "dog" &gt; cat后的新目录中:

  • &lt;cat cat &gt;dog:使用shell工具cat将“狗”从文件cat写入dog

  • &lt;cat cat&gt; cat cat :覆盖第一个命令,留下一个空白的cat 文件(不确定在第二个命令中间会发生什么)。

  • &lt;cat cat&gt; cat cat &gt;dog 2&gt;more:创建空文件dogmore,用空文件覆盖cat文件。

  • &lt;cat &gt;dog cat cat &lt;dog &gt;cat(创建空文件dog,用空文件覆盖cat

  • &lt;cat cat &gt;dog 2&gt;much 1&gt;more:用空文件覆盖cat;创建文件dog/more,每个文件都包含字符串“dog”,创建空much

(以上列表行为已在BASH (v4.3.46) 上测试。)

现在在某个时候,可怜的外壳决定它已经受够了。例如,当遇到:

&lt;cat dog&gt; cat cat &gt;dog &gt;cat

它抱怨:

bash: dog: 找不到命令

但还有一个额外的惊喜 - 该命令实际上已部分完成。与上述大多数示例一样,它用空白文件覆盖文件cat,并创建了一个空白文件dog

为了更好地理解“most popular Linux shells”和 CMD(标准 Windows shell)中复杂的 I/O 重定向处理:

  1. BASH (Linux)
  2. TCSH (Linux)
  3. KSH (Linux)
  4. ZSH (Linux)
  5. CMD (Windows)

...是这种order-ambiguous I/O重定向解析...

  1. 都支持? (我只有时间测试BASH(Linux)和cmd(Windows)。)
  2. 它支持所有受支持的可执行文件还是仅支持核心 shell 实用程序?
  3. 这些 shell 使用哪些规则来处理流/描述符的清理/排序,特别是在解析基于子字符串的选择而重定向显得不明确的命令时(例如,stuff.dat&gt;1test.dat&lt;2test.dat 其中1test.dat 和@987654360 @ 是文件)
  4. shell 之间的解析规则在多大程度上是一致的?
  5. 是什么决定了这些 shell 中具有复杂 I/O 重定向模式的命令失败?

【问题讨论】:

标签: bash shell parsing cmd io-redirection


【解决方案1】:

对于 POSIX shell(即尝试实现 Posix 标准的 shell),解析算法实际上相当简单,并且在该标准中也有说明。这包括列表中的bashkshzsh(以及其他,例如dash),但不包括Windows cmdtcsh 类似,但不是 Posix。

重定向不是“顺序模糊”。它们从左到右被解析和执行。唯一可能奇怪的部分是它们可能与命令及其参数任意交错,但由于每个重定向之前都有一个重定向运算符,因此不会产生歧义。

对于simple commands,流程大致是:

  1. 命令被分割成单词。重定向操作符前面的词是重定向;这些将从命令中删除并保存以供以后处理。

    请注意,重定向运算符是自定界的,因此a&gt; ba &gt;ba&gt;b 之间没有任何区别。所有这些都是单词a,重定向运算符&gt;,单词b&gt;b 将被视为重定向。因此,&lt;a&gt; b 的语法可能会让人类读者感到困惑(因此应该避免),但它不会让 shell 感到困惑,它会将其视为以更正常的方式编写为 &lt;a &gt;b

  2. ID= 开头的引导词是赋值(其中ID 是任何看起来像变量名的东西)。这些也被删除以供以后处理。与重定向不同,这些是唯一被识别的直到第一个单词(如果有的话),这不是一个赋值。

  3. 剩余的单词(如果有)根据扩展规则进行扩展,这可能涉及拆分扩展的单词。展开后的第一个词(如果有的话)是命令,其余的词是命令参数。

  4. 重定向从左到右执行。输出重定向 (&gt;foo) 创建或截断命名文件;附加重定向 (&gt;&gt;foo) 仅创建文件。

  5. 分配被扩展和应用。如果有命令,则将分配应用于命令将在其中运行的子 shell 环境;否则,它们将应用于当前的 shell 环境。

  6. 如果有一个命令,它会使用作为argc/argv 参数传递给它的命令参数字来执行。

比如&lt;cat cat&gt; cat cat这行,看起来让你很困惑,从左到右解析为:

  • &lt;cat,输入重定向
  • cat,一条命令
  • &gt;cat,输出重定向
  • cat,论据

这导致重定向 &lt;cat&gt;cat 在调用带有参数 cat 的命令 cat 之前被执行。如果文件 cat 在执行该行之前在当前目录中不存在,则第一个重定向 (&lt;cat) 将失败,因此第二个重定向 (&gt;cat) 只有在文件确实存在时才会执行;它会立即截断(清空)文件。除非当前目录在 PATH 中,否则命令 cat 将从文件 /bin/cat 执行,这是一个不同的文件。由于为cat 命令提供了一个参数,它不会使用其标准输入,因此&lt;cat 重定向除了导致整个命令失败之外没有任何作用,除非文件cat 已经存在。由于cat 文件在执行cat cat 命令之前会被截断,因此不会将任何内容写入标准输出,cat 文件将保持为空。

关于你最后的问题:

  • 除了有关错误处理的一些细节之外,这些规则同样适用于所有简单命令,无论是否内置。

  • &gt;2foo 中的2 并不特殊,所以2foo 是一个文件名。 FD 重复使用&gt;&amp; 重定向运算符指示; &gt;&amp;2foo 被视为尝试复制 2foo,这是无效的,因为 2foo 不是整数。 Posix 认为这是未指定的行为,因此实际的 shell 可能会做任何事情。请参阅 Posix shell 规范的Section 2.7.5 了解详细信息(或至少官方行)。

  • 重定向可能会因文件不存在或文件权限不允许该操作而失败。如上所述,重定向是从左到右执行的,这可能会在“复杂”情况下产生影响。

【讨论】:

  • 就您对&lt;cat cat&gt; cat cat 的解析而言,在我执行所有这些命令之前echo "dog" &gt; cat,因此该文件确实存在并且cat cat 按预期打印dog。那么为什么它在BASH 中被截断?根据我对您的解释的阅读,它不应该在文件catstdin 中读取,在文件catstdout 中读取吗?为什么它将文件cat 覆盖为空,因为第一个命令应该读入非空?不过,总体来说很好的解释,只是我正在寻找的那种信息,以补充 Adv 中的示例。 BASH 脚本指南。
  • @jason:它在被重定向打开输出时被截断。请参阅我的第 4 点。正如我还提到的,cat 如果您给它参数,则不会从 stdin 读取。请参阅man cat 了解更多信息。
  • @jason:也许混淆的地方在于您有一个模型,其中以某种方式按输入顺序评估命令,因此它对重定向等负有一些责任(就像 MS- DOS shell 内置程序)。 Unix shell 不能以这种方式工作。在将控制权交给命令之前,会解析整个命令行并创建执行环境。该执行环境包括指示的重定向,因此它们对正在执行的实用程序在文本上不可见;该实用程序只是从标准流中读取和写入。
  • 啊哈我明白了.. &lt;cat cat&gt; ...我把cat&gt;误读为&gt;cat。现在有道理了。到控制程序执行时,文件已被清除...
  • @jason: &lt;cat &gt;cat cat cat 会产生相同的效果。重定向从命令行中删除,然后首先重定向,然后从左到右处理剩余的命令字。所以精确的交错并不重要;只有每组的顺序。
【解决方案2】:

对不起,linux 不是我的领域,但cmd 是。这是cmd 有限的答案,您必须加入它以获取更多信息。

都支持?

基本重定向运算符(&lt;&gt;&gt;&gt;|)包含在 ms-dos 2.0(仍为 command.com)中,并且从那时起在所有版本中都可用。

从 windows 95(仅从内存中)处理重复运算符(&gt;&amp;&lt;&amp;)也可用。

other shells 中的更多外来/非标准运算符不可用。

它支持所有支持的可执行文件还是只支持核心 shell 实用程序?

cmd 中,您可以请求重定向您想要的任何可执行文件或内部命令,但结果将取决于可执行文件(在控制台模式下与否)与stdin/stdout/stderr 交互的能力.

例如。

  • timeout.exe,控制台子系统可执行文件不允许输入重定向

  • mshta.exe,一个图形子系统可执行文件允许您使用FileSystemObject 获取对StdOut 的引用并写入它

使用了哪些规则...?

cmd解析规则很简单。左到右。如果最终解析的命令有意义(不是不平衡或明显错误),则执行它,否则会出现语法错误。

i = stdin input redirection
o = stdout output redirection
e = stderr output redirection
c = command to execute
a = arguments to the command

> file1 < file2 cat
^o      ^i      ^c   

<cat cat >dog
^i   ^c  ^o

<cat cat> cat cat
^i   ^c ^o    ^a

<cat cat> cat cat >dog 2>more
^i   ^c ^o    ^a  ^o   ^e       Second output cancels & replaces first one

<cat >dog cat cat <dog >cat
^i   ^o   ^c  ^a  ^i   ^o       Second i/o set cancels & replaces fist one

<cat cat >dog 2>much 1>more     Second output cancels & replaces first one
^i   ^c  ^o   ^e     ^o

<cat dog> cat cat >dog >cat     Multiple output replacement
^i   ^c ^o    ^a  ^o   ^o

stuff.dat>1test.dat<2test.dat
^c       ^o        ^i

在解析命令并确定没有任何语法错误后,必须在启动命令之前创建重定向。如果没有任何问题(输入文件存在,输出文件可以写入),则分配适当的句柄并启动程序/命令(如果存在)。

shell 之间的解析规则在多大程度上是一致的?

cmd.exe 规则在 windows 版本之间是一致的,并且向后兼容旧版本 command.com 中使用的语法。

只是一个意见,但是,为什么外壳之间应该有任何一致性?如果一切都一致,为什么要拥有多个?

是什么决定了这些 shell 中具有复杂 I/O 重定向模式的命令失败?

你如何确定失败?你如何确定成功? shell 会尝试做你所要求的,而不是你想要的。甚至明确的命令也可以以unspected 的先验方式运行。

cmd parses 命令并将它们转换为内部表示。在此表示中,与请求的命令相关的数据与重定向信息分开。在开始执行任何东西之前,“命令部分”和“重定向部分”都必须在语法上正确(从解析器的角度来看)。

命令即将执行时,重定向请求为processed,获取所需的文件/句柄。如果一切都可以建立,那么命令将在创建的上下文中执行。

所以,失败可能是

  • 语法问题(解析时)
  • 资源获取问题(在创建重定向上下文时,在开始执行命令之前)
  • 权限/硬件问题(执行命令期间)

【讨论】:

  • 关于 shell 之间的一致性我没有强烈的意见——我只是对在我的测试中给出的一致性程度感兴趣,对于我在上面CMD 中尝试的几个例子它像BASH 一样工作。至于“失败”,我将其量化为命令产生的错误导致行终止而不读取/对所有参数采取行动,而不是从产生意外结果的角度来看“失败”(正如我所预料的那样) .我明白你的意思,不过,这个词是模棱两可的。希望澄清。
  • 顺便说一句,这是一个令人难以置信的答案。我希望我能标记两个正确的答案。谢谢...很有启发性,我认为人们会遇到这个并学到很多东西。
猜你喜欢
  • 1970-01-01
  • 2021-11-09
  • 1970-01-01
  • 2013-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多