【问题标题】:How to read binary file in Perl如何在 Perl 中读取二进制文件
【发布时间】:2016-05-01 21:24:39
【问题描述】:

我在编写 Perl 脚本来读取二进制文件时遇到问题。

我的代码如下,$file 是二进制格式的文件。我尝试通过网络搜索并应用到我的代码中,尝试将其打印出来,但似乎效果不佳。

目前它只打印'&&&&&&&&&&"和""ppppppppppp",但我真正想要的是它可以打印出每个$line,这样我以后可以做一些其他的后期处理。另外,我不太确定$data 是什么,因为我看到它是文章中示例代码的一部分,说明假设是一个标量。我需要有人可以指出我的代码中哪里出错了。以下是我所做的。

my $tmp = "$basedir/$key";
opendir (TEMP1, "$tmp");
my @dirs = readdir(TEMP1);
closedir(TEMP1);

foreach my $dirs (@dirs) {
    next if ($dirs eq "." || $dirs eq "..");
    print "---->$dirs\n";
    my $d = "$basedir/$key/$dirs";
    if (-d "$d") {
        opendir (TEMP2, $d) || die $!;
        my @files = readdir (TEMP2); # This should read binary files
        closedir (TEMP2);

        #my $buffer = "";
        #opendir (FILE, $d) || die $!;
        #binmode (FILE);
        #my @files =  readdir (FILE, $buffer, 169108570);
        #closedir (FILE);

        foreach my $file (@files) {
            next if ($file eq "." || $file eq "..");
            my $f = "$d/$file";
            print "==>$file\n";
            open FILE, $file || die $!;
            binmode FILE;
            foreach ($line = read (FILE, $data, 169108570)) {
                print "&&&&&&&&&&&$line\n";
                print "ppppppppppp$data\n";
            }
            close FILE;
        }
    }
}

我已更改我的代码,使其如下所示。现在我可以读取 $data。感谢 J-16 SDiZ 指出这一点。我正在尝试将我从二进制文件中获得的信息推送到一个名为“@array”的数组中,想从数组中 grep 数据以获取匹配“p04”但失败的字符串。谁能指出错误在哪里?

my $tmp = "$basedir/$key";
opendir (TEMP1, "$tmp");
my @dirs = readdir (TEMP1);
closedir (TEMP1);

foreach my $dirs (@dirs) {
    next if ($dirs eq "." || $dirs eq "..");
    print "---->$dirs\n";
    my $d = "$basedir/$key/$dirs";
    if (-d "$d") {
        opendir (TEMP2, $d) || die $!;
        my @files = readdir (TEMP2); #This should read binary files
        closedir (TEMP2);

        foreach my $file (@files) {
            next if ($file eq "." || $file eq "..");
            my $f = "$d/$file";
            print "==>$file\n";
            open FILE, $file || die $!;
            binmode FILE;
            foreach ($line = read (FILE, $data, 169108570)) {
                print "&&&&&&&&&&&$line\n";
                print "ppppppppppp$data\n";
                push @array, $data;
            }
            close FILE;
        }
    }
}

foreach $item (@array) {
    #print "==>$item<==\n"; # It prints out content of binary file without the ==> and <== if I uncomment this.. weird!
    if ($item =~ /p04(.*)/) {
        print "=>$item<===============\n"; # It prints "=><===============" according to the number of binary file I have.  This is wrong that I aspect it to print the content of each binary file instead :(
        next if ($item !~ /^w+/);
        open (LOG, ">log") or die $!;
        #print LOG $item;
        close LOG;
    }
}

再次,我将代码更改如下,但它仍然无法正常工作,因为它无法通过检查“日志”文件正确地 grep“p04”。它确实grep了整个文件,包括像这样的二进制文件“@^@^@^@^G^D^@^@^@^^@p04bbhi06^@^^@^@^@^@^@^@^@^ @hh^R^@^@^@^^@^@^@p04lohhj09^@^@^@^^@@" .我正在考虑的是它只用 p04 grep 任何东西,例如 grep p04bbhi06 和 p04lohhj09。这是我的代码的运行方式:-

foreach my $file (@files) {
    next if ($file eq "." || $file eq "..");
    my $f = "$d/$file";
    print "==>$file\n";
    open FILE, $f || die $!;
    binmode FILE;
    my @lines = <FILE>;
    close FILE;
    foreach $cell (@lines) {
        if ($cell =~ /b12/) {
            push @array, $cell;
        }
    }
}

#my @matches = grep /p04/, @lines;
#foreach $item (@matches) {
foreach $item (@array) {
    #print "-->$item<--";
    open (LOG, ">log") or die $!;
    print LOG $item;
    close LOG;
}

