【问题标题】:Pipe, standard input and command line arguments in BashBash 中的管道、标准输入和命令行参数
【发布时间】:2010-12-03 01:20:30
【问题描述】:

考虑:

command1 | command2

command1 的输出是用作 command2 的标准输入还是作为 command2 的命令行参数?

例如,

cat test.sh | grep "hehe"

不使用管道的等价形式是什么?

我试过了

grep "hehe" $(cat test.sh)

而且似乎不正确。

【问题讨论】:

  • 您的问题有点令人困惑 - 希望输入重定向或简单地提供文件名作为参数是您想要的。另一方面,如果您要问如何在不使用管道的情况下获取一个命令的标准输出并将其提供给另一个命令的标准输入......这就是管道的定义。

标签: bash pipe


【解决方案1】:

什么是使用命令行参数的 bash 管道?

管道和命令行参数是不可互换的不同输入形式。如果一个程序允许您拥有两者的等效形式,那就是该程序单独的选择。 (在源代码中,命令行参数显示为变量中的文本,而管道显示为打开的文件,包括标准输入和标准输出。Bash I/O 重定向语法,如稍后使用的,技术上不属于 到命令行参数,即使在命令行上写在它们旁边……)​​

但让我们学究一下,也回答这个问题:

什么相当于不使用 bash 管道字符的 bash 管道?

答案:cat test.sh | grep "hehe" 等价于

grep "hehe" < <(cat test.sh)

说明:

  • 管道将一个命令的标准输出重定向到另一个命令的标准输入。要设置标准输入的来源,我们可以使用输入重定向( &lt; …)而不是使用管道字符。

  • 但是,仅仅使用输入重定向 (grep "hehe" &lt; test.sh) 并不等同于管道,因为它使用 file 作为标准输入的源,而管道使用 输出命令 em> (cat test.sh)。因此,此外,我们添加了进程替换&lt;(…) 以将文件到标准输入的输入替换为从命令到标准输入的输入。

  • 当然,我们这里的示例是令人困惑的,因为这两个变体具有相同的效果:

      grep "hehe" < test.sh
      grep "hehe" < <(cat test.sh)
    

    但从技术上讲,来自文件的标准输入的输入与来自从文件获取其输入的命令的输出的标准输入输入仍然是不同的机制。

  • 对于更详细的解释,我推荐另外两个答案:herehere

来源: Advanced Bash Scripting Manual, section on process substitution(从“其他一些用法”开始阅读)。

【讨论】:

  • 脚本能否区分来自文件的输入和来自命令的输入? (如果是,如何?)您能否添加一个示例,其中两个变体没有相同的效果?
  • @winklerrr:“来自文件的输入”与“来自命令输出的输入”:这对我来说有点草率,我会解决的。更准确地说,grep … &lt; file.ext 是“通过标准输入从文件输入”,grep … &lt; &lt;(…) 是“通过标准输入从命令输出输入”。由于在这两种情况下,输入来自stdin,脚本将其视为/dev/fd/0,因此脚本无法区分这些情况。
【解决方案2】:
grep "hehe" < test.sh

输入重定向 - 当然仅适用于单个文件,而 cat 适用于任意数量的输入文件。


考虑符号:

grep "hehe" $(cat test.sh)
grep "hehe" `cat test.sh`

在这种情况下,它们是等价的;在嵌套使用中使用“$(cmd)”表示法要容易得多,例如:

x=$(dirname $(dirname $(which gcc)))
x=`dirname \`dirname \\\`which gcc\\\`\``

(这为您提供了安装 GCC 的基本目录,以防您想知道。)

grep 示例中,发生的情况是test.sh 的内容被读取并拆分为空格分隔的单词,并且每个这样的单词都作为grep 的参数提供。由于grep 处理"hehe" 之后的单词(当然,grepnot 看到双引号 - 在这种情况下不需要它们;作为一般规则,使用单引号引号而不是双引号,尤其是在复杂字符串(如经常使用 shell 元字符的正则表达式)周围)...正如我所说,grep"hehe" 之后的单词视为文件名,并尝试打开每个文件,通常会失败郁闷,因为文件不存在。这就是为什么该符号在这种情况下不合适的原因。


在重新审视这个问题之后,还有更多可以说的——还没有说的。

首先,许多 Unix 命令被设计为“过滤器”;他们从一些文件中读取输入,以某种方式对其进行转换,然后将结果写入标准输出。此类命令专为在命令管道中使用而设计。示例包括:

  • grep
  • 特罗夫和亲戚
  • awk(带有警告)
  • sed
  • 排序

所有这些过滤器都具有相同的一般行为:它们采用命令行选项来控制它们的行为,然后它们要么读取指定为命令行参数的文件,要么,如果没有这样的参数,它们读取它们的标准输入。有些(如sort)可以选择控制其输出的去向而不是标准输出,但这相对不常见。

