考虑以下代码:
command1 | command2
命令1的输出是作为命令2的标准输入还是作为命令行参数传递给命令2?
例如,
cat test.sh | grep "hehe"
不使用管道符号,它的等效形式是什么?
我尝试过
grep "hehe" $(cat test.sh)
看起来似乎不正确。
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命令都设计为“过滤器”;它们从一些文件中读取输入,以某种方式进行转换,并将结果写入标准输出。这些命令旨在用于命令管道中。例如:
所有这些过滤器都具有相同的一般行为:它们接受命令行选项以控制其行为,然后它们要么读取作为命令行参数指定的文件,要么(如果没有这样的参数)读取标准输入。某些过滤器(如sort
)可以具有选项来控制输出去向而不是标准输出,但这相对较少见。
还有一些严格读取标准输入并写入标准输出的纯过滤器 - tr
就是其中之一。
其他命令具有不同的行为。Eric Raymond 在《UNIX编程艺术》中提供了一种命令类型分类。
有些命令会在标准输出上生成文件名列表 - 两个经典的命令是 ls
和 find
。
有时,您想将文件名生成器的输出应用为过滤器的命令行参数。有一个程序可以自动完成这项工作 - 它就是 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版本的 find
和 xargs
具有相应的选项来解决这个问题:
find . -name '*.[chyl]' -print0 | xargs -0 grep -n magic_name /dev/null
选项'-print0
'的意思是“打印以NUL '\0'结尾的文件名”(因为在(简单)文件名中唯一不允许出现的字符是'/'和NUL,而显然'/'可以出现在路径名中)。相应的'-0
'告诉xargs
查找以NUL结尾而不是以空格分隔的名称。
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”。
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管道和命令行参数是不可互换的不同输入形式。如果一个程序允许你同时使用这两种等效形式,那就取决于该程序本身。(在源代码中,命令行参数以变量中的文本形式出现,而管道则出现为打开的文件,包括stdin和stdout。Bash I/O重定向语法,如下面所示,严格来说不属于命令行参数,尽管它们紧挨着命令行参数写在一起...)
但让我们严谨地回答以下问题:
答案:使用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脚本编程手册,进程替换的章节(从“其他用法”开始阅读)。
grep … < file.ext
是“通过stdin从文件输入”,而 grep … < <(…)
是“通过stdin从命令输出输入”。由于在这两种情况下,输入都通过stdin
进行,脚本将其视为/dev/fd/0
,因此脚本无法区分这些情况。 - tanius它被用作标准输入。
尝试:
grep "hehe" - $(cat test.sh)
这可能是错误的;我无法在这台电脑上进行测试。如果您尝试不使用管道符号进行操作,grep将把最后一个参数作为文件名处理,即查找名为[contents of test.sh]的文件。如果您传递一个连字符(-)(或者不放置最后一个参数),则告诉它使用stdin作为文件。
您也可以直接传递要扫描的文件给grep:
grep "hehe" test.sh
...但是你似乎在问一个更一般化的bash问题,而不是一个grep使用问题,所以这可能没有太大帮助。
$()
一样,只是不能嵌套,并且更容易出错。后者的形式可能是 Tim 寻找的。 - Cascabel