Bash中的管道、标准输入和命令行参数

13

考虑以下代码:

command1 | command2

命令1的输出是作为命令2的标准输入还是作为命令行参数传递给命令2?

例如,

cat test.sh | grep "hehe"

不使用管道符号,它的等效形式是什么?

我尝试过

grep "hehe" $(cat test.sh)

看起来似乎不正确。


1
你的问题有点混淆 - 希望输入重定向或仅仅提供文件名作为参数是你所需要的。另一方面,如果你想知道如何将一个命令的标准输出传递给另一个命令的标准输入而不使用管道...那就是管道的定义。 - Cascabel
4个回答

16
grep "hehe" < test.sh

输入重定向 - 当然只对单个文件有效,而 cat 对任意数量的输入文件都有效。


考虑这些符号:

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

在这种情况下,这些是等价的;在嵌套使用中,例如:

$(cmd)

使用'$(cmd)'符号更加容易。

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

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

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


重新审视问题后,还有更多可以说的 - 这些尚未被提及。

首先,许多Unix命令都设计为“过滤器”;它们从一些文件中读取输入,以某种方式进行转换,并将结果写入标准输出。这些命令旨在用于命令管道中。例如:

  • cat
  • grep
  • troff 和相关工具
  • awk(带有警告)
  • sed
  • sort

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

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

其他命令具有不同的行为。Eric Raymond 在《UNIX编程艺术》中提供了一种命令类型分类。

有些命令会在标准输出上生成文件名列表 - 两个经典的命令是 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 的影响,这种空格现在已经很普遍了。GNU版本的 findxargs 具有相应的选项来解决这个问题:

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

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


输入重定向是将stdin输入或命令行参数提供给grep吗? - Tim
实际上,我的问题是关于更一般的命令,而不仅仅是grep。 - Tim
所以我看到,清理完之后...我已经概括了我的答案:D - Jonathan Leffler
3
我不确定你是否回答了原始问题。答案是“管道将标准输出连接到标准输入”。 - Bryan Oakley
@Bryan - 是的,我忘记在“修复”问题后这样做了,所以它可以被识别。 - Jonathan Leffler

6
另一种重定向的形式是进程替换。
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”。


2
我不喜欢你在第三行使用“等价”的方式:grep“hehe”test.sh最终将test.sh的内容用作stdin,而grep“hehe”<(cat test.sh)则使用命令cat test.sh的输出作为stdin。考虑一下如果按以下方式运行命令所产生的结果差异:grep -H“hehe”<(cat test.sh)grep -H“hehe”test.sh - Isaac Kleinman

2

如何使用命令行参数实现类似于bash管道的功能?

管道和命令行参数是不可互换的不同输入形式。如果一个程序允许你同时使用这两种等效形式,那就取决于该程序本身。(在源代码中,命令行参数以变量中的文本形式出现,而管道则出现为打开的文件,包括stdin和stdout。Bash I/O重定向语法,如下面所示,严格来说不属于命令行参数,尽管它们紧挨着命令行参数写在一起...)

但让我们严谨地回答以下问题:

如果不使用bash管道字符,如何实现类似于bash管道的功能?

答案:使用cat test.sh | grep "hehe"等同于

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

说明:

  • Pipes redirect stdout of one command to stdin of another. To set the source of stdin, we can use input redirection ( < …) instead of using the pipe character.

  • However, just using input redirection (grep "hehe" < test.sh) is not the equivalent to pipes because it uses a file as the source for stdin, while pipes use the output a command (cat test.sh). So in addition, we add process substitution <(…) to replace input from a file to stdin with input from a command to stdin.

  • Of course, our example here is confusing because the two variants have the same effects:

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

    But technically, input from stdin from a file is still a different mechanism than input from stdin from the output of a command that gets its input from a file.

  • For an even more detailed explanation, I recommend two other answers: here and here.

来源:高级Bash脚本编程手册,进程替换的章节(从“其他用法”开始阅读)。


脚本能区分文件输入和命令输入吗?如果可以,如何区分?请提供一个例子,其中两种变体效果不同。 - winklerrr
1
@winklerrr:“从文件输入”与“从命令输出输入”的区别:这是我措辞有些粗糙,我会修正的。更准确地说,grep … < file.ext 是“通过stdin从文件输入”,而 grep … < <(…) 是“通过stdin从命令输出输入”。由于在这两种情况下,输入都通过stdin进行,脚本将其视为/dev/fd/0,因此脚本无法区分这些情况。 - tanius

0

它被用作标准输入。

尝试:

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

这可能是错误的;我无法在这台电脑上进行测试。如果您尝试不使用管道符号进行操作,grep将把最后一个参数作为文件名处理,即查找名为[contents of test.sh]的文件。如果您传递一个连字符(-)(或者不放置最后一个参数),则告诉它使用stdin作为文件。

您也可以直接传递要扫描的文件给grep:

grep "hehe" test.sh

...但是你似乎在问一个更一般化的bash问题,而不是一个grep使用问题,所以这可能没有太大帮助。


该程序会遍历test.sh中的每个单词,并查找与该单词同名的文件,然后在这些文件上执行grep操作(通常成功率非常有限)。 - Jonathan Leffler
反引号是命令替换,就像 $() 一样,只是不能嵌套,并且更容易出错。后者的形式可能是 Tim 寻找的。 - Cascabel
再次强调,命令替换是行不通的。grep的参数不是要在其中搜索的字符串;它们是要在其中进行搜索的文件。您的第一种形式现在将通过标准输入和test.sh中给定的所有文件进行grep搜索。 - Cascabel
谢谢!是的,我的问题更多是通用性的。许多Bash命令都是以这种方式实现的,它们可以从标准输入和命令行参数中获取相同的输入吗?grep只是我试图弄清楚这个问题的一个例子。 - Tim
@Jefromi:啊,我明白了。你知道有什么方法可以做到这一点吗?我本来想在“-”后面加上一个\n,但我不确定那样是否有效。@Tim:许多命令使用“-”作为stdin“文件”; 在bash中可能是这样实现的(即,它将适用于所有命令),但请不要引用我。 - Jarett Millard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接