bunnym4n has asked for the wisdom of the Perl Monks concerning the following question:

I rarely find myself working so close to the metal with perl, but I'd like to be able to read a binary file in and switch some of the bytes. My problem seems to lie in the assignment or the conditional I've implemented. There just seems to be so much abstraction in a higher level language like perl.
#!/usr/bin/perl
use strict;

my IN, "<infile.dat";
my OUT, ">outfile.dat";
my $buffer;
$|=1;

while(read(IN, $buffer, 1)){
     if($buffer == hex('0x00')){
          $buffer = hex('0x17');}
     print OUT $buffer;
}
This matches about 90% of the bytes, but I know it's catching the conditional way too often. Also, it actually just prints the hex number and not the binary data. I know I'm fubar'ing the handling somehow; just don't know how :P

Replies are listed 'Best First'.
Re: Binary conditionals
by moritz (Cardinal) on Sep 30, 2008 at 14:22 UTC
    So you want to substitute all zero bytes with 0x17?
    open my $in, '<:raw', 'infile.dat' or die $!; open my $out, '>:raw', 'outfile.dat' or die $!; # reading with a bigger block size is much more efficient # than byte by byte reading local $/ = \1024; while (<$in>) { my $copy = $in; $copy =~ s/\x00/\x17/g; print $out $copy; } close $in or die $!; close $out or die $!;

    Your code fails because it writes the number 23 (0x17), not the character 23. You can fix that with the chr function.

      Of course y/\0/\x17/ would be even quicker than the substitution . . .</nit>

      The cake is a lie.
      The cake is a lie.
      The cake is a lie.

      Just as an aside, you should also open the file in binmode. Generally only important on Windows, it is just good practice to specify it.
        Is that needed even though :raw is used? I think that the binmode documentation isn't entirely clear. It says:
        If LAYER is omitted or specified as :raw the filehandle is made suitable for passing binary data.

        That reads as if it made a call to binmode superfluous, but on the other hand it talks about a certain orthogonality between IO layers and binmode HANDLE.

      Ok, I can't do a simple s/// because I actually have to rotate them. It's something with the conditional or the assignment. I tried updating the code in this manner
      #!/usr/bin/perl
      
      open my $in,  '<:raw', 'out.dat' or die $!;
      open my $out, '>:raw', 'outfile.jpg' or die $!;
      local $/ = \1024;
      my $byte;
      while (read($in, $byte, 1)) {
      	if($byte == x00){
      		$byte = chr(x17);}
      	elsif($byte == x17){
      		$byte = chr(x00);}
      	elsif($byte == x4E){
      		$byte = chr(x42);}
      	elsif($byte == x42){
      		$byte = chr(x4E);}
      	elsif($byte == x24){
      		$byte = chr(x90);}
      	elsif($byte == x90){
      		$byte = chr(x24);}
      	print $out $byte;
      }
      close $in or die $!;
      close $out or die $!;
      
      all it prints is many nulls and sporadic digits 1..9. I've even tried this with an if($byte eq chr(x00)) but it doesn't work eighther.
        Always start your scripts with
        use strict; use warnings;

        It catches many of your errors. The correct comparison is either $byte eq "\x00" or $byte eq chr(0x00).

        You can still use a substitution if your mapping is constant:

        my %table = ( "\x00" => "\x17", "\x17" => "\x00", ... ); my $re = join '|', map quotemeta, keys %table; # open in and out files... # and then substitute: $str =~ s/($re)/$table{$1}/g

        Since only characters are involved you can use tr///:

        while (<$in>) { tr/\x00\x17\x4E\x42\x24\x90/\x17\x00\x42\x4E\x90\x24/; print $out $_; }
Re: Binary conditionals
by Narveson (Chaplain) on Sep 30, 2008 at 14:45 UTC

    Now that moritz has answered your question, I have a question of my own.

    I can't compile

    my IN, "<infile.dat"; my OUT, ">outfile.dat";

    Is this what your code looked like when you ran it, or did something get mangled on the way to being posted here?

      Yes, the post was incomplete. Subsequent posts using
      open my IN, "<infile.dat";
      or similar were correct.
        Not quite. This gives an error message:
        No such class IN at ./714572.pl line 10, near "open my IN" syntax error at ./714572.pl line 10, near "my IN," Execution of ./714572.pl aborted due to compilation errors.

        moritz's code has it right because he uses a lexical filehandle. He also uses the recommended 3-argument form of open and checks its status:

        open my $in, '<:raw', 'infile.dat' or die $!;
Re: Binary conditionals
by JavaFan (Canon) on Sep 30, 2008 at 14:29 UTC
    One character strings in Perl are still strings; not numbers as in C. So, $buffer == hex ('0x00') would match if, and only if, $buffer == 0, hence, if $buffer contains the character "0". And, as pointed out, when you write it, you write 0x17, aka 23, as digits.

    Furthermore, hex('0x00') is redundant. It's equal to 0x00. The same with hex('0x17') which is the same as 0x17.

    The right solution has already been mentioned; no need to repeat it.

      C. So, $buffer == hex ('0x00') would match if, and only if, $buffer == 0, hence, if $buffer contains the character "0".

      Sorry, I have to disagree:

      $ perl -wle '"a" == 0 && print "yes"' Argument "a" isn't numeric in numeric eq (==) at -e line 1. yes

      So you see that 'a' == 0, and when you have warnings enabled, you also get a warning.

        Right. And this is why the OP complains that too many characters match (but not every character). $buffer==0x0 is true for every char except '1' to '9'.

        Rule One: "Do not act incautiously when confronting a little bald wrinkly smiling man."