有一些纯过滤器 - tr 就是其中之一 - 严格读取标准输入并写入标准输出。

其他命令有不同的行为。 Eric Raymond 为“The Art of UNIX Programming”中的命令类型提供了分类。

一些命令会在标准输出中生成文件名列表 - 两个经典命令是 lsfind

有时,您希望将文件名生成器的输出用作过滤器的命令行参数。有一个程序可以自动执行此操作 - 它是 xargs

通常,您会使用:

find . -name '*.[chyl]' | xargs grep -n magic_name /dev/null

这将生成扩展名为“.c”、“.h”、“.y”和“.l”的文件的完整列表(C 源代码、标题、Yacc 和 Lex 文件)。当xargs 读取列表时,它会创建以grep -n magic_name /dev/null 开头的命令行,并将每个单词(由空格分隔)作为参数。

在过去,Unix 文件名不包含空格。在 Mac 和 Windows 的影响下,这样的空间现在已经司空见惯。 findxargs 的 GNU 版本有互补的选项来处理这个问题:

find . -name '*.[chyl]' -print0 | xargs -0 grep -n magic_name /dev/null

'-print0' 选项的意思是“打印以 NUL '\0' 结尾的文件名”(因为不能出现在(简单)文件名中的唯一字符是 '/' 和 NUL,显然,' /' 可以出现在路径名中)。对应的 '-0' 告诉 xargs 查找以 NUL 结尾的名称,而不是空格分隔的名称。

【讨论】:

  • 输入重定向是否为 grep 提供标准输入或命令行参数?
  • 其实我的问题是针对更通用的命令,而不仅仅是针对 grep。
  • 所以我明白了,在清理之后......我已经概括了我的答案:D
  • 我不确定您是否回答过最初的问题。答案是“管道将标准输出连接到标准输入。”
  • @Bryan - 是的;我在“解决”问题后忘记这样做了,所以它是可识别的。
【解决方案3】:

另一种形式的重定向是进程替换。

grep "hehe" <(cat test.sh)

相当于:

grep "hehe" test.sh

两者都查看test.sh本身的内容。

虽然如前所述,此命令:

grep "hehe" $(cat test.sh)

test.sh 中查找文件名并将它们用作grep 的参数。所以如果test.sh 包含:

scriptone
scripttwo

然后grep 将在每个文件的内容中查找“hehe”。

【讨论】:

  • 我不喜欢您在第 3 行使用“等效”:grep "hehe" test.sh 最终将 test.sh 的内容用作标准输入,而 grep "hehe" &lt;(cat test.sh) 使用命令cat test.sh 作为标准输入。如果您按以下方式运行命令,请考虑结果的差异:grep -H "hehe" &lt;(cat test.sh)grep -H "hehe" test.sh
【解决方案4】:

它被用作标准输入。

试试:

grep "hehe" - $(cat test.sh)

这可能是错误的;我无法在这台电脑上测试它。如果您像尝试过的那样在不使用管道的情况下执行此操作,则 grep 会将最后一个参数视为文件名,即查找名为 [contents of test.sh] 的文件。如果你给它传递一个 - (或者不放最后一个参数),你告诉它使用标准输入作为文件。

您也可以只传递 grep 文件来扫描:

grep "hehe" test.sh

...但您似乎更多地问的是一个笼统的 bash 问题,而不是真正的 grep 用法问题,所以这可能没有太大帮助。

【讨论】:

  • 获取 test.sh 中的每个单词并查找与该单词同名的文件,然后对这些文件进行 greps(通常成功率非常有限)。
  • 反引号是命令替换,就像$(),只是不可嵌套,更容易搞砸。后一种形式可能是蒂姆正在寻找的。​​span>
  • 再次,命令替换不起作用。 grep 的参数不是要在其中搜索的字符串;它们是要在其中搜索的文件。您的第一个表单现在将通过标准输入和所有在 test.sh 中给出的名称的文件进行 grep。
  • 谢谢!是的,我的问题更笼统。许多 bash 命令是以这种方式实现的,它们可以从标准输入和命令行参数获取相同的输入,这是真的吗? grep 只是我试图弄清楚这个问题的一个例子。
  • @Jefromi:啊,我明白了。你知道有没有办法做到这一点?我打算在 - 之后放一个 \n,但我也不确定这是否可行。 @Tim:很多命令都使用 - 作为标准输入“文件”;它甚至可以在 bash 中以这种方式实现(即,它适用于所有命令),但不要引用我的话。
猜你喜欢
  • 2021-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-28
  • 1970-01-01
  • 2012-01-28
  • 1970-01-01
相关资源
最近更新 更多