我应该关心不必要的猫吗?

56 performance pipe shell-script cat

许多命令行实用程序可以从管道或文件名参数中获取输入。对于长 shell 脚本,我发现以 a 开头的链cat使其更具可读性,尤其是在第一个命令需要多行参数的情况下。

相比

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla
Run Code Online (Sandbox Code Playgroud)

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla
Run Code Online (Sandbox Code Playgroud)

后一种方法效率较低吗?如果是这样,差异是否足以关心脚本是否每秒运行一次?可读性的差异并不大。

Cal*_*leb 53

“最终”答案当然是由The Useless Use of catAward带给你的。

cat 的目的是连接(或“连接”)文件。如果它只是一个文件,将它与任何内容连接在一起是浪费时间,并且会花费您一个过程。

实例化 cat 只是为了让您的代码以不同的方式读取,只需要多一个进程和多一组不需要的输入/输出流。通常,脚本中的真正阻碍将是低效的循环和实际处理。在大多数现代系统上,额外cat的代码不会影响您的性能,但几乎总有另一种方式来编写您的代码。

正如您所注意到的,大多数程序都能够接受输入文件的参数。然而,总是有一个 shell 内置<函数可以在任何需要 STDIN 流的地方使用,它可以通过在已经运行的 shell 进程中完成工作来节省一个进程。

你甚至可以在你写的地方发挥创意。通常,它会在您指定任何输出重定向或管道之前放置在命令的末尾,如下所示:

sed s/blah/blaha/ < data | pipe
Run Code Online (Sandbox Code Playgroud)

但也不必如此。它甚至可以排在第一位。例如,您的示例代码可以这样编写:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
Run Code Online (Sandbox Code Playgroud)

如果您关心脚本的可读性,并且您的代码足够混乱以致于添加一行 forcat可以使其更容易理解,那么还有其他方法可以清理您的代码。我经常使用的一种方法是将管道分解为逻辑集并将它们保存在函数中,这有助于使脚本在以后易于理解。然后脚本代码变得非常自然,管道的任何一部分都更容易调试。

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data
Run Code Online (Sandbox Code Playgroud)

