如何将一个文件有效地流式传输到多个管道

时间:2018-08-14 07:07:13

标签: bash pipe tee

我有一个脚本想要在一个非常大的文件上运行几个程序/管道。示例:

26-06-1993

内核将尝试将文件缓存在RAM中,因此如果很快再次读取它,则可能是RAM中的副本。但是,文件很大,程序运行速度也大相径庭,因此不太可能有效。为了最大程度地减少IO使用率,我想一次读取文件。

我知道使用te​​e和moreutils的小便复制数据的两种方法:

grep "ABC" file > file.filt
md5sum file > file.md5

还有另一种“最佳”方法吗?哪种方法制作的副本最少? >()或|-编辑到哪个程序是否有所不同?如果一个程序太慢,这些方法是否会尝试在RAM中缓冲数据?它们如何扩展到许多阅读器程序?

2 个答案:

答案 0 :(得分:1)

tee(命令)使用fopen打开每个文件,但在每个文件上设置_IONBF(无缓冲)。它从标准输入中read到每个文件* fwrite

pee(命令),每个命令popen,将每个命令设置为无缓冲,从标准输入中设置read,并且将每个文件*设置fwritepopen使用pipe(2),其容量为65536字节。写入完整缓冲区将被阻止。 pee也使用/ bin / sh来解释命令,但我认为不会添加任何缓冲/复制。

mkfifo(命令)使用mkfifo(libc),后者在下面使用管道,打开文件/管道块,直到另一端打开。

bash <>()语法(sub.c:5712)使用pipemkfifopipe(如果支持/ dev / fds)。它不使用c fopen个调用,因此不设置缓冲。

因此,所有三个变体(小便,三通>(),mkfifo ...)都应具有相同的行为,从stdin读取并在不进行缓冲的情况下写入管道。数据在每次读取(从内核到用户)时都重复,然后在每次写入(从用户回到内核)时都重复,我认为 tee的fwrites不会引起额外的复制(因为没有缓冲区)。内存使用量最多可以增加到65536 * num_readers + 1 * read_size(如果没有人正在读取)。 tee首先写入stdout,然后依次写入每个文件/管道。

鉴于此小便只能在其他shell(鱼!)上工作,而缺少>()运算符等效项,因此bash似乎不需要它。如果您不喜欢bash,我更喜欢使用tee,但是如果您不喜欢bee,则它很适合。 bash <()当然不会被小便代替。手动进行mkfifoing和重定向很棘手,不太可能很好地处理错误。

pee可以通过使用tee库函数(而不是fwrite)来实现。我认为这将导致以最快的读取器的速度读取输入,并可能填满内核缓冲区。

答案 1 :(得分:0)

AFAIK,没有“最佳方法”来实现这一目标。但是我可以给您另一种方法,更详细些,不是一条线,而是更清晰些,因为每个命令都是自己编写的。使用命名管道:

mkfifo tmp1 tmp2
tee tmp1 > tmp2 < file &
cat tmp1 | md5sum > file.md5 &
cat tmp2 | grep "ABC" > file.filt &
wait
rm tmp1 tmp2
  1. 创建与要运行的命令一样多的名称管道。
  2. tee将输入文件输入命名管道(tee在标准输出中输出其输入,因此姓氏管道必须是重定向),使其在后台运行。
  3. 使用不同的命名管道作为要运行的不同命令的输入。让它们在后台运行。
  4. 最后,等待作业完成并删除临时命名管道。

这种方法的缺点是,当程序的速度有所变化时,所有程序都将以相同的速度读取文件(限制是缓冲区大小,一旦其中一个管道已满,其他管道将具有也要等待),因此,如果其中之一需要大量资源(如内存不足),则资源将用于所有进程的整个生命周期。