直到今天,我还没有意识到这个奖项,当时有些菜鸟试图将UUOC钉在我身上,以获得我的一个答案。这是一个cat file.txt | grep foo | cut ... | cut ...
。我给了他一个想法,只有在这样做之后,我才能看到他给我的链接,指的是奖项的起源和这样做的实践。进一步的搜索引导我这个问题。有点不幸的是,尽管有意识地考虑,但没有一个答案包括我的理由。
在教育他时,我并不打算采取防御措施。毕竟,在我年轻的时候,我会编写命令,grep foo file.txt | cut ... | cut ...
因为无论何时你经常使用单个grep
s,你都会学习文件参数的位置,并且知道第一个是模式,后者是文件名。
当我用cat
前缀回答问题时,这是一个有意识的选择,部分是因为“良好品味”的原因(用Linus Torvalds的话说),但主要是出于功能的令人信服的理由。
后一个原因更重要,所以我会先说出来。当我提供管道作为解决方案时,我希望它可以重复使用。管道很可能会在最后添加或拼接到另一个管道中。在这种情况下,如果文件参数存在,那么grep的文件参数会破坏可重用性,并且很可能在没有错误消息的情况下静默执行。I. e。grep foo xyz | grep bar xyz | wc
会给你多少行中xyz
含有bar
,而你期待同时包含的行数foo
和bar
。在使用之前必须在管道中更改参数的参数很容易出错。除此之外,它还存在无声失败的可能性,并成为一种特别阴险的做法。
前一个原因并不重要,因为许多“好品味”仅仅是一种直觉的潜意识理论,就像上面的沉默失败一样,当一些需要教育的人说“但不是那只猫无用“。
但是,我会尽量让我意识到前面提到的“好味道”的原因。这个原因与Unix的正交设计精神有关。grep
没有cut
,ls
没有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
因此我们违反cat
了全能的Unix 的精神。那么,如果是这种情况那么Unix就需要一个不同的命令来读取一个文件的输出并将其吐出到stdout(不是将它分页或只是纯粹的spit到stdout)。所以你会遇到你说cat file1 file2
或你说的情况dog file1
并且认真记得避免cat file1
避免获得奖励,同时也避免dog file1 file2
因为dog
如果指定了多个文件,设计将会引发错误。
希望在这一点上你同情Unix设计者不包括一个单独的命令将文件吐出到stdout,同时也命名cat
连接而不是给它一些其他名称。<edit>
有这样一只狗,不幸的<
经营者。不幸的是,它放置在管道末端阻止了易于组合。在开始时没有语法或美学上清洁的方式。不幸的是,不够通用,所以你从狗开始,但只是添加另一个文件名,如果你也希望它在前一个之后处理。(>
另一方面,它不是坏的一半。它在最后几乎是完美的位置。它通常不是管道的可重复使用的部分,因此它具有象征性的区别。)</edit>
接下来的问题是,为什么在没有任何进一步处理的情况下将命令仅仅吐出文件或将多个文件串联到stdout很重要?一个原因是避免让每个运行在标准输入上的Unix命令知道如何解析至少一个命令行文件参数,并将其用作输入(如果存在)。第二个原因是避免用户必须记住:(a)文件名参数的去向; (b)避免如上所述的无声管道错误。
这让我们知道为什么grep
有额外的逻辑。其基本原理是允许用户流畅地使用频繁且独立使用的命令(而不是作为管道)。对于可用性的显着增加,这是对正交性的轻微折衷。并非所有命令都应该以这种方式设计,并且不经常使用的命令应该完全避免文件参数的额外逻辑(记住额外的逻辑会导致不必要的脆弱性(bug的可能性))。例外情况是允许文件参数,例如grep
。(顺便说一下,ls
有一个完全不同的理由不仅接受,而且几乎需要文件参数)
最后,可以做得更好的是,如果标准输入可用,如果grep
(但不一定ls
)这样的例外命令会产生错误。这是合理的,因为命令包含违反全能Unix的正交精神的逻辑,以方便用户。为了进一步方便用户,即为了防止由无声故障引起的痛苦,这些命令应该通过警告用户是否存在无声故障的可能性来毫不犹豫地违反他们自己的违规行为。