如何在Perl中读取二进制文件

5

我正在编写一个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;
    }
}

我已经按照以下方式更改了代码,但它仍然无法正确地通过检查“log”文件来grep“p04”。它确实grep整个文件,包括二进制文件,如“@ ^ @ ^ @ ^ @ ^ G ^ D ^ @ ^ @ ^ @ ^ ^ @ p04bbhi06 ^ @ ^ ^ @ ^ @ ^ @ ^ @ ^ @ hh ^ R ^ @ ^ @ ^ @ ^ ^ @ ^ @ ^ @ p04lohhj09 ^ @ ^ @ ^ @ ^ ^ @” 。 我期望的是它只grep带有p04的任何内容,例如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;
}

使用autodie - Brad Gilbert
没有所谓的“二进制格式”。请更加精确。这些文件是什么格式?它们具有什么特征,使您称其为“二进制格式”? - reinierpost
它是以.gds格式保存的。这个文件可以在Unix中使用strings命令读取。它可以被我的Perl脚本读取,但我无法使用grep获取我想要的数据(在我的代码中是p04*)。 - Grace
如已建议,使用File::Find或其他方法获取文件列表。至于其余部分,你到底想要什么?如果找到匹配项,是输出整个文件内容还是只输出匹配的部分?又想要匹配什么?p04(.*)匹配从"p04"到下一个换行符的任何内容。然后,你可以在$1中获取这个"任何内容"。先不要纠缠于繁琐的目录处理,先集中精力在单个文件上想要得到什么。文件有多大?你只读取了前170MB。而且你一直在覆盖"日志"文件,所以它只包含最后一个文件的最后一项内容。 - mivk
2
@reinierpost 在“二进制文件”下的原帖中可能是指与文本文件相反的东西 - 例如与perldoc的-X文档中所述的相同,参见-B的解释。(引用:“-B”文件是一个“二进制”文件(与-T相反)。) - clt60
3个回答

7

使用:

$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都是二进制格式的,我想做的是读取每个文件以查找可读格式中的某些信息,并将其转储到另一个文件中(在这里我认为它为后处理)。我想执行类似于Unix中“strings <filename> | grep <text syntax>”的操作。其中<filename>是我的代码中的$file。我的问题在于无法读取二进制文件,以便我可以进行其他操作。谢谢。 - Grace

6

我不确定是否理解您的意思。

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

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

在Windows下,您可能需要添加binmode F;在*nix下则不需要。
如果您需要查找数组中包含某个单词的行,可以使用grep函数:
my @matches = grep /something/, @array_to_grep;

你将在新数组@matches中得到所有匹配的行。
顺便说一句:我认为一次性读入大量二进制文件到内存中不是一个好主意。你可以逐个搜索它们...
如果你需要找到匹配发生的位置,你可以使用另一个标准函数index:
my $offset = index('myword', $file);

嗨Dinanoid,感谢你的回答,我尝试过了但是对我来说效果不好。我已经按照上面的方法(我的代码)进行编辑,但是没有起到效果。另外,我也按照你的建议尝试了下面的代码,但对我来说也没有起到效果。你能指出我哪里做错了吗?谢谢。 - Grace
1
$file 会被分配为什么?字符数组?字符串?其他的什么? - Peter Mortensen

0

我不确定能否完全回答楼主的问题,但以下是一些相关的笔记。(编辑:这个方法与@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 成为二进制文件。由于由echo添加的结尾\n(如从hexdump中可以看到),因此该文件的大小为13个字节,由du看到。

现在,让我们看看当我们使用perl<>钻石运算符(又称 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)。这是因为钻石型文件句柄<FH>操作符实际上是readline的快捷方式(请参见Perl Cookbook:第8章,文件内容)。

同样的链接表明,应该取消输入记录分隔符\$(默认设置为\n),以便将整个文件读入。您可能希望此更改仅为本地更改,这就是为什么使用大括号和local而不是使用undef(请参见 Perl习语解释- 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的实际内容。

希望这能帮助到某些人,
干杯!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接