我应该照顾不必要的猫吗?


50

许多命令行实用程序可以从管道或文件名参数中获取输入。对于长Shell脚本,我发现以a开头的链cat使其更具可读性,尤其是在第一个命令需要多行参数的情况下。

相比

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

后一种方法效率较低吗?如果是这样,差异是否足以关心脚本是否运行(例如每秒运行一次)?可读性差异不大。


30
与我的系统实际启动猫程序相比,我花更多的时间看着人们在这个网站上相互攻击有关无用猫的使用
Michael Mrozek

4
@迈克尔:100%同意。哎呀,我花了更多的时间链接到旧的usenet奖一次,这比我的计算机浪费在实例化上cat。但是,我认为这里更大的问题是代码的可读性,这通常性能的优先考虑。当更快的实际可写入的漂亮,为什么不呢?指出问题cat通常会使用户对管道和流程有更好的了解。值得付出努力,因此他们下次可以编写易于理解的代码。
卡莱布

3
实际上,还有另一个我不喜欢第一种形式的原因-如果您想在管道的开头添加另一个命令,那么您也必须移动参数,因此编辑会比较烦人。(当然,这并不意味着您必须使用cat; Caleb关于使用函数和重定向的观点也可以解决该问题。)
Cascabel 2011年


1
今天晚上上班,我的工作拒绝了。我打开stackoverflow并找到一个标题为“我应该关心不必要的猫吗?”的问题。并看到一些无家可归的动物和程序员,正在考虑是否养活它们……
Boris Burkov

Answers:


46

“确定的”答案当然是“无用cat奖”给您带来

cat的目的是连接(或“连接”)文件。如果仅是一个文件,则根本不进行任何连接将浪费时间,并且会花费您一个过程。

实例化cat只是为了使您的代码能以不同的方式读取,从而只需要一个进程和一组不需要的输入/输出流。通常,脚本中的实际滞留将是效率低下的循环和实际处理。在大多数现代系统上,增加性能cat并不会降低性能,但是几乎总是存在另一种编写代码的方法。

您注意到,大多数程序都可以接受输入文件的参数。但是,总是有内置的shell <,可以在需要STDIN流的任何地方使用它,这可以通过在已经运行的shell进程中进行工作来为您节省一个进程。

您甚至可以在编写的地方获得创意。通常,在指定任何输出重定向或管道之前,它将放置在命令的末尾,如下所示:

sed s/blah/blaha/ < data | pipe

但这不是必须的。它甚至可以排在第一位。例如,您的示例代码可以这样写:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

如果您担心脚本的可读性,并且您的代码过于混乱,以至于希望增加一行代码,cat使它更容易理解,那么还有其他方法可以清除您的代码。我经常使用的一种有助于使脚本易于稍后查找的方法是将管道分成逻辑集并将其保存在函数中。这样脚本代码就变得非常自然,并且管线的任何一部分都更易于调试。

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

然后,您可以继续fix_blahs < data | fix_frogs | reorder | format_for_sql。像这样的pipleline确实很容易遵循,并且可以轻松调试各个组件的各自功能。


26
我不知道<file在命令之前会发生这种情况。这解决了我所有的问题!

3
@Tim:Bash和Zsh都支持,尽管我认为这很丑。当我担心我的代码漂亮且可维护时,我通常使用函数对其进行清理。看到我的最后编辑。
卡莱布

8
@Tim <file可以来随时随地在命令行上:<file grep needlegrep <file needlegrep needle <file。例外是复杂的命令,例如循环和分组。有重定向一定要来后闭幕done/ }/ )/等。@Caleb在所有的Bourne / POSIX shell中都适用。我不同意它的丑陋。
吉尔(Gilles)“所以,别再邪恶了”,

9
@Gilles,在bash中,您可以替换$(cat /some/file)$(< /some/file),它执行相同的操作,但避免产生进程。
cjm 2011年

3
只是为了确认那$(< /some/file)是有限的可移植性。它确实可以在bash中工作,但不能在BusyBox ash中工作,例如FreeBSD sh。可能也不起作用,因为最后三个外壳都是近亲。
dubiousjim 2012年

22

以下是一些缺点的摘要:

cat $file | cmd

过度

< $file cmd
  • 首先,要注意:在上面(出于讨论目的)故意缺少双引号$file。在这种情况下cat,除了zsh; 之外总是一个问题。在重定向的情况下,这仅对于bashor ksh88和,对于某些其他shell(仅在交互时(不在脚本中))是一个问题。
  • 最常被提及的缺点是产生了额外的过程。请注意,如果cmd是内置的,那么在某些shell中甚至有2个进程bash
  • 仍然在性能方面,除了cat内置的shell中,还有一条额外的命令正在执行(当然,还要加载和初始化)(以及与其链接的库)。
  • 仍然在性能方面,对于大文件,这意味着系统将不得不交替调度catcmd进程,并不断填充和清空管道缓冲区。即使cmd1GB大量read()的系统调用的时间,控制将不得不来回之间cat,并cmd因为管道不能保持在同一时刻数据的几千字节以上。
  • 有些cmds(如wc -c)可以在其stdin是常规文件时执行某些优化操作,而cat | cmd由于stdin只是一个管道而无法执行。使用cat和管道,这也意味着它们不能seek()在文件中。对于诸如tac或的命令tail,在性能上有很大的不同,因为这意味着cat它们需要将整个输入存储在内存中。
  • cat $file,甚至它的更正确的版本cat -- "$file"不会像某些特定文件名正常工作-(或--help什么的开始-,如果你忘记了--)。如果坚持使用catcat < "$file" | cmd出于可靠性考虑,他可能应该改为使用。
  • 如果$file无法打开进行读取(访问被拒绝,不存在...),< "$file" cmd它将报告一致的错误消息(由外壳程序提供)并且运行cmd,虽然cat $file | cmd仍将运行,cmd但其stdin看起来像是一个空文件。这也意味着,如果无法打开< file cmd > file2file2则不会破坏file

