您可以从 Perl 中的 .tar.bz2 存档中逐行流式传输文件吗?

Posted

技术标签:

【中文标题】您可以从 Perl 中的 .tar.bz2 存档中逐行流式传输文件吗?【英文标题】:Can you stream file-per-file, line-per-line from a .tar.bz2 archive in Perl? 【发布时间】:2016-11-03 09:07:31 【问题描述】:

我们有很多压缩数据,它们实际上是目录及其包含 XML 文件的子目录的压缩磁带存档;例如

omega/    
- alpha/
  - a/
    - file1.xml
    - file2.xml
    - file3.xml
  - b/
    - file1.xml
    - file2.xml
    - file3.xml
  - c/
    - ...
- beta/
  - a/
    - file1.xml
    - file2.xml
    - file3.xml
  - b/
    - ...
  - c/
    - ...
- gamma/
  - a/
    - ...
  - b/
    - ...
  - c/
    - ...

结果将是诸如omega.tar.bz2 之类的文件,这些文件的大小可以达到数百 GB。

即使我们知道这是一个 存档 文件类型,如果我们需要时仍然能够使用它的内容,那就太好了。因此,我想知道是否可以在 Perl 中以流式方式读取这些文件,即无需先解压缩磁盘上的所有内容,也无需将 whole *.tar.bz2 文件加载到记忆。

我知道使用IO::Uncompress 您可以使用 Bunzip2,但据我所见和测试,这会将整个文件读入内存,这对于我们的大文件是不可能的。下面是 Bunzipping 的示例代码(不包括 TAR)。

use strict;
use warnings;
use IO::Uncompress::Bunzip2 qw(bunzip2 $Bunzip2Error) ;

my $filename = '/path/to/file/file1.xml.bz2';
open(my $fh, '<', $filename)
  or die "Could not open file '$filename' $!";

my $buffer ;
bunzip2 $filename => \$buffer
  or die "bunzip2 failed: $Bunzip2Error\n";

print STDOUT "$buffer\n";

考虑到 TAR,还有一个Archive::Extract 模块,它允许将.tar.bz2 文件(类型tbz)读入Extract Object,但这又会将整个文件读入内存,即对于我们庞大的文件,这是不可能的。

由于我自己对该主题的研究,我认为不太可能以流媒体方式(即每行一行)读取 BZIP2 的 TAR。不过,我没有压缩经验,所以也许有一种方法可以在给定多个数据块的情况下重建文件行。

Tl;dr:您能否从 BZIP2 压缩 TAR 存档中流式传输文件内容(每行或类似)?

【问题讨论】:

【参考方案1】:

Compress::Raw::Bzip2 允许您逐块解压缩 bzip2 输入块,即在流中。但由于 .tar.bz2 首先是一个 tar 文件,然后使用 bzip2 压缩,因此您需要先将所有数据解压缩到 tar 文件中的文件位置,然后才能访问所需的数据,即没有办法在不解压缩的情况下查找该文件,直到该文件。如果您对此感到满意,您也许可以使用Archive::Tar::Stream,即将 bzip2 解码器的输入输入流式 Tar 解析器。我自己从未使用过它,但它看起来就像是专门为这种用例开发的。

如果您可以选择更改输入文件的格式,我建议您使用将压缩文件存储在存档中的格式(如 ZIP),而不是压缩完整存档(即 .tar.bz2)。通过这种方式,您可以轻松找到特定的压缩文件并仅解压缩此文件,而不是该文件之前的所有内容。

【讨论】:

感谢您提供的信息。但是,如果您使用 ZIP,在访问任何压缩文件之前是否需要解压缩整个 ZIP?这是否类似于首先对所有文件进行 bzip 压缩并然后将它们压缩为 tar 格式? @BramVanroy:使用 ZIP,文件在存档中被压缩,也就是说,人们可以找到一个特定的文件,然后解压缩它,而不需要将存档中的所有内容解压缩到这个文件。【参考方案2】:

所有 IO::Compress 和 IO::Uncompress 模块都支持流式传输,包括 IO::Uncompress::Bunzip2。您展示的示例代码(见下文)为您想要从文件中读取所有压缩数据并一次性将其解压缩到缓冲区的常见用例使用了一种便捷方法(bunzip2)。

my $buffer ;
bunzip2 $filename => \$buffer
  or die "bunzip2 failed: $Bunzip2Error\n";

这是流式 Bunzip2 用例的用法

my $bz = IO::Uncompress::Bunzip2->new($filename);

# $bz is a regular Perl filehandle, so can read it a line at a time
while (<$bz>)

    ....


# or a bock at a time
read($bz, $buffer, 1024);

close $gz;

如果你能找到一个接受 perl 文件句柄并且本身是流式传输的 tar 模块,你可以给它一个 IO::Uncompress::Bubzip2 对象。

另一种选择是让“real”tar 二进制文件为您处理这个问题。更新版本的 gnu tar 会自动检测压缩,你可以让 tar 写入标准输出。因此,您可以像这样打开 tar 命令的文件句柄

open my $data, "tar -Of $file.tar.bz2 |";

while (<$data>)

    ....

【讨论】:

以上是关于您可以从 Perl 中的 .tar.bz2 存档中逐行流式传输文件吗?的主要内容,如果未能解决你的问题,请参考以下文章

在 perl 中创建和读取 tar.bz2 文件

如何在使用 tar 时保持文件所有权

使用 Perl 从巨大的存档中提取单个

Linux命令(十五) 打包或解压文件 tar

Linux压缩与解压缩

如何使用 CBZip2OutputStream 压缩多个文件