我有一个包含许多 zip 文件的 tar,每个文件都包含 xml 文件。我想解压、解压缩,然后对 xml 文件中的纯文本进行一些操作。整个程序都是以 bash 管道的形式编写的。

我需要 unzip 命令返回:

  1. zip 中每个文件的未压缩文件内容
  2. 文件分隔符(用于了解一个文件的文件内容何时停止以及下一个文件的文件内容何时开始)
  3. zip 文件中每个文件的名称

管道中的下一个命令需要这 3 个条件才能正确完成其工作。文件内容和分隔符必须位于管道 (stdout) 中,文件名可以位于同一管道中,也可以位于变量中或其他地方。

问题:我应该使用哪种 unzip 实现,以及如何执行此操作?

bsdtar 可以工作(但据我所知无法返回文件名):

tar -xf ~/tar/0.tar --to-command 'bsdtar -xO --include='*.html' --include='*.xhtml' | iconv -f UTF-8 -t UTF-8//IGNORE | htmlq -tw'

PS 我在 bash 管道中而不是在磁盘上执行解压、解压缩和所有操作,因为写入磁盘会使程序速度降低 30 倍。每个 zip 都包含大量小文件,稍后尝试查找它们会遇到磁盘 I/O 瓶颈。

PPS 我知道解压需要先读到最后,所以理论上管道应该没什么用。实际上,这不会减慢程序的速度(我假设整个 zip 文件都存储在 RAM 中,这没问题)。

8

  • 在管道中处理这个问题过于复杂,为什么不尝试使用“RAM 磁盘”呢?例如


    – 


  • @Hannu 谢谢,如果我找不到其他解决方案,我会尝试这个。我的程序的其余部分已经写成了管道,只是这一个命令让我卡住了。


    – 

  • (1) 在 ramdisk 或 HDD/SSD 上,临时目录和常规文件被发明出来以供使用。管道对于适合它们的问题来说非常优雅。将管道用于显然不是为管道设计的程序或格式没有任何好处。 (2) 这是一项重复性任务吗?还是一次性(或罕见)的工作?如果是后者,那么可能设计一个强大的管道可能比处理常规文件花费的时间更长。如果是前者,那么也许您应该考虑切换/转换为更适合您需求的存储格式。


    – 

  • @KamilMaciorowski 1. 不错,我会尝试使用 ramfs 来完成这一步。我想知道是否有任何标准做法来决定何时使用 ramfs 而不是管道。 2. 这是一项重复性任务,我需要以这种方式处理许多 TB 的数据。每个 tarball 都是 5 GB。关于切换存储格式,下载所有 tarball、解压和解压缩它们所需的时间足够长,我最好第一次就完全处理它们。


    – 

  • 关于 (2):“切换”:我希望从现在开始,无论谁创建文件,都可以首先以更好的格式创建它们。 “转换”:如果您需要多次处理同一个档案(每次可能略有不同),但您选择将输入存储为单个档案,而不是单独的文件。请注意,我的问题是因为您的帖子没有告诉我们:(a)任务是重复的;(b)如果所有输入文件都已创建或其中一些即将创建,并且您可以请求更改;(c)如果您需要多次处理每个文件。


    – 


最佳答案
1

这应该是一个评论,但可能太长了。它可能会提供一些指导。

gzip相当智能。它无法假设有多少内存可用(因此它不会将整个文件读入内存),并且直到该阶段完成时它才知道已实现的压缩量。(它当然会使用正常缓存。)

在压缩时,我相信它会累积存档中的文件列表(以及每个文件的统计信息),并将其附加到所有数据之后。然后,它会巧妙地编写一个结尾,其中包含该文件列表开头的搜索地址。文件统计信息还可以包含每个子文件开头在 zip 文件中的偏移量,这也会优化部分提取。

-l因此,使用或选项的unzip-v可以搜索到末尾,按 sizeof(epilogue) 向后搜索,并报告文件内容而不读取任何其他内容。您可以通过在 下运行一个小的测试文件来验证我的猜想strace

unzip -v您可能会发现,使用 获取文件名列表、使用 解析并分别提取每个文件(可能还会添加文件详细信息)的速度足够快awk。这也是 下的一项有趣练习strace,它将展示查找/读取策略。

您可能会发现它需要将文件写入磁盘,但您可以使用 ram-disk 来优化此过程,因为单个文件很小。此外,zip 系列可能会在处理完某些文件后删除它们——在尝试生产之前,请先在某个开发目录中进行测试。

如果这是一份生产工作,我可能会断开提取和上传的连接。将文件提取到pend子目录中,将完成的文件移动到live子目录中,然后上传器(可能同时)将上传的文件移动到done目录中。

编辑:注意zcat并且gunzip不能完全处理 zip 档案。zip并且unzip是必需的。

我制作了一个测试 .zip 文件并在其中运行了几个命令strace。结果有些令人困惑,因为文件结构将某些内容(例如文件列表)与块边界对齐,并且unzip还读取了完整对齐的 8192 字节块。

但我可以确认:

(a)unzip -v Test1.zip进行两次查找和一次读取,以生成详细的文件列表。

(b)unzip Test1.zip csvParse.c进行五次查找和六次读取来搜索、找到并提取一个文件。它不会读取任何不需要的内容。

我的结论是,单独处理每个提取的文件不会对性能产生重大影响,并且会简化所需的处理。

档案内容列表似乎按列非常规则地格式化,并且应该很容易解析以获取文件名。

5

  • 注意:tar 文件包含其中文件的某种目录(?)。“.tar.gz”文件是相同的 tar 文件,压缩为单个文件 – 没有“目录”,必须解压才能读取目录。另一方面,pkzip 文件和其他“归档器”文件有目录。[未研究!旧信息;可能有误]


    – 


  • 感谢您花时间回复,我会尝试这样做。(解析列表,然后单独提取每个文件)


    – 

  • @Hannu 基本正确。tar在每个存档文件之前内联写入一个控制块(通常为 512 字节),并且每个存档都会四舍五入为 512 字节的倍数。这类似于磁带,您可以跳过块但不能查找。然后整个文件被压缩在一起,破坏了可能有助于选择任何特定文件的任何对齐方式。如果它没有被压缩,并且在可查找的介质上,tar 可以跳到下一个控制块(我认为它可以在某些磁带驱动器上执行此操作,但不是全部)。


    – 

  • @ghosts_in_the_code 最终统计:我制作了一个 215 MB 的 .zip 文件(四个 128M 的文件,中间有一个 340 字节的文件,压缩率为 58%)。unzip -p发送到 /dev/null 需要 10.3 秒。仅提取小文件就需要real 0m0.008s


    – 

  • @Paul_Pedant 谢谢,我会研究一下!我正在考虑完全跳过文件名。但您的解决方案肯定对某些人有用。感谢您花时间打出这篇文章。


    –