How can I convert hex strings into numbers in Perl? - perl

I recently wrote a script which parsed a text representation of a single binary byte month field.
(Don't ask :-{ )
After fiddling with sprintf for a while I gave up and did this;
our %months = qw / x01 1
x02 2
x03 3
x04 4
x05 5
x06 6
x07 7
x08 8
x09 9
x0a 10
x0b 11
x0c 12 /;
...
my $month = $months{$text};
Which I get away with, because I'm only using 12 numbers, but is there a better way of doing this?

If you have
$hex_string = "0x10";
you can use:
$hex_val = hex($hex_string);
And you'll get: $hex_val == 16
hex doesn't require the "0x" at the beginning of the string. If it's missing it will still translate a hex string to a number.
You can also use oct to translate binary, octal or hex strings to numbers based on the prefix:
0b - binary
0 - octal
0x - hex

See hex and/or oct.
#!/usr/bin/perl
use strict;
use warnings;
my #months = map hex, qw/x01 x02 x03 x04 x05 x06 x07 x08 x09 x0a x0b x0c/;
print "$_\n" for #months;

If I understand correctly you have 1 byte per month - not string "0x10", but rather byte with 10 in it.
In this way, you should use unpack:
my $in = "\x0a";
print length($in), "\n";
my ($out) = unpack("c", $in);
print length($out), "\n", $out, "\n"
output:
1
2
10
If the input are 3 characters, like "x05", then changing is also quite simple:
my $in = "x0a";
my $out = hex($in);

Here's another way that may be more practical for directly converting the hexadecimals contained in a string.
This make use of the /e (e for eval) regex modifier on s///.
Starting from this string:
$hello_world = "\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64";
Hexadecimals to characters :
print $hello_world =~ s/\\x([0-9a-fA-F]{2})/chr hex $1/gre;
Hexadecimals to decimal numbers :
print $hello_world =~ s/\\x([0-9a-fA-F]{2})/hex $1/gre;
Drop the /r modifier to substitute the string in-place.
One day I used a python script that did stuff with a binary file and I was stuck with a bytes literal (b'\x09\xff...') containing only hexadecimal digits.
I managed to get back my bytes with a one-liner that was a variant of the above.

Related

Converting to hexadecimal value in Perl

The following code:
$tmp1 = $_;
print "tmp1 is $tmp1";
$tmp1_hex = hex($tmp1);
print "tmp1_hex is $tmp1_hex\n";
$Lat_tmp1 = ($tmp1_hex >> 8) &0x00ff;
prints:
tmp1 is 0018
tmp1_hex is 24
The text file I'm reading the data from contains the string 0018, but when I convert it to the hex value I shouldn't be receiving 24.
If you want to convert to hex rather than from hex, use sprintf:
my $tmp1_hex = sprintf '%x', $tmp1;
The hex function merely interprets the string as a number in hexadecimal form. Beyond that, it's just a number and its original representation doesn't matter.
When you print a number, Perl uses its internal format (%g) to show it. That's a normal, decimal float.
If you want to output the number as something other than Perl's internal format, use printf and the appropriate specifier:
printf '%x', $number;
You probably want something like:
my $tmp1 = '0018';
print "tmp1 is $tmp1\n";
my $tmp1_hex = hex( $tmp1 );
printf "tmp1_hex is %x\n", $tmp1_hex;
Note that the bitwise operators don't need you to convert the number to any particular base. The number is the same number no matter how you display it.
The function hex() converts from hex to decimal. Try something like this instead
$tmp=$_;
$tmp1_hex=sprintf("%X",$tmp);

Perl - convert hexadecimal to binary and use it as string

I am new to Perl and I have difficulties using the different types.
I am trying to get an hexadecimal register, transform it to binary, use it a string and get substrings from the binary string.
I have done a few searches and what I tried is :
my $hex = 0xFA1F;
print "$hex\n";
result was "64031" . First surprise : can't I print the hex value in Perl and not just the decimal value ?
$hex = hex($hex);
print "$hex\n";
Result was 409649. Second surprise : I would expect the result to be also 64031 since "hex" converts hexadecimal to decimal.
my $bin = printf("%b", $hex);
It prints the binary value. Is there a way to transform the hex to bin without printing it ?
Thanks,
SLP
Decimal, binary, and hexadecimal are all text representations of a number (i.e. ways of writing a number). Computers can't deal with these as numbers.
my $num = 0xFA1F; stores the specified number (sixty-four thousand and thirty-one) into $num. It's stored in a format the hardware understands, but that's not very important. What's important is that it's stored as a number, not text.
When print is asked to print a number, it prints it out in decimal (or scientific notation if large/small enough). It has no idea how the number of created (from a hex constant? from addition? etc), so it can't determine how to output the number based on that.
To print an number as hex, you can use
my $hex = 'FA1F'; # $hex contains the hex representation of the number.
print $hex; # Prints the hex representation of the number.
or
my $num = 0xFA1F; # $num contains the number.
printf "%X", $num; # Prints the hex representation of the number.
You are assigning a integer value using hexadecimal format. print by default prints numbers in decimal format, so you are getting 64031.
You can verify this using the printf() by giving different formats.
$ perl -e ' my $num = 0xFA1F; printf("%d %X %b\n", ($num) x 3 ) '
64031 FA1F 1111101000011111
$ perl -e ' my $num = 64031; printf("%d %X %b\n", ($num) x 3 ) '
64031 FA1F 1111101000011111
$ perl -e ' my $num = 0b1111101000011111; printf("%d %X %b\n", ($num) x 3 ) '
64031 FA1F 1111101000011111
$
To get the binary format of 0xFA1F in string, you can use sprintf()
$ perl -e ' my $hex = 0xFA1F; my $bin=sprintf("%b",$hex) ; print "$bin\n" '
1111101000011111
$
lets take each bit of confusion in order
my $hex = 0xFA1F;
This stores a hex constant in $hex, but Perl doesn't have a hex data type so although you can write hex constants, and binary and octal constants for that matter, Perl converts them all to decimal. Note that there is a big difference between
my $hex = 0xFA1F;
and
my $hex = '0xFA1F';
The first stores a number into $hex, which when you print it out you get a decimal number, the second stores a string which when printed out will give 0xFAF1 but can be passed to the hex() function to be converted to decimal.
$hex = hex($hex);
The hex function converts a string as if it was a hex number and returns the decimal value and, as up to this point, $hex has only ever been used as a number Perl will first stringify $hex then pass the string to the hex() function to convert that value from hex to decimal.
So to the solution. You are almost there with printf(),there is a function called sprintf() which takes the same parameters as printf() but instead of printing the formatted value returns it as a string. So what you need is.
my $hex = 0xFA1F;
my $bin = sprintf("%b", $hex);
print $bin;
Technical note:
Yes I know that Perl stores all its numbers internally as binary, but lets not go there for this answer, OK?
If you're ok with using a distribution, I wrote Bit::Manip to make my prototyping a bit easier when dealing with registers (There's also a Pure Perl version available if you have problems compiling the XS code).
Not only can it fetch out bits from a number, it can toggle, clear, set etc:
use warnings;
use strict;
use Bit::Manip qw(:all);
my $register = 0xFA1F;
# fetch the bits from register using msb, lsb
my $msbyte = bit_get($register, 15, 8);
print "value: $msbyte\n";
print "bin: " . bit_bin($msbyte) . "\n";
# or simply:
# printf "bin: %b\n", $msbyte;
Output:
value: 250
bin: 11111010
Here's a blog post I wrote that shows how to use some of the software's functionality with an example datasheet register.

