如何从具有 1 TB 磁盘空间的系统上的 700 GB txt 文件中删除前 3 亿行?

Kri*_*ris 161 text-processing

如何从一个 700?GB 的文本文件中删除前 3 亿行,该系统的总磁盘空间为 1?TB,可用空间为 300?GB?(我的系统有 2?GB 的内存。)我找到的答案使用 sed、tail、head:

但我认为(请纠正我)我无法使用它们,因为磁盘空间被限制为 1?TB,并且它们在处理过程中生成一个新文件和/或有一个 tmp 文件。

该文件包含 JSON 格式的数据库记录。

fro*_*utz 158

可以使用dd(或使用 loop devices)就地删除前 n 行(或字节)。它不使用临时文件并且没有大小限制;但是,这很危险,因为没有进度跟踪,任何错误都会使您的文件损坏。

示例:创建一个 1000 行的示例文件:

$ seq 1 1000 > 1000lines.txt
$ head -n 3 1000lines.txt
1
2
3
$ tail -n 3 1000lines.txt
998
999
1000
Run Code Online (Sandbox Code Playgroud)

我们要删除前 300 行。它对应多少字节?

$ stat -c %s 1000lines.txt
3893 # total bytes
$ head -n 300 1000lines.txt | wc -c
1092 # first 300 lines bytes
$ echo $((3893-1092))
2801 # target filesize after removal
Run Code Online (Sandbox Code Playgroud)

该文件是 3893 字节,我们要删除前 1092 字节,留下一个 2801 字节的新文件。

要删除这些字节,我们使用 GNUdd命令,conv=notrunc否则文件将在您复制其内容之前被删除:

$ dd conv=notrunc iflag=skip_bytes skip=1092 if=1000lines.txt of=1000lines.txt
5+1 records in
5+1 records out
2801 bytes (2.8 kB, 2.7 KiB) copied, 8.6078e-05 s, 32.5 MB/s
Run Code Online (Sandbox Code Playgroud)

这将删除前 300 行,但现在重复最后 1092 个字节,因为文件尚未截断:

$ truncate -s 2801 1000lines.txt
Run Code Online (Sandbox Code Playgroud)

这将文件缩小到其最终大小,删除文件末尾的重复行。

结果:

$ stat -c %s 1000lines.txt 
2801

$ head -n 3 1000lines.txt
301
302
303

$ tail -n 3 1000lines.txt
998
999
1000
Run Code Online (Sandbox Code Playgroud)

处理较大文件的过程类似。您可能需要设置更大的块大小以获得更好的性能(块大小选项ddbs)。

