对于“猫的无用用途”的普遍共识是什么?

对于“猫的无用用途”的普遍共识是什么?

当我通过管道传输多个 unix 命令(例如 grep、sed、tr 等)时,我倾向于使用 cat 指定正在处理的输入文件。因此类似于cat file | grep ... | awk ... | sed ...

但最近,在我的回答中留下了几条评论,指出这是对 cat 的无用使用,所以我想我应该在这里提出这个问题。

我查阅了这个问题并发现了维基百科关于 UUOC 的文章猫的无用用途奖在我看来,这些论点都是从效率的角度出发的。

我在这里遇到的最接近的问题是这个:叫猫会不会很浪费?——但这并不是我所问的。

我猜想 UUOC 阵营建议的是使用cmd1 args < file | cmd2 args | cmd3 ..或者如果命令有一个从文件读取的选项,那么将文件作为参数传递。

但对我来说,cat file | cmd1 ... | cmd2阅读和理解起来似乎更容易。我不必记住将输入文件发送到不同命令的不同方式,并且该过程从左到右按逻辑顺序进行。首先输入,然后是第一个过程……依此类推。

我是否不明白人们为什么会认为 cat 毫无用处?我知道,如果我运行一个每 2 秒运行一次的 cron 作业,并且要进行大量处理,那么 cat 可能就太浪费了。但除此之外,大家对 cat 的使用有什么普遍共识?

答案1

它是无用的,因为这样使用它并不能完成其他可能更有效的选项无法完成的任何事情(即产生正确的结果)。

cat远不止如此cat somefile。请查阅man cat或阅读我在这个答案中写了什么。但如果您确实只需要单个文件的内容,那么不使用cat来获取文件内容可能会带来一些性能优势。

至于可读性,这取决于您的个人喜好。cat出于同样的原因,我喜欢将文件放入其他命令中,尤其是当性能方面可以忽略不计时。

这还取决于你编写的脚本。如果这是你自己的 shell 和桌面计算机的便捷方法,除了你之外没有人会关心。如果你偶然发现一个情况,链中的下一个工具最好能够寻找,并将其作为低性能路由器或类似设备上某些最小 Linux 系统上常用的软件进行分发,而这些设备对处理能力有真正的限制,那就不同了。它总是取决于上下文。

答案2

