没用猫的用法?

这可能是在许多常见问题 – 而不是使用:

cat file | command 

(这被称为无用的猫),正确的方式应该是:

 command < file 

第二,“正确”的方式 – 操作系统不必产生额外的过程。
尽管知道,我继续使用无用的猫有两个原因。 第一 – 更美观 – 我喜欢数据只从左到右统一移动。 而且用别的东西(gzcat,echo,…)替换cat更容易,添加第二个文件或插入新的过滤器(pv,mbuffer,grep …)。

第二个原因 – 我“觉得”在某些情况下可能会更快。 因为有两个过程,所以更快,第一(猫)做阅读,第二做什么。 而且它们可以并行运行,这意味着执行速度有时会更快。

我的逻辑是正确的(第二个原因)?

直到今天,我还没有意识到这个奖项,当时一些新手试图把UUOC钉在我身上,我的一个答案是。 这是一个cat file.txt | grep foo | cut ... | cut ... cat file.txt | grep foo | cut ... | cut ... cat file.txt | grep foo | cut ... | cut ... 我给了他一个我的想法,只有在这样做后,他访问了他给我的链接,指的是奖励的起源和做法。 进一步的搜索引起了我的这个问题。 有些不幸,尽管有意识的考虑,答案都没有包括我的理由。

我对他的回应并不意味着防守。 毕竟,在我年轻的时候,我会把这个命令写成grep foo file.txt | cut ... | cut ... grep foo file.txt | cut ... | cut ... grep foo file.txt | cut ... | cut ...因为每当你做频繁的单个grep s你学习文件参数的位置,它已经准备好的知识,第一个是模式,后面的是文件名。

当我回答这个问题的时候,使用cat是一个有意识的选择,部分原因是“良好品味”(用Linus Torvalds的话来说),但主要是因为功能强大的原因。

后面的原因更重要,所以我会先排除。 当我提供一个流水线作为解决方案时,我希望它是可重用的。 很可能在一条管道的末端添加一条管道,或者将其连接到另一条管道。 在这种情况下,使用grep的文件参数可以重用可用性,如果文件参数存在,很可能会默默无误发出错误消息。 I. e。 grep foo xyz | grep bar xyz | wc grep foo xyz | grep bar xyz | wc会给你多少行xyz包含bar而你期望的行数包含foobar 。 在使用流水线之前必须将参数更改为流水线中的命令容易出错。 再加上沉默的失败的可能性,这成为一个特别阴险的做法。

前一个原因也并不重要,因为许多“良好的品味”仅仅是一个事物的直觉潜意识的理由,就像上面沉默的失败,当一些需要教育的人说:“但是不是那只猫没用“。

不过,我也会试着去意识到前面提到的“好味道”的原因。 这个原因与Unix的正交设计精神有关。 grep不会cutls不会grep 。 因此,至少grep foo file1 file2 file3违背设计精神。 正交的方式是cat file1 file2 file3 | grep foo 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 file1 | grep foo因此我们违背了cat和全能Unix的精神。 那么,如果是这样的话,那么Unix将需要一个不同的命令来读取一个文件的输出并吐出到标准输出(不分页或任何东西只是一个纯粹的吐出标准输出)。 所以你会有这样的情况,你说cat file1 file2或者你说dog file1并认真记得避免cat file1避免得到奖,同时也避免dog file1 file2因为希望dog的设计会抛出一个错误,如果多个文件指定。

希望在这一点上,你同情Unix设计师,不包括一个单独的命令吐出一个文件到标准输出,同时也命名cat连接,而不是给它一些其他的名字。 <edit>删除了不正确的注释,实际上<是一个高效的无拷贝工具,可以将一个文件吐出到stdout中,这样你就可以在流水线的开头定位,所以unix设计者确实包含了一些专门用于这个</edit>

接下来的问题是为什么只有将文件吐出或将几个文件连接到stdout的命令才是重要的,而不需要进一步处理? 一个原因是避免每一个在标准输入上运行的Unix命令都知道如何解析至少一个命令行文件参数,如果它存在的话就用它作为输入。 第二个原因是避免用户必须记住:(a)文件名参数的位置; 和(b)避免上面提到的静音管道错误。

这让我们知道为什么grep确实有额外的逻辑。 其基本原理是允许用户流畅地使用经常使用和独立使用的命令(而不是管道)。 这是正交性的一个轻微的妥协,可用性显着增加。 并不是所有的命令都应该这样设计,不常用的命令应该完全避免文件参数的额外逻辑(记住额外的逻辑会导致不必要的脆弱性(一个bug的可能性))。 例外是允许像grep那样的文件参数。 (顺便说一下, ls有一个完全不同的理由,不仅仅接受,而且非常需要文件参数)