2
关于性能:该测试表明,差异约为1 pct,除非您对流oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange

2
@OleTange。这是另一个测试:truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c。图片中包含很多参数。性能损失可以从0到100%。无论如何,我认为罚款不会是负数。
斯特凡Chazelas

2
wc -c这是一个非常独特的案例,因为它具有快捷方式。如果您改为这样做,wc -w则它可以与grep我的示例进行比较(即很少的处理-这就是'<' 有所作为的情况)。
Ole Tange

@OleTange,甚至(wc -w在Linux 4.9 amd64上C语言环境中的1GB稀疏文件上),我发现cat方法在多核系统上花费的时间增加了23%,而将它们绑定到一个核上则花费了5%。显示由多个内核访问数据所产生的额外开销。如果您更改管道的大小,使用不同的数据,涉及实际的I / O,使用使用splice()的cat实现,您可能会得到不同的结果。所有这些都证实了图片中包含很多参数而且无论如何cat都无济于事。
斯特凡Chazelas

1
对于拥有1GB文件的我来说,wc -w如果是直接的简单grep,则相差约2%... 15%。然后,奇怪的是,如果它在NFS文件共享上,则通过catgist.github.com/rdp/7162414833becbee5919cda855f1cb86)管道传输,读取它实际上要快20%…
rogerdpack

16

推杆<file上的管线的端部比具有较少可读cat file在开始。自然英语从左到右阅读。

<file一个流水线的启动也比猫少可读,我会说。单词比符号更具可读性,尤其是符号指向错误的符号。

使用cat保留command | command | command格式。


我同意,使用<一次会使代码的可读性降低,因为它破坏了多管道的语法一致性。
A.Danischewski,2015年

@Jim您可以通过以下方式创建别名来解决可读性<alias load='<'然后使用eg load file | sed ...。别名可以在运行后在脚本中使用shopt -s expand_aliases
niieani '16

1
是的,我知道别名。但是,尽管此别名用单词代替了符号,但它要求读者了解您的个人别名设置,因此不是很方便。
吉姆(Jim)

8

这里其他答案似乎没有直接解决的一件事是,这样的使用cat并不是“无用的”,即“产生了无关紧要的猫进程”;从“催生只执行不必要的工作的猫进程”的意义上讲,这是没有用的。

对于这两种情况:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

Shell启动一个sed进程,分别从somefile或stdin读取,然后进行一些处理-读取直到打到换行符,然后用'bar'替换该行的第一个'foo'(如果有),然后打印那条线到标准输出并循环。

如果是:

cat somefile | sed 's/foo/bar/'

外壳会生成cat过程和sed过程,并将cat的stdout连接到sed的stdin。cat进程从文件中读取了几千个字节或几兆字节的块,然后将其写到其stdout中,如上面的第二个示例中所示,sed sommand从那里开始拾取。当sed处理该块时,cat正在读取另一个块并将其写入其stdout中,以便sed在下一个工作。

换句话说,添加cat命令所需要的额外工作不仅是产生额外cat进程的额外工作,而且还是两次读取文件字节而不是一次写入字节的额外工作。现在,实际上,在现代系统上,这并没有太大的不同-它可能会使您的系统执行几微秒的不必要工作。但是,如果它是针对您计划分发的脚本的,那么可能是针对那些已经在功率不足的机器上使用它的人们,那么,几毫秒的时间会累加很多迭代。


2
请参阅oletange.blogspot.dk/2013/10/useless-use-of-cat.html,以测试使用额外的的开销cat
Ole Tange

@OleTange:我偶然发现了这个,并访问了您的博客。(1)虽然我看到的内容(大部分是英语),但在丹麦语中(我猜)看到了一堆词:“ Klassisk”,“ Flipcard”,“ Magasin”,“ Mosaik”,“Sidebjælke”,“Øjebliksbillede” ,“ Tidsskyder”,“ Blog-arkiv”,“ Om mig”,“ Skrevet”和“ Vis kommentarer”(但“ Tweet”,“ Like”和cookie横幅是英文)。您知道吗?它在您的控制之下吗?(2)由于网格线不完整,我在读取表(2a)时遇到了麻烦;(2b)我不明白您所说的“差异(pct)”的含义。
G-Man说'Resstate Monica''Mar

blogspot.dk由Google运行。尝试替换为blogspot.com。“差异(pct)”是用ms cat除以不带cat百分比的ms (例如,264 ms / 216 ms = 1.22 = 122%= 22%慢cat
Ole Tange
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.