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 cat
Award带给你的。
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
. 读起来像这样的流水线真的很容易理解,并且可以在各自的功能中轻松调试各个组件。
Sté*_*las 26
以下是一些缺点的总结:
cat $file | cmd
Run Code Online (Sandbox Code Playgroud)
超过
< $file cmd
Run Code Online (Sandbox Code Playgroud)
首先,请注意:上面有(为了讨论的目的)缺少双引号$file
。在 的情况下cat
,这总是一个问题,除了zsh
; 在重定向的情况下,只有在交互时(不是在脚本中),这只是bash
orksh88
和其他一些 shell(包括bash
在 POSIX 模式下)的问题。
最常提到的缺点是产生了额外的进程。请注意,如果cmd
是内置的,则在某些 shell 中甚至是 2 个进程,例如bash
.
仍然在性能方面,除了在cat
内置的 shell 中,还有一个额外的命令正在执行(当然还有加载和初始化(以及它链接到的库))。
仍然在性能方面,对于大文件,这意味着系统将不得不交替调度cat
和cmd
进程并不断填充和清空管道缓冲区。即使cmd
确实1GB
很大read()
,在一次系统调用,控制将不得不来回之间cat
,并cmd
因为管道不能持有超过一次数据几千字节。
某些cmd
s(如wc -c
)可以在它们的 stdin 是常规文件时进行一些优化,cat | cmd
因为它们的 stdin 只是一个管道,因此它们无法处理。使用cat
和管道,这也意味着它们不能seek()
在文件中。对于像tac
or 之类的命令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
何处cmd1
和cmd2
它们的重定向同时和独立执行,并且您需要将其编写为{ cmd1 | cmd2; } < file > file2
或(cmd1 | cmd2 > file2) < file
例如避免file2
被破坏cmd1
和cmd2
在file
无法打开时运行。
小智 16
放在<file
管道的末尾比cat file
在开始时可读性差。自然英语从左到右阅读。
<file
我会说,放置管道的开头也比 cat 可读性差。一个词比一个符号更易读,尤其是一个似乎指向错误方向的符号。
使用cat
保留command | command | command
格式。
这里的其他答案似乎没有直接解决的一件事是,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
进程的额外工作,也是两次而不是一次读取和写入文件字节的额外工作。现在,实际上,在现代系统上,这并没有太大的区别——它可能会让你的系统做几微秒的不必要的工作。但是,如果它是针对您计划分发的脚本,可能是在功能已经不足的机器上使用它的人,那么几微秒可以在大量迭代中累加。
归档时间: |
|
查看次数: |
6748 次 |
最近记录: |