许多命令行实用程序可以从管道或文件名参数中获取输入。对于长 shell 脚本,我发现以 a 开始链cat
使其更具可读性,特别是如果第一个命令需要多行参数。
比较
sed s/bla/blaha/ data \
| grep blah \
| grep -n babla
和
cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla
后一种方法效率较低吗?如果是这样,差异是否足以关心脚本是否运行(例如每秒运行一次)?可读性差异并不大。
答案1
“最终”的答案当然是由cat
奖项的无用用途。
cat 的目的是连接(或“连接”)文件。如果它只是一个文件,那么将它与任何东西连接起来都是浪费时间,并且会花费你一个过程。
实例化 cat 以便您的代码以不同的方式读取,只会增加一个进程和一组不需要的输入/输出流。通常,脚本中真正的阻碍将是低效的循环和实际处理。在大多数现代系统上,额外的一个cat
不会影响你的性能,但几乎总是有另一种方法来编写你的代码。
正如您所注意到的,大多数程序都能够接受输入文件的参数。然而,只要需要 STDIN 流,总是可以使用内置的 shell <
,这将通过在已经运行的 shell 进程中完成工作来节省一个进程。
您甚至可以在编写位置上发挥创意。通常,在指定任何输出重定向或管道之前,它会放置在命令的末尾,如下所示:
sed s/blah/blaha/ < data | pipe
但事实并非一定如此。它甚至可以是第一位的。例如,您的示例代码可以这样写:
< data \
sed s/bla/blaha/ |
grep blah |
grep -n babla
如果您关心脚本的可读性,并且您的代码足够混乱,添加一行 forcat
预计会使其更易于理解,那么还有其他方法可以清理您的代码。我经常使用的一种方法是将管道分解为逻辑集并将它们保存在函数中,这有助于使脚本稍后更容易理解。这样脚本代码就变得非常自然,并且管道的任何一部分都更容易调试。
function fix_blahs () {
sed s/bla/blaha/ |
grep blah |
grep -n babla
}
fix_blahs < data
然后您可以继续fix_blahs < data | fix_frogs | reorder | format_for_sql
。像这样的管道确实很容易理解,并且可以在各自的功能中轻松调试各个组件。
答案2
以下是一些缺点的总结:
cat $file | cmd
超过
< $file cmd
首先,请注意:上面(为了讨论的目的而故意)缺少双引号
$file
。在 的情况下cat
,这始终是一个问题,除了zsh
;在重定向的情况下,这仅是bash
orksh88
and 的问题,对于某些其他 shell(包括bash
POSIX 模式)仅在交互时(不在脚本中)。最常提到的缺点是会产生额外的进程。请注意,如果
cmd
是内置的,在某些 shell 中甚至会生成 2 个进程,例如bash
。仍然在性能方面,除了在
cat
内置的 shell 中,还有一个正在执行的额外命令(当然还有加载和初始化(以及它链接到的库))。仍然在性能方面,对于大文件,这意味着系统必须交替调度
cat
和cmd
进程,并不断填充和清空管道缓冲区。即使一次cmd
进行1GB
大型read()
系统调用,控制也必须在 和 之间来回切换cat
,cmd
因为管道一次不能容纳超过几千字节的数据。当它们的标准输入是常规文件时,某些
cmd
s(例如wc -c
)可以进行一些优化,但它们不能这样做,cat | cmd
因为它们的标准输入只是一个管道。有了cat
管道,这也意味着它们不能seek()
在文件中。对于像tac
或 这样的命令tail
,这在性能上产生了巨大的差异,因为这意味着cat
它们需要将整个输入存储在内存中。cat $file
,甚至它更正确的版本对于某些特定的文件名(或者如果您忘记了以 开头的任何文件名)cat -- "$file"
都无法正常工作。如果一个人坚持使用,他可能应该使用,以确保可靠性。-
--help
-
--
cat
cat < "$file" | cmd
如果
$file
无法打开读取(访问被拒绝、不存在...),< "$file" cmd
将报告一致的错误消息(由 shell)并不是runcmd
, whilecat $file | cmd
仍会运行cmd
,但其标准输入看起来像是一个空文件。这也意味着,在诸如 之类的东西中< file cmd > file2
,如果无法打开,file2
则不会被破坏。file
或者换句话说,您可以选择打开输入和输出文件的顺序,而不是
cmd file > file2
始终打开输出文件的顺序(通过 shell)前输入文件(由cmd
),这几乎是不可取的。但请注意,它不会帮助同时和独立执行
cmd1 < file | cmd2 > file2
wherecmd1
和cmd2
以及它们的重定向,并且您需要编写为{ cmd1 | cmd2; } < file > file2
或(cmd1 | cmd2 > file2) < file
例如以避免file2
被破坏以及cmd1
在无法打开cmd2
时运行。file
答案3
放在<file
管道末尾的可读性不如放在cat file
开头的可读性。自然英语从左到右阅读。
<file
我想说,将管道的开头放在管道的开头也比 cat 可读性差。单词比符号更具可读性,尤其是似乎指向错误方向的符号。
使用cat
保留command | command | command
格式。
答案4
这里的其他答案似乎没有直接解决的一件事是,cat
像这样使用并不是“无用”,因为“产生了一个不起作用的无关猫进程”;从“产生一个只做不必要的工作的猫进程”的意义上来说,它是无用的。
对于这两种情况:
sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'
shell 启动一个 sed 进程,该进程从某个文件或标准输入(分别)读取,然后进行一些处理 - 它读取直到遇到换行符,用“bar”替换该行上的第一个“foo”(如果有),然后打印该行到标准输出并循环。
如果是:
cat somefile | sed 's/foo/bar/'
shell 生成一个 cat 进程和一个 sed 进程,并将 cat 的标准输出连接到 sed 的标准输入。 cat 进程从文件中读取几千字节或可能兆字节的块,然后将其写入其标准输出,sed sommand 从那里获取,如上面的第二个示例所示。当 sed 处理该块时,cat 正在读取另一个块并将其写入其标准输出,以便 sed 进行下一步处理。
换句话说,添加命令所需的额外工作cat
不仅仅是生成额外cat
进程的额外工作,也是读取和写入文件字节两次而不是一次的额外工作。现在,实际上来说,在现代系统上,这不会产生巨大的差异 - 它可能会使您的系统执行几微秒的不必要的工作。但是,如果您计划分发一个脚本,可能分发给在功率已经不足的机器上使用它的人,则几微秒可能会经过多次迭代。