Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Pack uneven length hex

by melez (Sexton)
on Nov 23, 2018 at 08:33 UTC ( [id://1226210]=perlquestion: print w/replies, xml ) Need Help??

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

Hello monks, I have a problem with perl's pack function and null byte padding. Here's the problem:
print unpack "H*", pack("H*", "a12");
This returns a120, because packed data is padded to full byte length at the end, not at the front. This is quite problematic. Possible solution is to do this instead
my $string = "a12"; print unpack "H*", pack("H*", "0" x (length $string % 2) . $string);
Of course this is a lot uglier, requires you to actually have a variable declared and is not as explicit as the first version, however it works. My question is: is there a possibility to do the same thing with just pack format string to avoid patching it with length() of the string? Is there a cpan library that takes care of this? I wrote my own helper function for this but I've run into some circullar using of my packages which I want to avoid. Help appreciated!

Replies are listed 'Best First'.
Re: Pack uneven length hex
by choroba (Cardinal) on Nov 23, 2018 at 12:21 UTC
    Regex solution:
    $string =~ s/^(?=(?:..)+.$)/0/;

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Pack uneven length hex
by Eily (Monsignor) on Nov 23, 2018 at 09:59 UTC

    If you have a maximum length for your input sprintf may help: sprintf "%016s", EXPR. It will align all your inputs to your max size, although you could still remove the leading \0s. (And I'm assuming you do want a byte string as the output, otherwise hex might help)

    requires you to actually have a variable declared
    You could use either map or for to alias the value of your EXPR to $_:
    print unpack "H*", pack("H*", map { "0" x (length($_) % 2) . $_ } "a12 +"); print unpack "H*", pack("H*", "0" x (length($_) % 2) . $_ ) for "a12";
    That's still not pretty (and you could do even worse, with an anonymous sub).

    I don't get the issue with your helper function and circular packages though. Can't you just put the fonction alone in a file, in the worst case? Or, if it really comes to it, just redefine it wherever you need, the function is fairly short anyway:

    sub zpad { my ($str,) = @_; length($str)%2 ? "0$str" : $str } print unpack 'H*', pack 'H*', zpad "a30"

      A faster way to calculate

      length($_) % 2

      is to do this:

      length($_) & 1

      Gives the same result, but it's often a bit faster, because the former forces the processor to perform a division, whereas the second one is a simple bitwise AND. We grab the last bit, and that's all.

        thanks! .oO( all the divisions I have caused... )

      Thank you for the reply. I don't want to set maximum length on a hex string. I work with very big numbers so hex() is out of question, and in my case byte string is actually what I want to get.
      The map/for is quite interesting, however I was hoping there's a clever way to work with parameters in pack() itself, which would change it's behavior to pad left side (I couldn't find anything like that on my own).
      Inserting a tiny function like this seems to be the best solution, however as this is meant to be in a CPAN package I wanted to keep it as clean as possible, and at least two of my files will require this functionality. Why do it even pad numbers on the right side?
        I was hoping there's a clever way to work with parameters in pack() itself, which would change it's behavior to pad left side

        Not sure that it can be classified as "clever", but if you "scalar reverse()" twice, it seems to produce the correct result (for your given example, at least) :
        print scalar reverse unpack "H*", pack("H*", scalar reverse "a12");
        Cheers,
        Rob

        however I was hoping there's a clever way to work with parameters in pack() itself
        As far as I can tell, there isn't one. Though I agree that would have been interesting.

        this is meant to be in a CPAN package I wanted to keep it as clean as possible
        I suppose that's why you don't want to put the function in its own file? Because it kind of belongs in a separate module?

        Why do it even pad numbers on the right side?
        Because adding things on the right of your data should not change how the left part is interpreted, I guess

        I just thought of another way to do what you want: pack 'H*', 'a12' =~ s/^(.(..)*)$/0$1/rxs. Still not as clear as a function though.

Re: Pack uneven length hex
by james28909 (Deacon) on Nov 23, 2018 at 14:50 UTC
    $string =~ s/(.)/sprintf("%02x",ord($1))/egs;
Re: Pack uneven length hex
by melez (Sexton) on Nov 23, 2018 at 21:51 UTC

    Thank you all for your responses! I've decided it's best to create new package with helpers which has no other dependecies to avoid circullar dependencies. Too bad pack() can't do it on it's own!

Re: Pack uneven length hex
by ikegami (Patriarch) on Nov 26, 2018 at 10:01 UTC

    If you want always want to output 16-bits,

    pack "n", hex "a12"

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1226210]
Approved by hippo
Front-paged by kcott
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (3)
As of 2024-04-18 23:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found