如何有效地拆分大文本文件而不拆分多行记录?

Rol*_*olf 9 sed awk text-processing split wc

我有一个很大的文本文件(gz 后约 50Gb)。该文件包含4*N行或N记录;即每条记录由 4 行组成。我想将此文件拆分为 4 个较小的文件,每个文件的大小约为输入文件的 25%。如何在记录边界拆分文件?

一种天真的方法是zcat file | wc -l获取行数,将该数字除以 4,然后使用split -l <number> file. 但是,这会遍历文件两次,并且行计数非常慢(36 分钟)。有没有更好的办法?

这很接近,但不是我要找的。接受的答案也会计算行数。

编辑:

该文件包含 fastq 格式的测序数据。两条记录看起来像这样(匿名):

@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF
Run Code Online (Sandbox Code Playgroud)

每条记录的第一行都以@.

编辑2:

zcat file > /dev/null 需要 31 分钟。

EDIT3: 只有第一行以@. 其他人都不会。见这里。记录需要保持有序。在生成的文件中添加任何内容都是不行的。

mik*_*erv 4

我认为你不能做到这一点 - 不可靠,也不是你要求的方式。问题是,存档的压缩率可能不会从头到尾均匀分布 - 压缩算法将比其他部分更好地应用于某些部分。这就是它的工作原理。因此,您不能根据压缩文件的大小来划分分割。

更重要的是,gzip只是不支持存储大小超过 4GB 的压缩文件的原始大小 - 它无法处理它。因此,您无法查询存档以获得可靠的大小 - 因为它会欺骗您。

4 行的事情 - 这真的很简单。4 个文件的事情 - 我只是不知道如何在不首先提取存档以获得其未压缩大小的情况下可靠且均匀分布地做到这一点。我不认为你可以,因为我尝试过。

但是,您可以做的是设置分割输出文件的最大大小,并确保这些文件始终在记录障碍处被破坏。您可以轻松做到。这是一个小脚本,它将通过提取gzip存档并通过带有特定参数的几个显式dd管道缓冲区传输内容来完成此操作count=$rpt,然后再将其传递lz4以动态解压缩/重新压缩每个文件。我还添加了一些小tee管道技巧,将每个段的最后四行打印到 stderr。

(       IFS= n= c=$(((m=(k=1024)*k)/354))
        b=bs=354xk bs=bs=64k
        pigz -d </tmp/gz | dd i$bs o$b |
        while   read -r line _$((n+=1))
        do      printf \\n/tmp/lz4.$n\\n
        { {     printf %s\\n "$line"
                dd count=$c i$b o$bs
        }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
        } 3>&1| tail -n4 |tee /dev/fd/2 |
                wc -c;ls -lh /tmp/[gl]z*
        done
)
Run Code Online (Sandbox Code Playgroud)

这将继续下去,直到处理完所有输入。它不会尝试按一定百分比进行分割(这是它无法获得的),而是按照每次分割的最大原始字节数进行分割。不管怎样,你的问题的一个重要部分是你无法获得你的存档的可靠大小,因为它太大了 - 无论你做什么,都不要再这样做 - 使分割小于 4gbs 一块这一轮, 或许。这个小脚本至少使您能够执行此操作,而无需将未压缩的字节写入磁盘。

这是一个精简版,只保留了基本内容 - 它没有添加所有报告内容:

(       IFS= n= c=$((1024*1024/354))
        pigz -d | dd ibs=64k obs=354xk |
        while   read -r line _$((n+=1))
        do {    printf %s\\n "$line"
                dd count=$c obs=64k ibs=354xk
        }  |    lz4 -BD -9  >/tmp/lz4.$n
        done
)  </tmp/gz
Run Code Online (Sandbox Code Playgroud)

它所做的所有事情与第一个相同,大多数情况下,它只是没有太多可说的。此外,混乱也减少了,因此可能更容易看到正在发生的事情。

问题IFS=只是read每次迭代处理一行。我们read之所以选择它,是因为我们需要在输入结束时结束循环。这取决于您的记录大小- 根据您的示例,每条记录大小为 354 字节。我gzip用一些随机数据创建了一个 4+GB 的存档来测试它。

随机数据是这样获得的:

(       mkfifo /tmp/q; q="$(echo '[1+dPd126!<c]sc33lcx'|dc)"
        (tr '\0-\33\177-\377' "$q$q"|fold -b144 >/tmp/q)&
        tr '\0-\377' '[A*60][C*60][G*60][N*16][T*]' | fold -b144 |
        sed 'h;s/^\(.\{50\}\)\(.\{8\}\)/@N\1+\2\n/;P;s/.*/+/;H;x'|
        paste "-d\n" - - - /tmp/q| dd bs=4k count=kx2k  | gzip
)       </dev/urandom >/tmp/gz 2>/dev/null
Run Code Online (Sandbox Code Playgroud)

...但也许您不需要太担心,因为您已经拥有数据和所有内容。回到解决方案...

基本上pigz- 解压速度似乎比实际快一点zcat- 通过管道输出未压缩的流,并dd缓冲输出到大小为 354 字节倍数的写入块中。该循环将read$line每次迭代时测试一次输入是否仍然到达,然后在printf调用另一个循环以读取大小为 354 字节倍数的块之前进行循环 - 以与缓冲同步printflz4dddd过程同步 - 持续时间。由于初始原因,每次迭代都会有一次短读取read $line- 但这并不重要,因为lz4无论如何我们都会在 - 我们的收集器进程中打印它。

我已经将其设置为每次迭代都会读取大约 1GB 的未压缩数据,并将流内数据压缩到大约 650Mb 左右。lz4比几乎任何其他有用的压缩方法都要快得多 - 这就是我在这里选择它的原因,因为我不喜欢等待。xz不过,在实际压缩方面可能会做得更好。不过,有一件事lz4是它通常可以以接近 RAM 的速度解压缩 - 这意味着很多时候您可以lz4快速解压缩存档,就像您无论如何都可以将其写入内存一样。

大的每次迭代都会做一些报告。两个循环都会打印dd有关传输的原始字节数和速度等的报告。大循环还将打印每个周期的最后 4 行输入,以及相同的字节数,后面是ls我写入lz4档案的目录。以下是几轮输出:

(       IFS= n= c=$(((m=(k=1024)*k)/354))
        b=bs=354xk bs=bs=64k
        pigz -d </tmp/gz | dd i$bs o$b |
        while   read -r line _$((n+=1))
        do      printf \\n/tmp/lz4.$n\\n
        { {     printf %s\\n "$line"
                dd count=$c i$b o$bs
        }|      tee /dev/fd/3|lz4 -BD -9 >/tmp/lz4.$n
        } 3>&1| tail -n4 |tee /dev/fd/2 |
                wc -c;ls -lh /tmp/[gl]z*
        done
)
Run Code Online (Sandbox Code Playgroud)