主要问题是确定确切行号的正确字节偏移量。一般只能通过阅读和计数来完成。使用这种方法,即使您丢弃了大量文件,您也必须至少读取整个文件一次。

  • 如果我的生活依赖于它,我不会使用这个解决方案 ;-) 就地编辑文件可能会导致各种问题,包括完全丢失其内容。 (35认同)
  • @ArtemS.Tashkinov 当然......就地操作总是很危险的。对于非破坏性坏块、lvm pvmove、mdadm 增长也是如此……无论如何,对于那些特别担心 `dd` 的人,我还添加了 [循环设备方法](https://unix.stackexchange.com/a/610558/30851 ) 在另一个答案中。 (16认同)
  • dd 不会创建临时文件。这个答案假设您的文件系统行为正常。如果您将网络或融合文件系统添加到组合中,它可能无法以这种方式工作。 (11认同)

ter*_*don 130

如果您有足够的空间来压缩文件,这应该会释放大量空间,允许您执行其他操作,您可以尝试以下操作:

gzip file && zcat file.gz | tail -n +300000001 | gzip > newFile.gz
Run Code Online (Sandbox Code Playgroud)

这将首先创建gzip原始输入文件 ( file) file.gz。然后,zcat新创建的file.gz,它管道tail -n +300000001去除第一3M线,压缩结果,以节省磁盘空间,并将其保存为newFile.gz。在&&确保您只有在继续gzip操作成功(如果你的空间用完它会失败)。

请注意,文本文件是非常可压缩的。例如,我使用创建了一个测试文件seq 400000000 > file,它打印了从 1 到 400,000,000 的数字,这产生了一个 3.7G 的文件。当我使用上面的命令压缩它时,压缩文件只有849M,而newFile.gz我创建的只有213M。

  • 我猜 700GB 文本文件(带行)的内容也不是很随机。OP 没有指定,我猜它包含诸如日志数据或数据库转储之类的东西。它可能会压缩到原始大小的 25% 以下。如果一切都是 7 位 ASCII 字符,那么已经有大约 50% 的减少空间。 (51认同)
  • `seq` 产生的文本的熵可能非常低,这就是为什么_这个特定的_文本是非常可压缩的。更随机的东西很可能会表现得更糟。 (33认同)
  • @terdon 最后我使用了你的解决方案(我相信其他人也会工作 - 哦,我的,这个小问题是否爆炸了)。压缩文件只有 140GB(具有许多相同字段名称的 JSON 数据)并且适合磁盘。非常感谢! (18认同)
  • `尾巴| gzip` 避免保存要删除的行的压缩副本。 (4认同)
  • @AVee 你不是说 12.5% 的 7 位 ascii 吗?或者你有什么不同的想法? (4认同)
  • @PeterCordes gzip 和 bzip2 都可以在多核上完成,分别使用 pigz 和 lbzip2。 (3认同)
  • RFC 1951 DEFLATE 是一种非常古老的格式。仅将其用于向后兼容。您的默认格式应该是 zstd。它总是比 gzip 好(同时更小和更快),cli 实用程序使用多线程(`-T0`),它比 gzip 具有更广泛的时空权衡范围,并且它具有远程模式. 如果您想要更快的速度,请使用 lz4。如果您想要更好地压缩的东西,请使用 lzma/xz,但它很慢。如果您有自然语言文本,请从 p7zip-full 尝试(慢速)ppmd。 (3认同)
  • OP 表示磁盘上有 340GB 可用空间,因此可能不需要第一个 gzip,因为压缩后生成的较小文件可能适合。这可能会使它快得多。 (2认同)
  • @probably_someone 是的,这是一个公平的观点。这只是最简单的演示方式。 (2认同)
  • @DanielF 没有太多理由拥有这种大小的解压缩文本文件。如果需要从中提取信息,可以使用`zcat`或`zmore`查看,`zgrep`搜索,甚至可以直接在`emacs`这样的好编辑器中打开。 (2认同)
  • @jamesdlin 更像是反过来:为什么要在磁盘上保留一个大的未压缩文本文件?那只是无缘无故地占用空间。由于像 `zcat`、`zgrep` 和 `zmore` 这样的工具可以让您轻松操作/提取压缩数据,因此没有充分的理由保持文件未压缩。此外,在这种情况下,当我写答案时,假设没有足够的空间来保存新文件而不先压缩旧文件。请记住,`tail` 可以在工作时生成临时文件,这也会占用空间。 (2认同)

pin*_*ime 38

在某些文件系统(如 ext4 或 xfs)上,您可以使用fallocate()系统调用。

  • 不错,这就是我要推荐的。如果您要保留的行不是从块边界开始,那么首先执行此操作仍然是压缩 + 解压缩的有用设置。尽管就地复制`dd`可能是最好的,并且不需要任何额外的空间,因此根本不会从中受益。 (2认同)

fro*_*utz 31

您可以使用losetup,作为dd此处描述方法的替代方法。同样,这种方法同样危险。

同样,相同的测试文件和大小(从 1000 行文件中删除第 1-300 行):

$ seq 1 1000 > 1000lines.txt
$ stat -c %s 1000lines.txt
3893 # total bytes
$ head -n 300 1000lines.txt | wc -c
1092 # first 300 lines bytes
$ echo $((3893-1092))
2801 # target filesize after removal
Run Code Online (Sandbox Code Playgroud)

创建循环设备:

# losetup --find --show 1000lines.txt
/dev/loop0
losetup: 1000lines.txt: \
Warning: file does not fit into a 512-byte sector; \
the end of the file will be ignored.
# head -n 3 /dev/loop0
1 
2 
3 
# tail -n 3 /dev/loop0
921
922
923
Run Code Online (Sandbox Code Playgroud)

哎呀。缺少数字。这是怎么回事?

循环设备要求它们的后备文件是扇区大小的倍数。带行的文本文件通常不适合该方案,因此为了不错过文件末尾(最后一部分扇区)的内容,只需先附加一些数据,然后再试一次:

# head -c 512 /dev/zero >> 1000lines.txt
# losetup --find --show 1000lines.txt
/dev/loop1
losetup: 1000lines.txt: \
Warning: file does not fit into a 512-byte sector; \
the end of the file will be ignored.
# tail -n 3 /dev/loop1
999
1000
\0
Run Code Online (Sandbox Code Playgroud)

警告仍然存在,但内容现在已完成,所以没关系。

创建另一个,这次使用 300 行偏移:

# losetup --find --show --offset=1092 1000lines.txt
/dev/loop2
losetup: 1000lines.txt: \
Warning: file does not fit into a 512-byte sector; \
the end of the file will be ignored.
# head -n 3 /dev/loop2
301
302
303
# tail -n 3 /dev/loop2
999
1000
\0
Run Code Online (Sandbox Code Playgroud)

这是关于循环设备的好处。您不必担心会意外截断文件。您还可以在执行任何操作之前轻松验证您的偏移量是否确实正确。

最后,只需将其从偏移设备复制到完整:

cp /dev/loop2 /dev/loop1
Run Code Online (Sandbox Code Playgroud)

溶解循环装置:

losetup -d /dev/loop2 /dev/loop1 /dev/loop0
Run Code Online (Sandbox Code Playgroud)

(或:losetup -D解散所有循环设备。)

截断文件以达到目标文件大小:

truncate -s 2801 1000lines.txt
Run Code Online (Sandbox Code Playgroud)

结果:

$ head -n 3 1000lines.txt 
301
302
303
$ tail -n 3 1000lines.txt 
998
999
1000
Run Code Online (Sandbox Code Playgroud)


Ole*_*kov 19

如果您确实需要该任务,请再次投票支持自定义程序。C 或任何足够强大的动态语言,如 Perl 或 Python 都可以。我不会在这里写出源代码,但会描述在移动数据时防止数据丢失的算法:

  1. 从结束计数换行符读取您的大文件。在收集了一些可以安全地放入可用空间的预定义行数后,将此块写入单独的文件并剪掉大文件的尾部。使用块的文件名来存储行号。
  2. 之后,您将以完全擦除的大文件和许多占用相同空间的小文件结束。
  3. 计算您的 3 亿行 - 您可以立即删除与不必要行对应的所有块,因为您知道哪些块包含哪些行。
  4. 如果您实际上不需要大文件,您可以使用通配符或cat根据需要将它们串在一起,使用您需要的任何工具直接对剩余的块进行操作。
  5. 如果您毕竟需要大文件并且释放空间足以在删除不必要的块后存储剩余块的总和 - 只需将它们与cp或组合在一起cat
  6. 如果您需要大文件并且没有足够的空间,请编写另一个小程序,该程序将执行步骤 1 的相反操作:将列表和每个块的单独长度保存到某个列表文件。逐个读取块并将它们附加到新创建的“大文件”中。每次完成将块附加到大文件时,您将删除一个单独的包含该块的小文件,从而允许您就地重新组装文件。如果您在任何时候中断了写入块的过程,您可以通过计算任何特定块的正确偏移量来重新开始写入大文件,因为您已经预先保存了每个块的大小。


Sté*_*las 8

ksh93

tail -n +300000001 < file 1<>; file
Run Code Online (Sandbox Code Playgroud)

1<>;操作符是标准1<>操作符(以读+写模式打开而不会截断)的特定于 ksh93 的变体,如果该命令成功,它会在命令返回到命令离开其标准输出的位置截断文件。

使用其他外壳,您始终可以手动执行就地截断perl,例如:

{
  tail -n +300000001 &&
    perl -e 'truncate STDOUT, tell STDOUT'
} < file 1<> file
Run Code Online (Sandbox Code Playgroud)

要获得进度条,请使用pv

{
  head -n 300000000 | pv -s 300000000 -lN 'Skipping 300M lines' > /dev/null &&
    cat | pv -N 'Rewriting the rest' &&
    perl -e 'truncate STDOUT, tell STDOUT'
} < file 1<> file
Run Code Online (Sandbox Code Playgroud)

(如果它的输入和输出指向同一个文件,使用head | pvand cat | pvaspv将拒绝工作。pv -Sls 300000000也不会工作,因为pv在现有的第 300000000 行之后没有将指针留在文件中,就像head这样(并且是 POSIX 要求的)对于可查找的文件)pv | cat而不是cat | pv允许pv知道它需要读取多少并给你一个预计到达时间,但它目前是假的,因为它没有考虑到它没有从文件的开头读取的情况这里的情况)。

请注意,这些是危险的,因为文件正在被覆盖。如果前 300M 行包含空洞(对于有效的文本文件不应该发生),则可能会耗尽磁盘空间,并且文件的其余部分占用的空间比您在 FS 上的空闲空间多。