使用perl修改二进制文件中的条目

时间:2018-06-07 10:27:32

标签: perl binary

您好,

我有一个需要验证的xml文件。为此,我使用以下代码

use strict;
use warnings;
use XML::Parser;

my $File="folder/file1.xml";

my $p1 = new XML::Parser();
my $p2;
my $Crash_Error_String='';

eval{$p2=$p1->parsefile($File)};
$Crash_Error_String=$@ if !defined $p2 ;
if(!defined $p2){
    print $Crash_Error_String . "\n";
}

现在,如果文件不包含有效的XML,我会在变量$ Crash_Error_String中得到一个字符串,如下所示:

在第1771行,第58行,第248467位/usr/lib64/perl5/XML/Parser.pm第187行,格式不正确(无效令牌)。

这告诉我文件中的字节248467

存在XML相关问题

我现在可以打印出问题所在的值:

my($fh, $File, $byte_position, $byte_value);

$byte_position = 248467;

open($fh, "+<", $File) || die "can't open $File: $!";

binmode($fh) || die "can't binmode $File";

sysseek($fh, $byte_position, 0)  # NB: 0-based
      || die "couldn't see to byte $byte_position in $File: $!";

sysread($fh, $byte_value, 1) == 1
      || die "couldn't read byte from $File: $!";

printf "read byte with ordinal value %#02x at position %d\n",
    ord($byte_value), $byte_position;

close $fh;

在这个具体的例子中,给出了

读取位于248467的序数值为0x1f的字节

现在我的问题:如何用值_x001f _

替换值0x1f

我尝试了以下内容(将代码放在上面代码中对“sysread”和“close”的调用之间)

sysseek($fh, $byte_position, 0)  # NB: 0-based
      || die "couldn't see to byte $byte_position in $File: $!";

my $NewV="_x001f_";

syswrite($fh,$NewV);

但它会将新值立即放在问题字符串的右侧。另外它会把字符排到右边。

所以,在错误之前我在文件中有以下片段(XML Parser抱怨的字符实际上没有在下面显示,但它基本上是i和e的vérifier之间的字符)

pourvérifierlaréaction

在我替换后,我在文件中有以下片段

pourvérifi_x001f_éaction

正如您所看到的,替换字符串已经进入字符串的以下部分。

我想要的替代品是:

pourvérifi_x001f_erlaréaction

任何帮助都非常感激。

2 个答案:

答案 0 :(得分:1)

如果文件对于内存来说太大,但磁盘空间不是问题,最简单的解决方案是:

  1. 创建新文件。
  2. 将第一个248467字节复制到新文件。
  3. 将替换序列打印到新文件。
  4. 从旧文件中读取一个字节。
  5. 将剩余的字节复制到新文件中。
  6. 它可以就地完成,但它要复杂得多(问题会导致数据丢失)。

    use Fcntl qw( SEEK_CUR SEEK_SET );
    
    use constant BLOCK_SIZE => 4*1024*1024;
    
    my $qfn = 'file';
    my $offset = 248467;
    
    open(my $fh_src, '<:raw',  $qfn) or die("Can't open \"$qfn\": $!\n");
    open(my $fh_dst, '+<:raw', $qfn) or die("Can't open \"$qfn\": $!\n");
    
    sysseek($fh_src, $offest, SEEK_SET) or die($!);
    sysseek($fh_dst, $offest, SEEK_SET) or die($!);
    
    my $buf;
    {
       my $rv = sysread($fh_src, $buf, 1);
       die($!) if !defined($rv);
       die("Premature EOF") if !$rv;
       # Since we're only reading one byte, we don't need to worry about a partial read.
       $buf = sprintf("_x%04x_", ord($buf));
    }
    
    while (1) {
       my $written = 0;
       while ($written < length($buf)) {
          my $rv = syswrite($fh_dst, $buf, length($buf)-$written, $written);
          die($!) if !defined($rv);
          $written += $rv;
       }
    
       my $rv = sysread($fh_src, $buf, BLOCK_SIZE);
       die($!) if !defined($rv);
       last if !$rv;
    }
    
    # Must use sysseek instead of tell with sysread/syswrite.    
    truncate($fh_dst, sysseek($fh_dst, 0, SEEK_CUR))
       or die($!);
    

    从技术上讲,truncate不是必需的,因为新文件总是比旧文件大。

答案 1 :(得分:0)

› perl -i.bak -0777 -lpe's/\x1f/_x001f_/g' 50738935.xml

› hex 50738935.xml.bak
0000  70 6f 75 72 20 76 c3 a9  72 69 66 69 1f 65 72 20  pour v.. rifi.er 
0010  6c 61 20 72 c3 a9 61 63  74 69 6f 6e 0a           la r..ac tion.

› hex 50738935.xml
0000  70 6f 75 72 20 76 c3 a9  72 69 66 69 5f 78 30 30  pour v.. rifi_x00
0010  31 66 5f 65 72 20 6c 61  20 72 c3 a9 61 63 74 69  1f_er la  r..acti
0020  6f 6e 0a                                          on.