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

To oversimplify the problem, I need to convert a string of hex values into the actual values.

For this example, I will keep everything as printable characters.
Imagine the string (in a file):
48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21
I want to convert it to the string:
Hello, world!
So, I have something like:
while (defined($_ = <SOMEFILE>)) { my @cbytes = split; }
Beyond that, I have spent several hours looking at perfaq4 and perlpacktut (trying several ideas along the way), but nothing came out the way I expected.

I am sure there is a very simple way of doing this. I just don't see it.

Replies are listed 'Best First'.
Re: Basic transformations
by kejohm (Hermit) on Nov 29, 2010 at 11:43 UTC

    Here is some code that should do what you looking for:

    print pack 'c*', map {hex $_} qw(48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 2 +1);

    produces:

    Hello, world!
      The map function is what I was missing. That works perfectly. Thank you.
Re: Basic transformations
by mjscott2702 (Pilgrim) on Nov 29, 2010 at 11:47 UTC
    This seems to work, combining hex and chr - I'm sure it could be shorter:

    use strict; use warnings; while(<DATA>) { chomp; my @hex_chars = split; map { print chr(hex($_)); } @hex_chars; print "\n"; } __DATA__ 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21

    Output:
    Hello, world!
Re: Basic transformations
by chrestomanci (Priest) on Nov 29, 2010 at 11:43 UTC

    Try this:

    use strict; use warnings; my $line = '48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21'; my @cbytes = split / /, $line; my $retString; foreach my $byte (@cbytes) { my $hex = '0x'.$byte; my $value = eval $hex; my $char = chr $value; $retString .= $char; } print $retString;

    The trick is that perl will evaluate a literal number as hex if you prefix it with '0x', you then need to use eval to force perl to re-evalute it as a number instead of a string. From there is is just a matter of using chr (see perlfunc) to turn that number into a 1 char string.

      The trick is that perl will evaluate a literal number as hex if you prefix it with '0x', you then need to use eval to force perl to re-evalute it as a number instead of a string.
      Any reason not use the build in hex function, and instead use an eval, which, if the data comes from an untrusted source, has potential to have some "unwanted"side effects?

        Only that I did not think to use hex. It would probably be a better approach than eval.

        I don't think the OP had reason to distrust the input data, but if he did then it would be wise to sanitise with a regular expression like:my($clean) = ($raw =~ m/(\d+)/); regardless of how it is processed further.

Re: Basic transformations
by jwkrahn (Abbot) on Nov 29, 2010 at 13:32 UTC
    $ echo "48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21" | perl -lpe's/([[:xdig +it:]]{2})\s*/ chr hex $1 /eg' Hello, world!
Re: Basic transformations
by anonymized user 468275 (Curate) on Nov 29, 2010 at 16:51 UTC
    Yet another one liner for this:
    echo '48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21' | perl -lane 'for $ch ( +split /\s/ ) { $st .= chr(hex($ch)); } print $st;'

    One world, one people

      You are using the  -a switch which splits the fields on whitespace and puts them in  @F so you could write that as:

      echo '48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21' | perl -lane '$st .= chr + hex for @F; print $st;'
Re: Basic transformations
by locked_user sundialsvc4 (Abbot) on Nov 29, 2010 at 15:29 UTC

    FYI, the pack/unpack functions can also sometimes be very useful.

Re: Basic transformations
by Anonymous Monk on Nov 30, 2010 at 01:21 UTC
    I am having another problem with this in practice. My code seems to work, but it is producing some error messages I don't understand.
    A snippet of the actual code in use:
    #!/usr/bin/perl -w use strict; open ( SOMEFILE, '<', "$ARGV[0]" ) or die "Can't open $ARGV[0] for reading.\n"; while (defined($_ = <SOMEFILE>)) { my @cbytes = split; my @hbytes = map(hex, @cbytes); my $hstring = pack('c*', @hbytes); } close SOMEFILE;
    One line of data (remember, I only used printable characters for the example):
    99 00 00 00 dd 02
    And the output:
    Character in 'c' format wrapped in pack at ./test.pl line 11, <SOMEFIL +E> line 1. Character in 'c' format wrapped in pack at ./test.pl line 11, <SOMEFIL +E> line 1.
    Can someone tell me what is going on here? Thanks.

      Use the pack template 'C*' instead of 'c*'. The latter only handles byte values up to 127, the former handles up to 255.

        By the way, you could speed up your processing of that data enormously by skipping the split multiple maps over hex and chr et al. by using:

        $s = '48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21';; $s =~ tr[ ][]d; print pack 'H*', $s;; Hello, world!
        Thanks. I don't know how I missed that.
Re: Basic transformations
by fisher (Priest) on Nov 29, 2010 at 11:45 UTC
    for your example it is enough to just do
    while (<SOMEFILE>) { @bytes = split //; print join ":", @bytes; }
      Please try your suggestion with an example. It's very wrong.