然后您可以继续使用fix_blahs < data | fix_frogs | reorder | format_for_sql. 读起来像这样的流水线真的很容易理解,并且可以在各自的功能中轻松调试各个组件。

  • 我不知道 `&lt;file` 可以出现在命令之前。这解决了我所有的问题! (30认同)
  • @Tim `&lt;file` 可以出现在命令行的任何位置:`&lt;file grep Needle` 或 `grep &lt;file Needle` 或 `grep Needle &lt;file`。例外是复杂的命令,例如循环和分组;重定向必须在关闭`done`/`}`/`)`/etc 之后出现。@Caleb 这适用于所有 Bourne/POSIX shell。我不同意它是丑陋的。 (9认同)
  • @Gilles,在 bash 中,你可以用 `$(&lt; /some/file)` 替换 `$(cat /some/file)`,它做同样的事情,但避免产生一个进程。 (9认同)
  • 只是为了确认 `$(&lt; /some/file)` 的可移植性有限。它确实适用于 bash,但不适用于 BusyBox ash,例如,或 FreeBSD sh。可能也不适用于 dash,因为最后三个 shell 都是近亲。 (4认同)
  • @Tim:Bash 和 Zsh 都支持这一点,尽管我认为这很丑陋。当我担心我的代码是否漂亮和可维护时,我通常使用函数来清理它。请参阅我的最后一次编辑。 (3认同)
  • @SarahG:即使在现代系统上,`cat` 也会受到`tail`、`wc -c` 或其他一些从将常规或可查找文件作为其标准输入而获益良多的伤害。(参见 [Stéphane Chazelas 的回答](https://unix.stackexchange.com/questions/16279/should-i-care-about-unnecessary-cats/225608#225608) 这个问题。)唯一一次单独的`cat ` process 可以帮助您使用像 `/dev/urandom` 这样的特殊文件,在该文件中读取它需要大量 CPU 时间,而 [`cat` 将其放在一个单独的进程中](https://unix. stackexchange.com/q/323845/79808#comment571509_324212)。 (2认同)

Sté*_*las 26

以下是一些缺点的总结:

cat $file | cmd
Run Code Online (Sandbox Code Playgroud)

超过

< $file cmd
Run Code Online (Sandbox Code Playgroud)
  • 首先,请注意:上面有(为了讨论的目的)缺少双引号$file。在 的情况下cat,这总是一个问题,除了zsh; 在重定向的情况下,只有在交互时(不是在脚本中),这只是bashorksh88和其他一些 shell(包括bash在 POSIX 模式下)的问题。

  • 最常提到的缺点是产生了额外的进程。请注意,如果cmd是内置的,则在某些 shell 中甚至是 2 个进程,例如bash.

  • 仍然在性能方面,除了在cat内置的 shell 中,还有一个额外的命令正在执行(当然还有加载和初始化(以及它链接到的库))。

  • 仍然在性能方面,对于大文件,这意味着系统将不得不交替调度catcmd进程并不断填充和清空管道缓冲区。即使cmd确实1GB很大read(),在一次系统调用,控制将不得不来回之间cat,并cmd因为管道不能持有超过一次数据几千字节。

  • 某些cmds(如wc -c)可以在它们的 stdin 是常规文件时进行一些优化,cat | cmd因为它们的 stdin 只是一个管道,因此它们无法处理。使用cat和管道,这也意味着它们不能seek()在文件中。对于像tacor 之类的命令tail,这cat会对性能产生巨大影响,因为这意味着它们需要将整个输入存储在内存中。

  • cat $file,甚至它的更正确的版本cat -- "$file"不会像某些特定文件名正常工作-(或--help什么的开始-,如果你忘记了--)。如果一个人坚持使用cat,他可能应该使用它cat < "$file" | cmd来代替可靠性。

  • 如果$file无法打开读取(访问被拒绝,不存在...),< "$file" cmd将报告一致的错误消息(由外壳程序)并且运行cmd,同时cat $file | cmd仍会运行,cmd但其标准输入看起来像是一个空文件。这也意味着在诸如< file cmd > file2, 之类的东西中,file2如果file无法打开,则不会被破坏。

    或者换句话说,您可以选择输入和输出文件的打开顺序,而不是cmd file > file2总是在输入文件 (by )之前打开输出文件 (由 shell cmd),这几乎是不可取的。

    但是请注意,它无助于在cmd1 < file | cmd2 > file2何处cmd1cmd2它们的重定向同时和独立执行,并且您需要将其编写为{ cmd1 | cmd2; } < file > file2(cmd1 | cmd2 > file2) < file例如避免file2被破坏cmd1cmd2file无法打开时运行。

  • 关于性能:此测试显示差异约为 1 pct,除非您对流 http://oletange.blogspot.dk/2013/10/useless-use-of-cat.html 进行很少的处理 (2认同)
  • @OleTange。这是另一个测试:`truncate -s10G a; 时间 wc -c &lt; a; 时间猫 wc -c; 时间猫 猫 | wc -c`。有很多参数可以进入图片。性能损失可以从 0 到 100%。无论如何,我认为惩罚不会是负面的。 (2认同)
  • `wc -c` 是一个非常独特的例子,因为它有一个快捷方式。如果您改为执行`wc -w`,那么它与我的示例中的`grep` 相当(即很少处理-这是'&lt;' _can_ 产生影响的情况)。 (2认同)

小智 16

放在<file管道的末尾比cat file在开始时可读性差。自然英语从左到右阅读。

<file我会说,放置管道的开头也比 cat 可读性差。一个词比一个符号更易读,尤其是一个似乎指向错误方向的符号。

使用cat保留command | command | command格式。


god*_*eek 9

这里的其他答案似乎没有直接解决的一件事是,cat像这样使用并不是“无用的”,因为“产生了一个不起作用的无关猫进程”;从“产生只做不必要工作的 cat 进程”的意义上说,它是无用的。

在这两种情况下:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'
Run Code Online (Sandbox Code Playgroud)

shell 启动一个从 somefile 或 stdin(分别)读取的 sed 进程,然后进行一些处理 - 它读取直到遇到换行符,用“bar”替换该行上的第一个“foo”(如果有),然后打印那条线到标准输出和循环。

如果是:

cat somefile | sed 's/foo/bar/'
Run Code Online (Sandbox Code Playgroud)

shell 产生一个 cat 进程和一个 sed 进程,并将 cat 的 stdout 连接到 sed 的 stdin。cat 进程从文件中读取几千或几兆字节的块,然后将其写入其标准输出,sed 指令从那里获取,如上面的第二个示例所示。当 sed 正在处理该块时, cat 正在读取另一个块并将其写入其标准输出,以便 sed 下一步处理。

换句话说,添加cat命令所需的额外工作不仅仅是产生额外cat进程的额外工作,也是两次而不是一次读取和写入文件字节的额外工作。现在,实际上,在现代系统上,这并没有太大的区别——它可能会让你的系统做几微秒的不必要的工作。但是,如果它是针对您计划分发的脚本,可能是在功能已经不足的机器上使用它的人,那么几微秒可以在大量迭代中累加。

  • 请参阅 http://oletange.blogspot.dk/2013/10/useless-use-of-cat.html 以测试使用附加 `cat` 的开销。 (2认同)