perl-how to treat a string as a binary number?

Read a file that contains an address and a data, like below:
#0, 12345678
#1, 5a5a5a5a
...
My aim is to read the address and the data. Consider the data I read is in hex format, and then I need to unpack them to binary number.
So 12345678 would become 00010010001101000101011001111000
Then, I need to further unpack the transferred binary number to another level.
So it becomes, 00000000000000010000000000010000000000000001000100000001000000000000000100000001000000010001000000000001000100010001000000000000
They way I did is like below
while(<STDIN>) {
if (/\#(\S+)\s+(\S+)/) {
$addr = $1;
$data = $2;
$mem{$addr} = ${data};
}
}
foreach $key (sort {$a <=> $b} (keys %mem)) {
my $str = unpack ('B*', pack ('H*',$mem{$key}));
my $str2 = unpack ('B*', pack ('H*', $str));
printf ("#%x ", $key);
printf ("%s",$str2);
printf ("\n");
}
It works, however, my next step is to do some numeric operation on the transferred bits.
Such as bitwise or and shifting. I tried << and | operator, both are for numbers, not strings. So I don't know how to solve this.
Please leave your comments if you have better ideas. Thanks.
You can employ Bit::Vector module from metaCPAN
use strict;
use warnings;
use Bit::Vector;
my $str = "1111000011011001010101000111001100010000001111001010101000111010001011";
printf "orig str: %72s\n", $str;
#only 72 bits for better view
my $vec = Bit::Vector->new_Bin(72,$str);
printf "vec : %72s\n", $vec->to_Bin();
$vec->Move_Left(2);
printf "left 2 : %72s\n", $vec->to_Bin();
$vec->Move_Right(4);
printf "right 4 : %72s\n", $vec->to_Bin();
prints:
orig str: 1111000011011001010101000111001100010000001111001010101000111010001011
vec : 001111000011011001010101000111001100010000001111001010101000111010001011
left 2 : 111100001101100101010100011100110001000000111100101010100011101000101100
right 4 : 000011110000110110010101010001110011000100000011110010101010001110100010
If you need do some math with arbitrary precision, you can also use Math::BigInt or use bigint (http://perldoc.perl.org/bigint.html)
Hex and binary are text representation of numbers. Shifting and bit manipulations are numerical operations. You want a number, not text.
my $hex = '5a5a5a5a';
$num = hex($hex); # Convert to number.
$num >>= 1; # Manipulate the number.
$hex = sprintf('%08X', $num); # Convert back to hex.
In a comment, you mention you want to deal with 256 bit numbers. The native numbers don't support that, but you can use Math::BigInt.
My final solution of this is forget about treat them as numbers, just treat them as string . I use substring and string concentration instead of shift. Then for the or operation , I just add each bit of the string, if it's 0 the result is 0, else is 1.
It may not be the best way to solve this problem. But that's the way I finally used.

Perl Pack Unpack and Reverse Byte Order

I'm trying to write a script to find hex strings in a text file and convert them to their reverse byte order. The trouble I'm having is that some of the hex strings are 16 bit and some are 64 bits. I've used Perl's pack to pack and unpack the 16 bit hex numbers and that works fine, but the 64 bit does not.
print unpack("H*", (pack('I!', 0x20202032))). "\n"; #This works, gives 32202020
#This does not
print unpack("H*", (pack('I!', 0x4f423230313430343239303030636334))). "\n";
I've tried the second with the q and Q (where I get ffffffffffffffff). Am I approaching this all wrong?
As bit of background, I've got a multi-gigabyte pipe-delimited text file that has hex strings in reverse byte order as explained above. Also, the columns of the file are not standard; sometimes the hex strings appear in one column, and sometimes in another. I need to convert the hex strings to their reverse byte order.
Always use warnings;. If you do, you'll get the following message:
Integer overflow in hexadecimal number at scratch.pl line 8.
Hexadecimal number > 0xffffffff non-portable at scratch.pl line 8.
These can be resolved by use bigint; and by changing your second number declaration to hex('0x4f423230313430343239303030636334').
However, that number is still too large for pack 'I' to be able to handle.
Perhaps this can be done using simple string manipulation:
use strict;
use warnings;
my #nums = qw(
0x20202032
0x4f423230313430343239303030636334
);
for (#nums) {
my $rev = join '', reverse m/([[:xdigit:]]{2})/g;
print "$_ -> 0x$rev\n"
}
__END__
Outputs:
0x20202032 -> 0x32202020
0x4f423230313430343239303030636334 -> 0x3463633030303932343034313032424f
Or to handle digits of non-even length:
my $rev = $_;
$rev =~ s{0x\K([[:xdigit:]]*)}{
my $hex = $1;
$hex = "0$hex" if length($hex) % 2;
join '', reverse $hex =~ m/(..)/g;
}e;
print "$_ -> $rev\n"
To be pedantic, the hex numbers in your example are 32-bit and 128-bit long, not 16 and 64. If the longest one was only 64-bit long, you could successfully use the Q pack template as you supposed (provided hat your perl has been compiled to support 64-bit integers).
The pack/unpack solution can be used anyway (if with the addition of a reverse - you also have to remove the leading 0x from the hex strings or trim the last two characters from the results):
print unpack "H*", reverse pack "H*", $hex_string;
Example with your values:
perl -le 'print unpack "H*", reverse pack "H*", "4f423230313430343239303030636334"'
3463633030303932343034313032424f

How do I convert a decimal number to a hex string in Perl?

How do I convert a decimal number to a hex string in Perl?
For example, I would like to convert 2001 into "0x07" and "0xD1".
This works for that case:
($x,$y) = map { "0x$_" }
sprintf("%04X\n", 2001) =~ /(..)(..)/;
But I wonder what you're really trying to do. If you're trying to get UTF-16, this isn't the way you want to do that.
If you're trying to figure out the layout of packed binary data, then you should be using unpack. The "C4" format would work for a 4-byte integer.
$int = 2001;
$bint = pack("N", $int);
#octets = unpack("C4", $bint);
printf "%02X " x 4 . "\n", #octets;
# prints: 00 00 07 D1
For some purposes, you can use printf's vector print feature:
printf "%v02X\n", pack("N", 2001);
# prints: 00.00.07.D1
printf "%v02X\n", 255.255.255.240;
# prints: FF.FF.FF.F0
I'm not sure what exact format you want it in, but one way to convert to hex is:
sprintf("%x", 2001)