答案1
该构造<(tac file)
导致壳:
- 创建一个具有名称的管道
- 在 Linux 和 SysV 等具有 的系统上
/dev/fd
,使用常规管道,并将/dev/fd/<the-file-descriptor-of-the-pipe>
其用作名称。 - 在其他系统上,使用命名管道,这需要在磁盘上创建实际的文件条目。
- 在 Linux 和 SysV 等具有 的系统上
- 启动命令
tac file
并将其连接到管道的一端。 - 将命令行上的整个结构替换为管道的名称。
替换后命令行变为:
grep whatever < /tmp/whatever-name-the-shell-used-for-the-named-pipe
然后grep
执行,它读取其标准输入(即管道),读取它,并在其中搜索其第一个参数。
所以最终的结果是一样的......
tac file | grep whatever
...因为启动了相同的两个程序并且仍然使用管道来连接它们。但<( ... )
构建过程更加复杂,因为它涉及更多步骤,并且可能涉及临时文件(命名管道)。
该<( ... )
构造是一个扩展,在标准 POSIX bourne shell 或不支持/dev/fd
或命名管道的平台上不可用。仅出于这个原因,由于正在考虑的两种替代方案在功能上完全相同,因此更便携的command | other-command
形式是更好的选择。
由于额外的卷积,构建<( ... )
应该会更慢,但这只是在启动阶段,我不认为差异很容易测量。
笔记:在 Linux SysV 平台上,< ( ... )
不使用命名管道,而是使用常规管道。常规管道(实际上是所有文件描述符)可以通过特殊命名来引用,/dev/fd/<file-descriptor-number
因此 shell 使用该名称作为管道的名称。通过这种方式,它可以避免在真实文件系统中创建具有真实临时文件名的真实命名管道。尽管/dev/fd
技巧是最初出现在 中时用于实现此功能的技巧ksh
,但它是一种优化:在不支持此功能的平台上,将如上所述使用实际文件系统中的常规命名管道。
另请注意:将语法描述为<<( ... )
具有误导性。事实上它是<( ... )
,它被替换为管道的名称,然后作为<
整个事物前缀的另一个字符与此语法分开,并且它是用于从文件重定向输入的常规众所周知的语法。
答案2
| 有什么区别和 <<()?
他们之间有一个区别:
|
导致每个命令在单独的子 shell 中运行。<()
运行该命令,该命令在后台替换。
对于接下来的两个问题,我们将做一些strace
:
pipe
:
$ strace -fc bash -c 'tac /usr/share/dict/american-english | grep qwerty'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.008120 2707 3 1 wait4
0.00 0.000000 0 352 read
0.00 0.000000 0 229 write
0.00 0.000000 0 20 2 open
0.00 0.000000 0 29 2 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 117 lseek
0.00 0.000000 0 38 mmap
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 25 brk
0.00 0.000000 0 22 rt_sigaction
0.00 0.000000 0 18 rt_sigprocmask
0.00 0.000000 0 1 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 24 12 access
0.00 0.000000 0 1 pipe
0.00 0.000000 0 2 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 2 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 0.008120 1034 37 total
Process Substitution
:
$ strace -fc bash -c 'grep qwerty < <(tac /usr/share/dict/american-english)'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.14 0.016001 4000 4 2 wait4
0.46 0.000075 0 229 write
0.24 0.000038 0 341 read
0.16 0.000026 1 24 brk
0.00 0.000000 0 21 2 open
0.00 0.000000 0 27 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 117 lseek
0.00 0.000000 0 38 mmap
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 35 rt_sigaction
0.00 0.000000 0 24 rt_sigprocmask
0.00 0.000000 0 2 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 24 12 access
0.00 0.000000 0 1 pipe
0.00 0.000000 0 3 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 3 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 1 fcntl
0.00 0.000000 0 2 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 0.016140 1046 37 total
为什么有些东西比其他东西更快?
什么才是真正更快的呢?
您可以看到,比本例process substitution
慢,因为它使用了更多的系统调用。pipe
两者都花费大量时间等待子进程,但process substitution
使用更多的wait4()
系统调用,并且每次调用使用的时间都比pipe
.
为什么没有人建议 xargs ?
我认为xargs
这里没有什么帮助,这不是它的工作。
更新
正如 @Gilles 的建议,我使用更大的文件(从/dev/urandom
.这表明pipe
确实比 快process substitution
。
pipe
:
$ strace -fc bash -c 'tac sample.txt | grep qwerty'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
81.15 8.284959 2761653 3 1 wait4
17.89 1.825959 2 780959 read
0.91 0.092708 0 524286 write
0.05 0.005364 0 262146 lseek
0.00 0.000000 0 20 2 open
0.00 0.000000 0 29 2 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 38 mmap
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 25 brk
0.00 0.000000 0 22 rt_sigaction
0.00 0.000000 0 18 rt_sigprocmask
0.00 0.000000 0 1 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 24 12 access
0.00 0.000000 0 1 pipe
0.00 0.000000 0 2 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 2 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 10.208990 1567727 37 total
process substitution
:
$ strace -fc bash -c 'grep qwerty < <(tac sample.txt)'
$ time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.51 13.912869 3478217 4 2 wait4
0.38 0.053373 0 655269 read
0.09 0.013084 0 524286 write
0.02 0.002454 0 262146 lseek
0.00 0.000030 1 38 mmap
0.00 0.000024 1 24 12 access
0.00 0.000000 0 21 2 open
0.00 0.000000 0 27 close
0.00 0.000000 0 40 17 stat
0.00 0.000000 0 19 fstat
0.00 0.000000 0 18 mprotect
0.00 0.000000 0 6 munmap
0.00 0.000000 0 24 brk
0.00 0.000000 0 35 rt_sigaction
0.00 0.000000 0 24 rt_sigprocmask
0.00 0.000000 0 2 rt_sigreturn
0.00 0.000000 0 3 2 ioctl
0.00 0.000000 0 1 pipe
0.00 0.000000 0 3 dup2
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 1 getpeername
0.00 0.000000 0 3 clone
0.00 0.000000 0 3 execve
0.00 0.000000 0 1 uname
0.00 0.000000 0 1 1 fcntl
0.00 0.000000 0 2 getrlimit
0.00 0.000000 0 13 getuid
0.00 0.000000 0 13 getgid
0.00 0.000000 0 13 geteuid
0.00 0.000000 0 13 getegid
0.00 0.000000 0 1 getppid
0.00 0.000000 0 1 getpgrp
0.00 0.000000 0 3 arch_prctl
0.00 0.000000 0 1 time
------ ----------- ----------- --------- --------- ----------------
100.00 13.981834 1442060 37 total
答案3
我曾是不是能够复制显示的结果库恩勒姆。即使对于 2GB 的文件,在 MacOS Mojave 上的 Bash 5 中,我也看到进程替换和管道之间的时间非常相似。这对我来说很有意义,因为调用所涉及的开销将是最小的与对 2GB 文件的调用的实际处理相比,因此使用进程替换与管道的一次迭代将取决于随机性/首先运行哪个命令来缓存文件内容。
我曾是能够复制研究结果在这个问题中这表明进程替换比管道更快拨打这些电话的次数超过数千次。
这是我运行的命令和输出:
管道.sh:
shopt -s lastpipe
for i in {1..5000}; do
echo foo bar |
while read; do
echo $REPLY >/dev/null
done
done
proc-sub.sh:
for i in {1..5000}; do
while read; do
echo $REPLY >/dev/null
done < <(echo foo bar)
done
管道无最后管道.sh:
for i in {1..5000}; do
echo foo bar |
while read; do
echo $REPLY >/dev/null
done
done
测试:
time ./proc-sub.sh
real 0m9.505s
user 0m1.875s
sys 0m10.705s
time ./pipe.sh
real 0m14.036s
user 0m4.583s
sys 0m14.193s
time ./pipe-no-lastpipe.sh
real 0m16.696s
user 0m3.055s
sys 0m18.057s