在 Perl 中读取二进制文件

van*_*com 4 binary perl unpack

我有一个二进制文件,其中有一堆依次存储的数据块。数据块的格式如下:

Length [byte]   Content       Description
2               0xFFFF        Data block header
4               Epoch time    seconds since 00:00:00 UTC, January 1, 1970
2               value of N    Length of data following this value
N               Data          Data itself
Run Code Online (Sandbox Code Playgroud)

我尝试使用unpack,但这是错误的,因为数据的长度不固定。

我需要编写一个子例程,它将读取和解析数据块(每次调用子例程时一个数据块),直到文件末尾。

该文件使用big-endian

这是我到目前为止所尝试的:

use strict;
use warnings;

my $filename;

if (! $ARGV[0])
{
    die "Input filename is required";
}

sub setFile
{
    $filename = $_[0];
}

my $inFile = $ARGV[0];

setFile($inFile);

open INFILE, $filename or die "\nUnable to open input file";

binmode INFILE;

my $nbytes;

while (<INFILE>) {
    my( $header, $timestamp_hex, $datalength_hex ) = unpack 'H4 H8 H4', $_;
    my $timestamp = hex($timestamp_hex);
    my $datalength = hex($datalength_hex);
    print "$timestamp $datalength\n";

    for (my $i = 0; $i < $datalength; $i++)
    {
        my $data = unpack 'H', $_;
        print "$data";
    }
    print "\n";
}

close INFILE
    or die "Error while closing $filename: $!\n";
Run Code Online (Sandbox Code Playgroud)

ike*_*ami 5

<INFILE>没有意义。它会一直读取直到找到换行符。

如果内存中有整个文件,则可以使用以下命令:

my @fields = unpack('( n N n/a* )*', $file);
while (@fields) {
   my ($sig, $ts, $data) = splice(@fields, 0, 3);
   die "Incorrect signature" if $sig != 0xFFFF;
   process_rec($ts, $data);
}
Run Code Online (Sandbox Code Playgroud)

如果我们将标头与数据分开提取,我们可以节省内存并添加一些错误检查。

use constant HEADER_FORMAT => 'nNn';
use constant HEADER_LENGTH => length(pack(HEADER_FORMAT, 0, 0, 0));

while (length($file)) {
   last if length($file) < HEADER_LENGTH;
   my ($sig, $ts, $data_len) = unpack(HEADER_FORMAT, substr($file, 0, HEADER_LENGTH, ''));
   die "Incorrect signature" if $sig != 0xFFFF;
   last  if length($file) < $data_len;
   process_rec($ts, substr($file, 0, $data_len, ''));
}

die "Premature EOF" if length($file);
Run Code Online (Sandbox Code Playgroud)

从文件句柄读取是第二个片段的扩展。如果内存中没有整个文件,可以使用以下命令:

use constant HEADER_FORMAT => 'nNn';
use constant HEADER_LENGTH => length(pack(HEADER_FORMAT, 0, 0, 0));
use constant BLOCK_SIZE    => 128*1024;

sub make_fill_to = sub {
   my $fh      =  shift;
   my $buf_ref = \shift;
   my $eof = 0;

   return sub {
      my $bytes_needed = $_[1];
      while (!$eof && length($$buf_ref) < $bytes_needed) {
         my $rv = sysread($fh, $$buf_ref, BLOCK_SIZE, length($$buf_ref));
         die $! if !defined($rv);
         $eof = 1 if !$rv;
      }

      return !$eof;
   }
};

my $buf = '';
my $fill_to = make_fill_to($fh, $buf);
while (1) {
   $fill_to->(HEADER_LENGTH)
      or last LOOP;
   my ($sig, $ts, $data_len) = unpack(HEADER_FORMAT, substr($buf, 0, HEADER_LENGTH, ''));
   die "Incorrect signature" if $sig != 0xFFFF;
   $fill_to->($data_len)
      or last LOOP;
   process_rec($ts, substr($buf, 0, $data_len, ''));
}

die "Premature EOF" if length($buf);
Run Code Online (Sandbox Code Playgroud)

当用来select管理多个句柄时,读取必须先行,所以我习惯这样写。如果将上面的内容重构为将读取放在第一位,则以下内容将如下所示:

use constant HEADER_FORMAT => 'nNn';
use constant HEADER_LENGTH => length(pack(HEADER_FORMAT, 0, 0, 0));
use constant BLOCK_SIZE    => 128*1024;

my $buf = '';
my ($got_header, $sig, $ts, $data_len);
while (1) {
   my $rv = sysread($fh, $buf, BLOCK_SIZE, length($buf));
   die $! if !defined($rv);
   last if !$rv;

   while (1) {
      if (!$got_header) {
         last if length($buf) < HEADER_LENGTH;          
         ($sig, $ts, $data_len) = unpack(HEADER_FORMAT, substr($buf, 0, HEADER_LENGTH, ''));
         die "Incorrect signature" if $sig != 0xFFFF;
         $got_header = 1;
      }

      last if length($buf) < $data_len;
      process_rec($ts, substr($buf, 0, $data_len, ''));
      $got_header = 0;
   }
}

die "Premature EOF" if $got_buffer || length($buf);
Run Code Online (Sandbox Code Playgroud)