bounty将于明天到期。回答此问题可获得+500声望奖励。tripleee希望奖励现有答案:我想奖励乔纳森·莱弗勒简洁而明智的回答
这可能是在许多常见问题解答-而不是使用:
cat file | command
(这叫做猫的无用之用),正确的方法应该是:
command < file
在第二个,“正确”的方式-操作系统不必产生一个额外的进程。
尽管知道这一点,我继续使用无用的猫有两个原因。
1.更美观-我喜欢数据只从左到右均匀移动。而且更容易用其他东西替换cat
(gzcat
,echo
,...),添加第二个文件或插入新的过滤器(pv
,mbuffer
,grep
...)。
1.我“感觉”在某些情况下它可能会更快。更快是因为有2个进程,第一个(cat
)做阅读,第二个做任何事情。而且它们可以并行运行,这意味着有时执行速度更快。
我的逻辑正确吗(第二个原因)?
9条答案
按热度按时间91zkwejq1#
我不知道这个奖项,直到今天,当一些新手试图把UUOC钉在我的答案之一。这是一个
cat file.txt | grep foo | cut ... | cut ...
。我给了他一块我的想法,只有这样做后,访问了链接,他给我指的是奖项的起源和这样做的做法。进一步的搜索导致我这个问题。有点不幸的是,尽管有意识的考虑,这些答复都没有包括我的理由。我并不想为他辩护,毕竟,在我年轻的时候,我会把命令写成
grep foo file.txt | cut ... | cut ...
,因为每当你频繁地执行一个grep
时,你就知道了文件参数的位置,并且已经知道第一个是模式,后面的是文件名。我在回答这个问题时有意识地选择使用
cat
,部分原因是“品味高”(用Linus Torvalds的话来说),但主要原因是功能强大。后一个原因更重要,所以我将首先提出它。当我提供管道作为解决方案时,我希望它是可重用的。很有可能一个管道将被添加到另一个管道的末尾或拼接到另一个管道中。在这种情况下,使用grep的文件参数会破坏可重用性。并且如果文件参数存在的话,很有可能会 * 静默地 * 这样做,而不显示错误消息。
grep foo xyz | grep bar xyz | wc
将给予您xyz
中有多少行包含bar
,而您期望的是同时包含foo
和bar
的行数。在使用管道中的命令之前,必须更改其参数,这很容易出错,再加上可能出现静默失败,这就变成了一种特别危险的做法。前一个原因也不是不重要,因为很多“good taste“仅仅是一个直觉的潜意识的理由,像上面的沉默的失败,你不能认为正确的时刻,当一些人在需要教育说“但不是那只猫无用”的事情.
然而,我也会尝试意识到我提到的前一个“好品味”的原因。这个原因与Unix的正交设计精神有关。
grep
不是cut
,ls
也不是grep
。因此,至少grep foo file1 file2 file3
违背了设计精神。正交的方法是cat file1 file2 file3 | grep foo
。现在,X1 M15 N1 X只不过是X1 M16 N1 X的一个特例,如果你不以同样的方式对待它,你至少是在耗尽大脑时钟周期,试图避免无用的猫奖。这就引出了
grep foo file1 file2 file3
是连接的,而cat
是连接的,所以cat file1 file2 file3
是正确的,但是因为cat
在cat file1 | grep foo
中不是连接的,所以我们违反了cat
和万能的Unix的精神。如果是这种情况,那么Unix将需要不同的命令来读取一个文件的输出,并将其发送到stdout(不分页它或任何东西,只是一个纯粹的吐到stdout)。所以你会遇到这样的情况,你说cat file1 file2
或dog file1
,并自觉地记住避免cat file1
,以避免获奖,同时还避免了dog file1 file2
,因为如果指定多个文件,dog
的设计可能会抛出错误。希望此时您能同情Unix设计者,因为他们没有包含一个单独的命令来将文件拆分到stdout,同时还将
cat
命名为concatenate,而不是给它取其他名称。<edit>
删除了<
上的不正确注解,事实上,<
是一种高效的无拷贝工具,用于将文件拆分到stdout,您可以将其定位在管道的开始位置,因此Unix设计人员确实包含了一些专门用于此的功能</edit>
下一个问题是,为什么仅仅将一个文件或几个文件连接到stdout而不进行任何进一步处理的命令很重要?一个原因是避免让每个操作标准输入的Unix命令都知道如何解析至少一个命令行文件参数,并将其用作输入(如果存在)。第二个原因是避免用户必须记住:(a)文件名参数的位置;以及(B)避免如上所述的无声流水线缺陷。
这就引出了为什么
grep
具有额外的逻辑,其基本原理是允许用户流畅地使用频繁使用的命令,并且是在“独立”的基础上使用的(而不是作为流水线)。它是正交性的轻微妥协,以获得可用性的显著增益。并非所有命令都应该这样设计,不常用的命令应该完全避免文件参数的额外逻辑(记住额外的逻辑会导致不必要的脆弱性(bug的可能性))例外是允许文件参数,就像grep
的情况一样(顺便说一句,注意ls
有一个完全不同的原因,不仅接受文件参数,而且几乎需要文件参数)最后,如果在指定文件参数时标准输入也可用,那么
grep
(但不一定是ls
)等异常命令会生成错误,这本可以做得更好。ar7v8xwq2#
不要!
首先,重定向发生在命令的什么地方并不重要,所以如果你喜欢重定向到命令的左边,那就很好:
等于
第二,当你使用管道的时候,会有 * n +1 * 个进程和一个子shell出现,这无疑是比较慢的,在某些情况下,* n * 会是零(例如,当你重定向到一个shell内置程序时),所以使用
cat
你完全没有必要添加一个新进程。一般来说,无论何时发现自己在使用管道,都值得花30秒的时间看看是否可以消除它。(但可能不值得花超过30秒的时间。)下面是一些经常不必要地使用管道和进程的示例:
请随意编辑以添加更多示例。
7fyelxc53#
为了保护猫:
是的,
或
更有效,但是许多调用没有性能问题,所以您不必关心。
人体工程学原因:
我们习惯于从左到右阅读,所以像这样的命令
是很难理解的。
必须跳过进程1,然后从左向右读取。这可以通过以下方法修复:
不知何故,看起来好像有一个箭头指向左边,那里什么都没有。更令人困惑和看起来像花哨的引用是:
并且生成脚本通常是迭代过程,
你可以看到你的进步,而
甚至不起作用。简单的方法不太容易出错,符合人体工程学的命令连锁是简单的与猫。
另一个主题是,大多数人接触到〉和〈作为比较运算符,早在使用计算机之前,当使用计算机作为程序员时,更经常接触到这些运算符。
将两个操作数与进行比较是反交换的,也就是说< and > is contra commutative, which means
我记得第一次使用〈进行输入重定向时,我担心
意思可能和
并以某种方式覆盖我的www.example.com脚本。也许这对许多初学者来说是个问题。a.sh script. Maybe this is an issue for many beginners.
罕见差异
后者可直接用于计算。
当然,这里也可以使用〈来代替文件参数:
但谁在乎呢一万五
如果我偶尔会遇到问题,我肯定会改变我的习惯,调用猫。
当使用非常大或很多很多的文件时,避免使用cat是可以的。对于大多数问题来说,使用cat是正交的,偏离主题,不是问题。
开始这些无用的无用使用猫讨论每第二个 shell 主题只是恼人和无聊,得到一个生活,等待你的成名分钟,当处理性能问题。
km0tfn4u4#
我不同意大多数过于自鸣得意的UUOC奖的例子,因为当教别人的时候,
cat
是一个方便的占位符,用于任何命令或生成适合所讨论的问题或任务的输出的复杂命令管道。这在Stack Overflow、ServerFault、Unix & Linux或任何SE站点上尤其如此。
如果有人特别问到优化,或者如果你想增加额外的信息,那么,很好,谈谈使用cat是如何低效的,但是不要因为人们选择了简单和易于理解的例子而不是复杂的例子而责骂他们。
简而言之,因为猫并不总是猫。
还有一个原因是,大多数喜欢到处给UUOC颁奖的人之所以这么做,是因为他们更关心炫耀自己有多"聪明",而不是帮助或教导别人。事实上,他们证明了自己可能只是又一个找到了一根小棍子来击败同龄人的新手。
下面是我在https://unix.stackexchange.com/a/301194/7696上发布的另一个UUOC:
UUOCMaven会说这是一个UUOC,因为很容易使
$filter
默认为空字符串,并使if
语句执行filter='| grep -v "^$"'
,但IMO不将管道字符嵌入到$filter
中,这个"无用的"cat
服务于自文档化printf
行上的$filter
不仅仅是sqlplus
的另一个参数的事实的极其有用的目的,它是一个可选的用户可选输出滤波器。如果需要多个可选输出过滤器,选项处理可以根据需要将
| whatever
附加到$filter
-管道中的一个额外cat
不会造成任何损害或导致任何明显的性能损失。gzszwxb45#
在UUoC版本中,
cat
必须将文件读入内存,然后将其写入管道,命令必须从管道中读取数据,因此内核必须复制整个文件 * 三 * 次,而在重定向的情况下,内核只需复制文件一次。使用:
是
cat
的一种完全不同的用法,也不一定是无用的用法。如果该命令是一个接受零个或多个文件名参数并依次处理它们的标准过滤器,它仍然是无用的。请考虑tr
命令:它是一个纯粹的过滤器,忽略或拒绝文件名参数,要向它馈送多个文件,必须使用cat
,如图所示(当然,还有另外一个讨论,tr
的设计不是很好;没有真正的理由不能将其设计为标准过滤器。)如果您希望命令将所有输入视为单个文件而不是多个单独的文件,即使命令接受多个单独的文件,这也可能是有效的:例如wc
就是这样的命令。cat single-file
的情况是无条件无用的。8aqjt8rx6#
作为一个经常指出这一点和许多其他shell编程反模式的人,我感到有义务(尽管为时已晚)加入进来。
Shell脚本在很大程度上是一种复制/粘贴语言,对于大多数编写shell脚本的人来说,他们并不是在里面学习语言;这只是他们必须克服的一个障碍,以便继续用他们实际上有点熟悉的语言做事情。
在这种情况下,我认为传播各种shell脚本反模式是破坏性的,甚至是潜在的破坏性的。人们在Stack Overflow上找到的代码在理想情况下应该可以复制/粘贴到他们的环境中,只需要最小的更改和不完全的理解。
在网络上的许多shell脚本资源中,Stack Overflow是不寻常的,因为用户可以通过编辑网站上的问题和答案来帮助塑造网站的质量。然而,code edits can be problematic是因为它很容易做出代码作者无意中做出的修改。因此,我们倾向于留下评论来建议对代码的修改。
UUCA和相关的反模式注解不仅仅针对我们所注解的代码的作者;他们是一个“告诫买者”,以帮助“读者”的网站成为意识到问题的代码,他们发现在这里。
我们不能指望出现这样的情况:Stack Overflow上没有答案推荐无用的
cat
(或无引号的变量,或chmod 777
,或大量其他反模式问题),但我们至少可以帮助教育用户,让他们将此代码复制/粘贴到脚本的最内层紧密循环中,该循环将执行数百万次。就技术原因而言,传统智慧是我们应该尽量减少外部进程的数量;在编写shell脚本时,这仍然是一个很好的通用指南。
eoxn13cs7#
另一个问题是管道可以静默地屏蔽一个子shell,对于这个例子,我将用
echo
替换cat
,但是同样的问题仍然存在。您可能希望
x
包含foo
,但实际上并非如此。您设置的x
位于为执行while
循环而派生的子shell中。shell中启动管道的x
具有不相关的值,或者根本没有设置。在bash4中,您可以配置一些shell选项,以便管道的最后一个命令与启动管道的命令在同一个shell中执行,但是您可以尝试这样做
并且
x
再次位于while
的子 shell 的本地。41zrol4v8#
我经常在例子中使用
cat file | myprogram
。有时候我被指责为无用地使用cat(http://porkmail.org/era/unix/award.html)。我不同意,原因如下:当阅读UNIX命令时,你期望命令后面跟着参数,然后是重定向。可以将重定向放在任何地方,但很少看到-因此人们将很难阅读这个例子。我相信
更容易阅读
如果你把重定向移到开头,你会把不习惯这个语法的人弄糊涂:
并且示例应该易于理解。
如果您知道程序可以读取
cat
,通常可以假设它可以读取输出到STDOUT的任何程序的输出,因此您可以根据自己的需要调整它并获得可预测的结果。假设
program1 < foo
可以工作,那么cat foo | program1
也可以工作是不安全的,但是相反的假设是安全的。如果STDIN是一个文件,这个程序可以工作,但是如果输入是一个管道,则会失败,因为它使用seek:性能成本
额外的
cat
是有成本的,为了了解成本,我运行了几个测试来模拟基线(cat
)、低吞吐量(bzip2
)、中等吞吐量(gzip
)和高吞吐量(grep
)。测试在低端系统(0.6 GHz)和普通笔记本电脑(2.2 GHz)上运行。每个系统上运行10次,并选择最佳时间来模拟每个测试的最佳情况。$ISO为ubuntu-11.04-desktop-i386.iso。(更好的表格如下:http://oletange.blogspot.com/2013/10/useless-use-of-cat.html)
结果表明,对于中低吞吐量,成本约为1%,这完全在测量的不确定性范围内,因此在实践中没有差异。
对于高吞吐量,差异更大,并且两者之间存在明显差异。
由此得出结论:在以下情况下,应使用
<
而不是cat |
:否则,使用
<
还是cat |
并不重要。因此,只有当且仅当以下情况时,您才应授予UUoC奖项:
6ojccjat9#
我认为(传统的方式)使用管道更快一点;在我的机器上,我使用了
strace
命令来查看发生了什么:无管道:
和管道:
您可以使用
strace
和time
命令进行一些测试,使用更多更长的命令进行良好的基准测试。