【问题讨论】:

  • 没有“二进制格式”之类的东西。请更准确。文件是什么格式的?它们有什么特点让你称它们为“二进制格式”?
  • 它是 .gds 格式。该文件可以在 Unix 中使用 strings 命令读取。它在我的 Perl 脚本中是可以读取的,但我无法 grep 我想要的数据(p04* 在我的代码中)。
  • 如前所述,使用 File::Find 或其他方式获取文件列表。剩下的,你真正想要什么?如果找到匹配项,输出整个文件内容?还是只是匹配的部分?你想匹配什么? p04(.*) 匹配从“p04”到下一个换行符的任何内容。然后你在$1 中拥有那个“任何东西”。去掉所有笨拙的目录内容,首先专注于你想要从单个文件中得到什么。文件有多大?您只阅读前 170MB。而且你一直覆盖“日志”文件,所以它只包含最后一个文件中的最后一项。
  • @reinierpost “二进制文件”下的 OP 可能与文本文件相反 - 例如与perldoc's -X documentation 中的内容相同,请参阅-B 解释。 (引用:-B 文件是“二进制”文件(与 -T 相反)。)

标签: perl


【解决方案1】:

用途:

$line = read (FILE, $data, 169108570);

数据在$data$line 是读取的字节数。

       my $f = "$d/$file" ;
       print "==>$file\n" ;
       open FILE, $file || die $! ;

我猜完整路径在$f,但你打开的是$file。 (在我的测试中——即使$f 也不是完整的路径,但我猜你可能还有一些其他的胶水代码......)

如果您只想遍历目录中的所有文件,请尝试File::DirWalkFile::Find

【讨论】:

  • 您好 J-16 SDiZ,感谢您的回复。每个 $file 都是二进制格式,我想要做的是读取文件的 eaxh 以 grep 可读格式的一些信息并转储到另一个文件中(我认为这是后处理)。我想像在 Unix 中那样执行“strings | grep ”。其中 是我代码中的 $file 。我的问题是无法读取二进制文件,因此我可以继续处理其他内容。谢谢。
【解决方案2】:

我不确定我是否理解正确。

如果你需要读取二进制文件,你可以像读取文本文件一样:

open F, "/bin/bash";
my $file = do { local $/; <F> };
close F;

在 Windows 下,您可能需要在 *nix 下添加 binmode F;,没有它也可以工作。

如果需要查找数组中的哪些行包含某个单词,可以使用grep函数:

my @matches = grep /something/, @array_to_grep;

您将在新数组@matches 中获得所有匹配的行。

顺便说一句:我认为一次将大量二进制文件读入内存不是一个好主意。您可以逐个搜索它们...

如果您需要找到匹配发生的哪里,您可以使用另一个标准函数index

my $offset = index('myword', $file);

【讨论】:

  • 您好 Dinanoid,感谢您的回答,我试过了,但对我来说效果不佳。我试图像上面那样编辑我的代码(我自己的代码,但它不起作用)。另外,按照您的建议尝试了以下代码,它也对我不起作用。你能指出我哪里做错了吗?谢谢。
  • $file 将分配给什么?字符数组?一个字符串?还有什么?
【解决方案3】:

我不确定我能否准确回答 OP 问题,但这里有一些可能相关的注释。 (编辑:这与@Dimanoid 的回答方法相同,但更详细)

假设您有一个文件,它是 ASCII 数据和二进制文件的混合体。这是bash 终端中的示例:

$ echo -e "aa aa\x00\x0abb bb" | tee tester.txt
aa aa
bb bb
$ du -b tester.txt 
13  tester.txt
$ hexdump -C tester.txt 
00000000  61 61 20 61 61 00 0a 62  62 20 62 62 0a           |aa aa..bb bb.|
0000000d

注意字节00(指定为\x00)是一个不可打印的字符,(在C中,它也意味着“字符串的结尾”)——因此,它的存在使得tester.txt二进制文件。正如du 所见,该文件的大小为13 个字节,因为echo 添加了尾随\n(从hexdump 可以看出)。

现在,让我们看看当我们尝试使用perl&lt;&gt; 菱形运算符(另见What's the use of <> in perl?)读取它时会发生什么:

$ perl -e '
open IN, "<./tester.txt";
binmode(IN);
$data = <IN>; # does this slurp entire file in one go?
close(IN);
print "length is: " . length($data) . "\n";
print "data is: --$data--\n";
'

length is: 7
data is: --aa aa
--

很明显,整个文件并没有被吞掉——它在行尾\n(而不是二进制文件\x00)处中断。那是因为菱形文件句柄&lt;FH&gt; 操作符实际上是readline 的快捷方式(参见Perl Cookbook: Chapter 8, File Contents

相同的链接告诉人们应该取消定义输入记录分隔符\$(默认设置为\n),以便吞食整个文件。您可能希望仅在本地进行此更改,这就是使用大括号和local 而不是undef 的原因(请参阅Perl Idioms Explained - my $string = do { local $/; };);所以我们有:

$ perl -e '
open IN, "<./tester.txt";
print "_$/_\n"; # check if $/ is \n
binmode(IN);
{
local $/; # undef $/; is global
$data = <IN>; # this should slurp one go now
};
print "_$/_\n"; # check again if $/ is \n
close(IN);
print "length is: " . length($data) . "\n";
print "data is: --$data--\n";
'

_
_
_
_
length is: 13
data is: --aa aa
bb bb
--

...现在我们可以看到文件被完整地吞食了。

由于二进制数据意味着不可打印的字符,您可能希望通过打印sprintfpack/unpack 来检查$data 的实际内容。

希望这对某人有所帮助,
干杯!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-10
    • 2013-01-08
    • 1970-01-01
    • 2011-01-26
    • 1970-01-01
    • 2013-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多