最后,如果标准输入在指定文件参数时也可用,那么grep (但不一定是ls )这样的例外命令会产生一个错误。 这是合理的,因为这些命令包含违反Unix的正交精神的逻辑,以方便用户使用。 为了进一步的用户便利,即为了防止由于无声故障造成的痛苦,这样的命令应该毫不犹豫地通过具有额外的逻辑来警告用户是否存在无声错误的可能性而违反其自身违规。

不!

首先,发生重定向的命令在哪里并不重要。 所以,如果你喜欢你的命令左侧的重定向,那很好:

 < somefile command 

是相同的

 command < somefile 

其次,当你使用管道时,有n + 1个进程和一个子shell。 这是最明显的慢。 在某些情况下, n应该是零(例如,当你重定向到一个shell内部时),所以通过使用cat你会不必要地增加一个新的进程。

作为一个概括,每当你发现自己使用管道,值得花费30秒,看看你是否可以消除它。 (但可能不值得超过30秒。)下面是一些管道和进程经常被不必要地使用的例子:

 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版本, cat必须将文件读入内存,然后写入管道,命令必须从管道读取数据,所以内核必须复制整个文件三次 ,而在重定向的情况下,内核只需要复制一次文件。 做一件事比做三次更快。

使用:

 cat "$@" | command 

是一个完全不同的,并不一定是无用的cat使用。 如果命令是一个接受零个或多个文件名参数的标准过滤器,并依次处理它们,那么它仍然是无用的。 考虑tr命令:它是一个纯粹的过滤器,忽略或拒绝文件名参数。 要将多个文件提供给它,您必须使用cat ,如图所示。 (当然,有一个单独的讨论, tr的设计不是很好,没有真正的理由,它不能被设计成一个标准的过滤器。)这也可能是有效的,如果你想命令把所有的输入作为一个文件而不是多个单独的文件,即使该命令会接受多个单独的文件:例如, wc就是这样一个命令。

这是无条件无用的cat single-file案例。

我不同意UUOC过度沾沾自喜的大多数情况,因为在教别人的时候, cat是任何命令或者硬皮复杂的命令管道的一个便利的位置持有者,它们产生适合于讨论的问题或任务的输出。

在Stack Overflow,ServerFault,Unix和Linux或任何SE网站上,这一点尤其如此。

如果有人专门询问优化问题,或者如果您想添加额外的信息,那就太好了,请谈谈如何使用猫效率低下。 但是不要反对,因为他们选择的目标是简单和易于理解的例子,而不是看着我如何冷静,我! 复杂。

总之,因为猫不总是猫。

另外,因为大多数喜欢参加UUOC的人都会这样做,因为他们更关心炫耀他们比帮助或教导人更“聪明”。 事实上,他们表明,他们可能只是另一个发现一个小棒击败他们的同伴的新手。


更新

以下是我在https://unix.stackexchange.com/a/301194/7696上发布的另一个UUOC:;

 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\n" "$@" | sqlplus -S sss/eee@sid | $filter } 

UUOC的学生会说这是一个UUOC,因为很容易使$filter默认为空字符串,并让if语句执行filter='| grep -v "^$"' filter='| grep -v "^$"'但IMO,通过不在$filter嵌入管道字符,这个“无用”的cat提供了非常有用的自我记录printf行上的$filter不仅仅是另一个参数到sqlplus ,这是一个可选的用户可选的输出过滤器。

如果有任何需要有多个可选输出过滤器,选项处理可能会追加| whatever | whatever ,只要需要经常$filter ,一个额外的cat都不会损害任何东西或造成任何明显的性能损失。

另一个问题是管道可以静静地掩盖子壳。 对于这个例子,我会用echo代替cat ,但同样的问题存在。

 echo "foo" | while read line; do x=$line done echo $x 

你可能期望x包含foo ,但是不包含。 你设置的x是在一个subshel​​l中产生执行while循环。 在启动管道的shell中, x具有不相关的值,或者根本没有设置。

在bash4中,可以配置一些shell选项,以便管道的最后一个命令在启动管道的那个shell中执行,但是你可以试试

 echo "foo" | while read line; do x=$line done | awk '...' 

而且x是局部的。

我认为使用管道的传统方式要快一些, 在我的盒子上我使用strace命令来看看发生了什么事情:

无管道:

 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.\n#"..., 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>\n\nint 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\n", 313 ) = 3 close(0) = 0 close(1) = 0 munmap(0xb7260000, 4096) = 0 close(2) = 0 exit_group(0) = ? 

和管道:

 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>\n\nint main(int "..., 32768) = 180 write(1, "#include<stdio.h>\n\nint main(int "..., 180) = 180 read(3, "", 32768) = 0 close(3) = 0 close(1) = 0 close(2) = 0 exit_group(0) = ? 13 

您可以使用stracetime命令进行一些测试,使用更多更长的命令进行良好的基准测试。