我经常cat file | myprogram在例子中使用。有时我被指责无用地使用 cat (http://www.iki.fi/era/unix/award.html我不同意,理由如下:

很容易理解发生了什么。

当读取 UNIX 命令时,您期望命令后跟参数,然后跟重定向。它可以将重定向放在任何地方,但很少看到 - 因此人们将很难阅读该示例。我相信

    cat foo | program1 -o option -b option | program2

比以下更容易阅读

    program1 -o option -b option < foo | program2

如果将重定向移至开始处,则可能会使不习惯此语法的人感到困惑:

    < foo program1 -o option -b option | program2

并且例子要容易理解。

很容易改变。

如果您知道该程序可以从 cat 读取,则通常可以假设它可以读取任何输出到 STDOUT 的程序的输出,因此您可以根据自己的需要进行调整并获得可预测的结果。

它强调,如果 STDIN 不是常规文件,程序也不会失败。

假设“如果”program1 < foo有效,“那么”cat foo | program1也会有效,这是不安全的。然而,实际上可以安全地假设相反的情况。如果 STDIN 是文件,则此程序可以运行,但如果输入是管道,则失败,因为它使用了 seek:

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

性能损失通常是无法衡量的。

我已经研究过http://oletange.blogspot.dk/2013/10/useless-use-of-cat.html结论是,cat file |如果处理的复杂性与简单的 grep 类似,并且性能比可读性更重要,则不要使用。其他情况cat file |都可以。

下面是一个例子| cat 增加性能提高50%: https://unix.stackexchange.com/questions/614154/useless-use-of-cat-increases-performance-why

答案3

在日常命令行使用中,其实并没有太大区别。你尤其不会注意到任何速度差异,因为不使用可以避免 CPU 占用时间cat,你的 CPU 只是处于空闲状态。即使你正在循环遍历数百或数千(甚至数十万)个项目,实际上也不会产生任何影响很多差异,除非你在一个负载很大的系统上(平均负载/ N CPU > 1)。

关键在于养成良好习惯并阻止不良习惯。用一句陈词滥调来说,细节决定成败。正是这样的细节将平庸与伟大区分开来。

这就像开车时,为什么要左转,而你可以右转三次?当然可以,而且效果很好。但如果你理解左转的力量,那么右转三次就显得很愚蠢了。

这并不是关于节省一个文件句柄、17k RAM 和 0.004 秒 CPU 时间的问题。而是关于使用 UNIX 的整个哲学。我插图中的“左转的力量”不仅仅是重定向输入,而是 UNIX 哲学。充分理解这一点将使您比周围的人更优秀,并且您将赢得那些理解的人的尊重。

答案4

直到今天,一些新手试图将 UUOC 归咎于我的一个答案时,我才知道这个奖项。这是一个cat file.txt | grep foo | cut ... | cut ...。我向他表达了我的意见,然后才访问了他给我的链接,其中提到了该奖项的起源和这样做的做法。进一步的搜索让我找到了这个问题。不幸的是,尽管经过深思熟虑,但所有答案都没有包含我的理由。

我教育他时并不是故意要辩解。毕竟,在我年轻的时候,我会把命令写成这样,grep foo file.txt | cut ... | cut ...因为每当你频繁地执行单个greps 时,你就会了解文件参数的位置,并且很容易知道第一个是模式,后面的是文件名。

我使用前缀来回答这个问题是经过深思熟虑的选择,cat部分原因是“好品味”(用 Linus Torvalds 的话来说),但主要原因是出于令人信服的功能原因。

后者的原因更重要,所以我会先说出来。当我提供管道作为解决方案时,我希望它是可重用的。很可能一个管道会被添加到另一个管道的末尾或拼接到另一个管道中。在这种情况下,给 grep 添加文件参数会破坏可重用性,而且很有可能会这样做默默如果文件参数存在,则不会出现错误消息。即,将给出包含grep foo xyz | grep bar xyz | wc中的行数,而您期望包含和的行数。在使用管道之前必须更改命令的参数很容易出错。再加上无声失败的可能性,它就成为一种特别阴险的做法。xyzbarfoobar

前一个原因也并非不重要,因为许多“好品味”仅仅是直觉的潜意识中对诸如上述无声失败之类的事情的合理解释,而当某个需要教育的人说“但那只猫不是没用的吗”时,你无法立即想到这些理由。

但是,我也会尝试让大家意识到我之前提到的“好品味”原因。这个原因与 Unix 的正交设计精神有关。grep不会cutls不会grep。因此至少grep foo file1 file2 file3违背了设计精神。正交的做法是cat file1 file2 file3 | grep foo。现在,grep foo file1仅仅是 的一个特例grep foo file1 file2 file3,如果你不以同样的方式对待它,你至少会浪费脑力时钟周期来试图避免无用的猫奖。

这引出了我们的论点,即grep foo file1 file2 file3是连接的,并且cat连接起来,所以它是适当的,cat file1 file2 file3但是因为cat不是连接的,cat file1 | grep foo所以我们违反了和万能的 Unix 的精神cat。好吧,如果是这样的话,那么 Unix 需要一个不同的命令来读取一个文件的输出并将其吐出到 stdout(不是对其进行分页或任何其他操作,只是纯粹地吐出到 stdout)。所以你会遇到这种情况,你说cat file1 file2或你说dog file1并认真记住避免cat file1获得奖励,同时也避免dog file1 file2因为希望的设计dog会在指定多个文件时抛出错误。

希望此时您能够同情 Unix 设计者,因为他们没有包含单独的命令来将文件吐到标准输出,同时也cat为连接命名而不是给它其他名称。<edit>有这样一条狗,不幸的<操作符。不幸的是,它被放置在管道的末端,阻碍了可组合性。没有语法上或美学上干净的方法将它放在开头。不幸的是,它不够通用,所以你从狗开始,但如果你也想在前一个文件名之后处理它,只需添加另一个文件名即可。(另一方面,这>并不算糟糕。它在末尾的位置几乎完美。它通常不是管道的可重用部分,因此它在符号上是不同的。)</edit>

下一个问题是,为什么只将一个文件或多个文件的串联输出到 stdout 而不进行任何进一步处理的命令很重要?一个原因是避免让每个对标准输入进行操作的 Unix 命令都知道如何解析至少一个命令行文件参数并将其用作输入(如果存在)。第二个原因是避免用户必须记住:(a) 文件名参数放在哪里;(b) 避免上述静默管道错误。

这让我们明白了为什么grep有额外的逻辑。这样做的理由是让用户能够流畅地执行经常使用的命令,并且独立基础(而不是管道)。这是对正交性的轻微妥协,但显著提高了可用性。并非所有命令都应以这种方式设计,不常用的命令应完全避免文件参数的额外逻辑(请记住,额外的逻辑会导致不必要的脆弱性(出现错误的可能性))。例外情况是允许文件参数,例如grep. 的情况(顺便说一句,请注意,这ls有完全不同的理由,不仅接受而且几乎需要文件参数)

grep最后,如果像 这样的例外命令(但不一定)在标准输入可用时生成错误,那么可以做得更好ls。这是合理的,因为这些命令包含的逻辑违反了万能的 Unix 为方便用户而制定的正交精神。为了进一步方便用户,即为了防止由静默故障造成的痛苦,这些命令应该毫不犹豫地违反自己的规定,在有静默故障的可能性时提醒用户。

相关内容