Useless use of cat?
这可能是许多常见问题解答 - 而不是使用:
1 | cat file | command |
(这被称为无用的猫),正确的方式应该是:
1 | command < file |
在第二,"正确"的方式 - 操作系统不必产生额外的过程。
尽管知道这一点,我继续使用无用的猫有两个原因。
更美观 - 我喜欢数据仅从左到右均匀移动。 并且更容易用其他东西(
我"觉得"在某些情况下可能会更快。 更快,因为有2个进程,第一个(
我的逻辑是否正确(第二个原因)?
直到今天我还没有意识到这个奖项,当时有一个新手试图将UUOC钉在我身上,以获得我的一个答案。那是
我并不打算在回应他时采取防御措施。毕竟,在我年轻的时候,我会把命令编写为
当我回答问题时,使用
后一个原因更重要,所以我会先说出来。当我提供管道作为解决方案时,我希望它可以重复使用。管道很可能会在最后添加或拼接到另一个管道中。在这种情况下,如果文件参数存在,那么grep的文件参数会破坏可重用性,并且很可能在没有错误消息的情况下静默执行。 I. e。
由于很多"好味道",前一个原因并不重要
对于诸如上面的沉默失败之类的东西来说,仅仅是一个直观的潜意识理论,当一些需要教育的人说"但不是那只猫没用"时,你无法想到这一点。
但是,我会尽量让我意识到前面提到的"好味道"的原因。这个原因与Unix的正交设计精神有关。
这导致我们认为
希望,在这一点上,你同情Unix设计者没有包含一个单独的命令来将文件吐出到stdout,同时也命名
接下来的问题是,为什么在没有任何进一步处理的情况下将命令仅仅吐出文件或将多个文件串联到stdout很重要?一个原因是避免让每个运行在标准输入上的Unix命令知道如何解析至少一个命令行文件参数,并将其用作输入(如果存在)。第二个原因是避免用户必须记住:(a)文件名参数的去向; (b)避免如上所述的无声管道错误。
这让我们知道为什么
最后,如果在指定文件参数时标准输入也可用,那么如果
好。
不!
首先,重定向发生在命令的哪个位置并不重要。所以如果你喜欢你的命令左边的重定向,那很好:
1 | < somefile command |
是相同的
1 | command < somefile |
其次,使用管道时会发生n + 1个进程和子shell。它最明显更慢。在某些情况下,n将为零(例如,当您重定向到内置的shell时),因此通过使用
作为概括,每当你发现自己使用管道时,值得花30秒时间来看看你是否可以消除它。 (但可能不值得花费超过30秒。)以下是一些不必要地经常使用管道和流程的示例:
1 2 3 4 5 | for word in $(cat somefile); … # for word in $(<somefile); … (or better yet, while read < somefile) grep something | awk stuff; # awk '/something/ stuff' (similar for sed) echo something | command; # command <<< something (although echo would be necessary for pure POSIX) |
随意编辑以添加更多示例。
我不同意过度自负的UUOC奖的大多数情况,因为在教别人时,
在Stack Overflow,ServerFault,Unix和Linux或任何SE站点等网站上尤其如此。
如果某人专门询问优化,或者您想添加有关它的额外信息,那么,很好,请谈谈如何使用cat效率低下。但是不要因为他们选择以他们的例子中的简单性和易于理解为目标而不是看我这么酷的时候来谴责别人!复杂。
总之,因为猫并不总是猫。
另外,因为大多数喜欢四处寻找UUOC的人都是这样做的,因为他们更关心的是炫耀他们的"聪明"程度,而不是帮助或教导人们。实际上,他们证明了他们可能只是另一个新手,他们找到了一个小棒来击败同龄人。
更新
这是我在https://unix.stackexchange.com/a/301194/7696的答案中发布的另一个UUOC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | sqlq() { local filter filter='cat' # very primitive, use getopts for real option handling. if ["$1" =="--delete-blank-lines" ] ; then filter='grep -v"^$"' shift fi # each arg is piped into sqlplus as a separate command printf"%s ""$@" | sqlplus -S sss/eee@sid | $filter } |
UUOC的学生会说这是一个UUOC,因为很容易将
如果需要有多个可选的输出过滤器,则选项处理可以根据需要随时将
对于UUoC版本,
使用:
1 | cat"$@" | command |
为了防御猫:
是,
1 | < input process > output |
要么
1 | process < input > output |
效率更高,但许多调用没有性能问题,所以你不在乎。
人体工程学原因:
我们习惯从左到右阅读,所以命令就像
1 | cat infile | process1 | process2 > outfile |
理解是微不足道的。
1 | process1 < infile | process2 > outfile |
必须跳过process1,然后从左到右阅读。这可以通过以下方式治愈:
1 | < infile process1 | process2 > outfile |
看起来不知何故,好像有一个箭头指向左边,没有任何东西。更令人困惑,看起来像花哨的引用是:
1 | process1 > outfile < infile |
并且生成脚本通常是一个迭代过程,
1 2 3 4 | cat file cat file | process1 cat file | process1 | process2 cat file | process1 | process2 > outfile |
你在哪里逐步看到你的进步
1 | < file |
甚至不起作用。简单的方法不易出错,符合人体工程学的命令连接对于cat来说很简单。
另一个话题是,大多数人在使用计算机之前和作为程序员使用计算机之前,都接触过>和<作为比较运算符,因此更多地接触到这些。
并且将两个操作数与<和>进行比较是对比交换,这意味着
1 | (a > b) == (b < a) |
我记得第一次使用<输入重定向时,我担心
1 | a.sh < file |
可能意味着相同
1 | file > a.sh |
并以某种方式覆盖我的a.sh脚本。对许多初学者来说,这可能是一个问题。
罕见的差异
1 2 3 4 | wc -c journal.txt 15666 journal.txt cat journal.txt | wc -c 15666 |
后者可以直接用于计算。
1 | factor $(cat journal.txt | wc -c) |
当然<也可以在这里使用,而不是文件参数:
1 2 3 4 | < journal.txt wc -c 15666 wc -c < journal.txt 15666 |
但是谁在乎 - 15k?
如果我偶尔会遇到问题,我肯定会改变调用猫的习惯。
当使用非常大或许多文件时,避免使用cat就可以了。对于大多数问题,猫的使用是正交的,偏离主题,而不是问题。
在每个第二个shell主题上开始这些无用的无用的cat讨论只会令人烦恼和无聊。在处理表演问题时,获得生命,等待一分钟的成名。
另一个问题是管道可以静默掩盖子壳。对于此示例,我将
1 2 3 4 5 | echo"foo" | while read line; do x=$line done echo"$x" |
您可能希望
在bash4中,您可以配置一些shell选项,以便管道的最后一个命令在与启动管道的shell相同的shell中执行,但是您可以尝试这个
1 2 3 | echo"foo" | while read line; do x=$line done | awk '...' |
并且
作为经常指出这个以及其他一些shell编程反模式的人,我觉得有必要,姗姗来迟,权衡。
Shell脚本是一种复制/粘贴语言。对于大多数编写shell脚本的人来说,他们不是在学习语言;这只是他们必须克服的一个障碍,以便继续用他们实际上熟悉的语言来做事。
在这种情况下,我认为传播各种shell脚本反模式具有破坏性,甚至可能具有破坏性。理想情况下,有人在Stack Overflow上找到的代码可以通过最少的更改和不完整的理解来复制/粘贴到他们的环境中。
在网络上的众多shell脚本资源中,Stack Overflow很不寻常,因为用户可以通过编辑网站上的问题和答案来帮助塑造网站的质量。但是,代码编辑可能会有问题,因为很容易进行代码作者不想要的更改。因此,我们倾向于留下评论来建议对代码的更改。
UUCA和相关的反模式评论不仅适用于我们评论的代码的作者;他们同样是一个警告,可以帮助网站的读者了解他们在这里找到的代码中的问题。
我们无法希望实现Stack Overflow上没有答案推荐无用的
就技术原因而言,传统观点认为我们应尽量减少外部流程的数量;在编写shell脚本时,这仍然是一个很好的一般指导。
我经常在示例中使用
-
很容易理解发生了什么。
在读取UNIX命令时,您需要一个命令,后跟参数,然后是重定向。可以将重定向放在任何地方但很少见 - 因此人们将更难以阅读该示例。我相信
1cat foo | program1 -o option -b option | program2比阅读更容易阅读
1program1 -o option -b option < foo | program2如果将重定向移至开头,则会让那些不熟悉此语法的人感到困惑:
1< foo program1 -o option -b option | program2示例应该易于理解。
-
它很容易改变。
如果您知道程序可以从
cat 读取,您通常可以假设它可以读取输出到STDOUT的任何程序的输出,因此您可以根据自己的需要调整它并获得可预测的结果。 -
它强调如果STDIN不是文件,程序不会失败。
假设如果
program1 < foo 起作用,那么cat foo | program1 也将起作用是不安全的。但是,假设相反是安全的。如果STDIN是文件,则此程序有效,但如果输入是管道,则该程序失败,因为它使用seek:1
2
3
4
5# works
< foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
# fails
cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'
性能成本
执行额外的
1 2 3 4 5 6 7 8 | cat $ISO | cat < $ISO cat cat $ISO | bzip2 < $ISO | bzip2 cat $ISO | gzip < $ISO gzip cat $ISO | grep no_such_string < $ISO grep no_such_string |
测试在低端系统(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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | CPU 0.6 GHz ARM Command cat $ISO| <$ISO Diff Diff (pct) Throughput \ Time (ms) User Sys Real User Sys Real User Sys Real User Sys Real Baseline (cat) 55 14453 33090 23 6937 33126 32 7516 -36 239 208 99 Low (bzip2) 1945148 16094 1973754 1941727 5664 1959982 3420 10430 13772 100 284 100 Medium (gzip) 413914 13383 431812 407016 5477 416760 6898 7906 15052 101 244 103 High (grep no_such_string) 80656 15133 99049 79180 4336 86885 1476 10797 12164 101 349 114 CPU Core i7 2.2 GHz Command cat $ISO| <$ISO Diff Diff (pct) Throughput \ Time (ms) User Sys Real User Sys Real User Sys Real User Sys Real Baseline (cat) 0 356 215 1 84 88 0 272 127 0 423 244 Low (bzip2) 136184 896 136765 136728 160 137131 -545 736 -366 99 560 99 Medium (gzip) 26564 788 26791 26332 108 26492 232 680 298 100 729 101 High (grep no_such_string) 264 392 483 216 84 304 48 308 179 122 466 158 |
结果表明,对于低和中等吞吐量,成本约为1%。这完全在测量的不确定性范围内,因此在实践中没有区别。
对于高吞吐量,差异更大,两者之间存在明显差异。
这导致了结论:如果出现以下情况,则应使用
- 处理的复杂性类似于简单的grep
- 性能比可读性更重要。
否则,使用
因此,您应该仅在以下情况下颁发UUoC奖励:
- 您可以测量性能的显着差异(在您颁发奖项时公布测量结果)
- 性能比可读性更重要。
我认为(使用管道的传统方式)更快一点;在我的盒子上,我使用
没有管道:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | toc@UnixServer:~$ strace wc -l < wrong_output.c execve("/usr/bin/wc", ["wc","-l"], [/* 18 vars */]) = 0 brk(0) = 0x8b50000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ad000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0 mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a5000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3 read(3,"\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0 mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7627000 mmap2(0xb779f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb779f000 mmap2(0xb77a2000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a2000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7626000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb76268d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0xb779f000, 8192, PROT_READ) = 0 mprotect(0x804f000, 4096, PROT_READ) = 0 mprotect(0xb77ce000, 4096, PROT_READ) = 0 munmap(0xb77a5000, 29107) = 0 brk(0) = 0x8b50000 brk(0x8b71000) = 0x8b71000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7426000 mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb72b6000 close(3) = 0 open("/usr/share/locale/locale.alias", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=2570, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77ac000 read(3,"# Locale name alias data base. #"..., 4096) = 2570 read(3,"", 4096) = 0 close(3) = 0 munmap(0xb77ac000, 4096) = 0 open("/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr_FR.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr_FR.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr_FR/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale-langpack/fr/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=316721, ...}) = 0 mmap2(NULL, 316721, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7268000 close(3) = 0 open("/usr/lib/i386-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=26064, ...}) = 0 mmap2(NULL, 26064, PROT_READ, MAP_SHARED, 3, 0) = 0xb7261000 close(3) = 0 read(0,"#include<stdio.h> int main(int"..., 16384) = 180 read(0,"", 16384) = 0 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7260000 write(1,"13 ", 313 ) = 3 close(0) = 0 close(1) = 0 munmap(0xb7260000, 4096) = 0 close(2) = 0 exit_group(0) = ? |
并用管道:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | toc@UnixServer:~$ strace cat wrong_output.c | wc -l execve("/bin/cat", ["cat","wrong_output.c"], [/* 18 vars */]) = 0 brk(0) = 0xa017000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb774b000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=29107, ...}) = 0 mmap2(NULL, 29107, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3 read(3,"\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\222\1\0004\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1552584, ...}) = 0 mmap2(NULL, 1563160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb75c5000 mmap2(0xb773d000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x178) = 0xb773d000 mmap2(0xb7740000, 10776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7740000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb75c4000 set_thread_area({entry_number:-1 -> 6, base_addr:0xb75c48d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 mprotect(0xb773d000, 8192, PROT_READ) = 0 mprotect(0x8051000, 4096, PROT_READ) = 0 mprotect(0xb776c000, 4096, PROT_READ) = 0 munmap(0xb7743000, 29107) = 0 brk(0) = 0xa017000 brk(0xa038000) = 0xa038000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=5540198, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb73c4000 mmap2(NULL, 1507328, PROT_READ, MAP_PRIVATE, 3, 0x2a8) = 0xb7254000 close(3) = 0 fstat64(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 open("wrong_output.c", O_RDONLY|O_LARGEFILE) = 3 fstat64(3, {st_mode=S_IFREG|0664, st_size=180, ...}) = 0 read(3,"#include<stdio.h> int main(int"..., 32768) = 180 write(1,"#include<stdio.h> int main(int"..., 180) = 180 read(3,"", 32768) = 0 close(3) = 0 close(1) = 0 close(2) = 0 exit_group(0) = ? 13 |
您可以使用