【问题标题】:Read, process continuous binary file - Efficiently读取、处理连续二进制文件 - 高效
【发布时间】:2016-02-23 13:47:04
【问题描述】:

我有非常大的二进制文件,没有行和字段分隔符。目标是高效地将这些文件处理成制表符分隔的文件。

文件结构如下:

每条记录都是固定长度,20 字节。每个字段的长度不同,三个字段的长度分别为 3、7 和 10 个字节。每个字段也代表不同的数据类型,字段 1 和 2 是 int,3 是 char。

处理这些文件最有效的方法是什么?我想让它尽可能简单,使用 Bash 工具,dd/od sed/awk,如果可能的话避免使用 perl/python,除非性能差异很大。

下面是一个工作尝试,它很慢。我是上述工具的新手,非常感谢详细的解释。

binfile="binfile.BIN"

for (( i = 0 ; i <= 20000000 ; i += 20 ))
do
    field1=$( od "${binfile}" -An --skip-bytes"$((${i}))" --read-bytes=3 --format=dI )
    field2=$( od "${binfile}" -An --skip-bytes"$((${i}+3))" --read-bytes=7 --format=dI )
    field3=$( od "${binfile}" -An --skip-bytes"$((${i}+10))" --read-bytes=10 --format=c )

    echo - ${field1}'\t'${field2}'\t'${field3} >> output.tab
done

【问题讨论】:

  • 你没有/不被允许写一个 5 行的 c 程序?那将非常简单且非常非常快。祝你好运。
  • 我不知道 C,它需要一堆样板才能打开文件,但除了 shell 之外几乎任何东西都很棒
  • 当你说二进制时,你的意思是前 3 个字节和接下来的 7 个字节在它们的机器级表示中代表实际的整数数据,而不是它们的 ascii 版本? 3 和 7 都是整数数据的不寻常长度(期望 4 和 8 代替),因此必须涉及一些填充。此外,数据的字节顺序也是一个考虑因素。您是否有机会发布一些示例数据和相应输出的简短十六进制转储?
  • 我从不使用二进制文件,所以我不会尝试回答您的问题,但是对于任何文本操作,您应该使用 awk 并且 GNU awk 具有二进制模式,所以我建议您看看在那,例如通过谷歌搜索或获取 Arnold Robbins 的《Effective Awk Programming, 4th Edition》一书。对于任何文本操作,awk 脚本将比 shell 循环快几个数量级。

标签: python bash perl awk sed


【解决方案1】:

从 STDIN 读取,输出到 STDOUT,并执行错误检查:

#!/usr/bin/perl

use strict;
use warnings;

use constant BLOCK_SIZE => 20;

binmode STDIN;    

while (1) {
    my $rv = read(STDIN, my $buf, BLOCK_SIZE);
    die("Error: $!\n") if !defined($rv);
    last if !$rv;
    die("Error: Insufficient data\n") if $rv != BLOCK_SIZE;
    print(join("\t", unpack('a3 a7 a10', $buf)), "\n");
}

但我很确定你会发现这比一次阅读更多内容要慢,所以我会使用以下内容:

#!/usr/bin/perl

use strict;
use warnings;

use constant BLOCK_SIZE => 20;

binmode STDIN;    

my $buf;    
while (1) {
    my $rv = sysread(STDIN, $buf, BLOCK_SIZE*64*1024, length($buf));
    die("Error: $!\n") if !defined($rv);
    last if !$rv;

    while (length($buf) >= BLOCK_SIZE) {
       print(join("\t", unpack('a3 a7 a10', substr($buf, 0, BLOCK_SIZE, '')), "\n");
    }
}

die("Error: Insufficient data\n") if length($buf);

【讨论】:

  • 在使用sysread的时候肯定需要处理EINTR。
  • @chansen,仅当您定义了信号处理程序时。但是如果你这样做,你需要处理所有系统调用的EINTRopensysreadprintopen等),而不仅仅是sysread
【解决方案2】:
fold -b -w 20 | cut --output-delimiter $'\t' -b 1-3,4-10,11-20

如果您的“cut”不支持 --output-delimiter,请尝试“gcut”(GNU cut)或考虑安装 GNU coreutils。

(请告诉我们您尝试的不同解决方案的速度有多快 :-)

【讨论】:

    【解决方案3】:
    open my $fh, '<:raw', shift;
    
    local $" = "\t";
    
    while ( read $fh, my $rec, 20 ) {
        my @f = unpack 'a3 a7 a10', $rec;
        print "@f\n";
    }
    

    【讨论】:

    • 这会将前两个字段解压缩成它们的整数等价物,还是只是按原样转储数据?结果字段不应该用制表符分隔吗?
    • @mhawke:看来你既不理解问题也不理解我的回答。你为什么审问我们?
    • 使用say join "\t", unpack ... 的魔力要小得多。你到底为什么要使用$"
    • @ikegami:因为它保留了执行字符串化的语句和print 干净。我更喜欢print join("\t", @f), "\n",但你不必同意。 $" 并不神奇,但是 Perl 的魔法有什么问题呢?
    • 我不认为将 4 行放在中间是“干净的”。我认为恰恰相反。 // 魔法有很多含义。我使用的是“在远处引起意外行动”的非常负面的含义,这是糟糕编程的定义。
    猜你喜欢
    • 2020-05-06
    • 1970-01-01
    • 2019-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多