Convert Hexdecimal to Decimal in Solaris awk

Hi there,

i want to use this Linux-script on a Solaris System to check the fragmentation Level of ZFS-DataSets:

#!/bin/sh

zdb -ddddd ${1} | awk --non-decimal-data \
'
/Indirect blocks/ {
  file_number++;
  next_block = 0;
}

/L0/ {
  split($3, fields, ":");
  this_block = ("0x"fields[2])+0;
  this_block_size = ("0x"fields[3])+0;
  total_blocks++;
  if( next_block != 0 ) {
    if( next_block == this_block ) {
      not_fragmented++;
    }
    else {
      fragmented++;
    }
  }
  next_block =  this_block + this_block_size;
}

END {
  total_fragment_blocks = fragmented + not_fragmented;
  printf("There are %d files.\n", file_number );
  printf("There are %d blocks and %d fragment blocks.\n", total_blocks, total_fragment_blocks );
  printf("There are %d fragmented blocks (%2.2f%%).\n", fragmented, fragmented*100.0/total_fragment_blocks );
  printf("There are %d contiguous blocks (%2.2f%%).\n", not_fragmented, not_fragmented*100.0/total_fragment_blocks );

Everything seems to work fine except the conversion from hex to dec at the following lines:

this_block = ("0x"fields[2])+0;
 this_block_size = ("0x"fields[3])+0;

I realized, that this is can't be done so easely with Solaris AWK. I found an other NAWK Script on the Internet which can do this but i have no idea how implement this in the main script.

Here is the conversion script:

BEGIN    { for(i=0;i<10;i++)  decv=i;
                   decv["a"]=10;decv["b"]=11;decv["c"]=12;decv["d"]=13;
                   decv["e"]=14;decv["f"]=15;
                   decv["A"]=10;decv["B"]=11;decv["C"]=12;decv["D"]=13;
                   decv["E"]=14;decv["F"]=15
                  }
        function hex(x) {
                  value = 0;
                  i     = 1;
                  n=length(x);
                  while ( n > 0 )  {
                        value = 16*value + decv[substr(x,i,1)];
                        n--;
                        i++;
                  }
                  return value
        }
               {printf("%12.0f\n",hex($1))}

Would be great if someone can help me.

Bye
Michael

Welcome to the forum.

It always helps if people in here see WHAT and HOW is going wrong. So, the input to the two questionable lines as well as the resulting output / value and / or error messages would be valuable. On first sight, with my awk version (mawk 1.3.3 Nov 1996), the conversion is done correctly.

Hi, you're absolut right, sorry for forgetting.
There were no special error messages except this:

bash-3.2# /script/fragmentation.sh tempool/data
There are 788 files.
There are 86526 blocks and 0 fragment blocks.
awk: division by zero
 record number 105858
bash-3.2#

what should be ok when the Pool is not fragmented.
Because this script never found any fragmention even on filesystems which are fragmented for sure i checked the awk statement line by line. To check the conversion of the two vars

this_block / this_block_size

is just added a printf stated to below them:

/L0/ {
  split($3, fields, ":");
  this_block = ("0x"fields[2])+0;
  this_block_size = ("0x"fields[3])+0;
  total_blocks++;

printf ("This Blocksize: " this_block "   This Blocksize Size: " this_block_size "\n" )

The result of this printf Statement ist always zero

This Blocksize: 0   This Blocksize Size: 0

what is not possible because the input line Shows

L0 0:26546fa00:800 4000L/800P F=10 B=308653/308653 

so the result should be the decimal output of the HEX number 26546fa00 and the decimal output of the sum of 26546fa00 and 800

So i removed the "+0" in the Statement

this_block = ("0x"fields[2])+0;

and then there were numbers... but only those hexadecimal numbers without letters. Next i investigated the net and found several Posts with the same Problem on Solaris awk. The solution is to use this extra function described above but i don't have any idea how to implement this in this awk-oneliner.

The xpg4-awk and nawk show the same Problem with

 this_block = ("0x"fields[2])+0;
  this_block_size = ("0x"fields[3])+0;

plus it can't use "--non-decimal-data"

The split($3, fields, ":"); with your input line L0 0:26546fa00:800 4000L/800P F=10 B=308653/308653 should have 4000L/800P in fields[1] and leave fields[2] and [3] empty... which explains the printf result perfectly.

EDIT: If I use above and do it for $2, it prints perfectly reasonable numbers:

10289084928
2048

Unfortunately this is not the solution.

Here is a short sample of the problem:

echo "2658a5600:800" | awk '{split($0,fields,":"); firstHex2Dec = ("0x"fields[1])+0; secondHex2Dec = ("0x"fields[2])+0; \
printf ("firstHex2Dec: "firstHex2Dec); printf ("   secondHex2Dec: "secondHex2Dec "\n") }'
firstHex2Dec: 0   secondHex2Dec: 0

The output of firstHex2Dec und secondHex2Dec should be decimal 10293499392 and 2048 now but it is "0" because the "+0" seemes to overwrite the var.
Now i remove the "+0"

bash-3.2# echo "2658a5600:800" | awk '{split($0,fields,":"); firstHex2Dec = ("0x"fields[1]); secondHex2Dec = ("0x"fields[2]); \
printf ("firstHex2Dec: "firstHex2Dec); printf ("   secondHex2Dec: "secondHex2Dec "\n") }'
firstHex2Dec: 0x2658a5600   secondHex2Dec: 0x800

The content of firstHex2Dec and secondHex2Dec is not overwritten by the "+0" but it is still not the decimal value as expected.

Im pretty sure the statement ("0x"array[n])+0 doesn't work in Solaris.

My Version of AWK ist:

bash-3.2# pkginfo -l  SUNWesu
   PKGINST:  SUNWesu
      NAME:  Extended System Utilities
  CATEGORY:  system
      ARCH:  i386
   VERSION:  11.10.0,REV=2005.01.21.16.34
   BASEDIR:  /
    VENDOR:  Oracle Corporation
      DESC:  additional UNIX system utilities, including awk, bc, cal, compress, diff, dos2unix, last, rup, sort, spell, sum, uniq, and uuencode
    PSTAMP:  on10ptchfeatx20130110213921
  INSTDATE:  Jan 17 2018 15:56
   HOTLINE:  Please contact your local service provider
    STATUS:  completely installed
     FILES:      169 installed pathnames
                  12 shared pathnames
                  21 linked files
                  18 directories
                 107 executables
                3726 blocks used (approx)

Thank you in advance

OK. Does it provide the sprintf function? Try

echo "2658a5600:800" | awk '{split($0,fields,":"); firstHex2Dec = sprintf ("%.f", "0x"fields[1]); secondHex2Dec = sprintf ("%.f", "0x"fields[2]);\
printf ("firstHex2Dec: "firstHex2Dec); printf ("   secondHex2Dec: "secondHex2Dec "\n") }'
firstHex2Dec: 10293499392   secondHex2Dec: 2048

Hi,

here is the output:

bash-3.2# echo "2658a5600:800" | awk '{split($0,fields,":"); firstHex2Dec = sprintf ("%.f", "0x"fields[1]); secondHex2Dec = sprintf ("%.f", "0x"fields[2]);\
printf ("firstHex2Dec: "firstHex2Dec); printf ("   secondHex2Dec: "secondHex2Dec "\n") }'
firstHex2Dec: 0   secondHex2Dec: 0

Did you try the two alternative awk versions as well?

You could try using strtonum like this:

  this_block = strtonum("0x"fields[2]);
  this_block_size = strtonum("0x"fields[3]);

of if this still fails write you own hextoint() function:

#!/bin/sh

zdb -ddddd ${1} | awk \
'
BEGIN { for(i=split("1 2 3 4 5 6 7 8 9 a b c d e f", decv);i;i--) dv[decv]=i }
function hextoint(hexstr, num)
{
   for(i=1;i<=length(hexstr);i++)
      num = num * 16 + dv[tolower(substr(hexstr,i,1))]
   return num
}

/Indirect blocks/ {
  file_number++;
  next_block = 0;
}

/L0/ {
  split($3, fields, ":");
  this_block = hextoint(fields[2]);
  this_block_size = hextoint(fields[3]);
  total_blocks++;
  if( next_block != 0 ) {
    if( next_block == this_block ) {
      not_fragmented++;
    }
    else {
      fragmented++;
    }
  }
  next_block =  this_block + this_block_size;
}

END {
  total_fragment_blocks = fragmented + not_fragmented;
  printf("There are %d files.\n", file_number );
  printf("There are %d blocks and %d fragment blocks.\n", total_blocks, total_fragment_blocks );
  printf("There are %d fragmented blocks (%2.2f%%).\n", fragmented, fragmented*100.0/total_fragment_blocks );
  printf("There are %d contiguous blocks (%2.2f%%).\n", not_fragmented, not_fragmented*100.0/total_fragment_blocks );

LOL, I had prepared a function very similar to what Chubler_XL presented:

BEGIN                   {for (n = split("0123456789ABCDEF", X, ""); n; n--) DECVAL[X[n]] = n-1}

function X2D(HEXV, TMP) {for (n = LX = split (toupper(HEXV), X, ""); n; n--) TMP += DECVAL[X[n]] * 16 ^ (LX-n)
                         return TMP
                        }

Good Morning,

i found a solution:

/L0/ {
  split($3, fields, ":");
this_block_hex=sprint toupper(fields[2]);
this_block_size_hex=sprint toupper(fields[3]);
"echo \"ibase=16;"this_block_hex"\"|bc -l" | getline this_block; close("echo \"ibase=16;"this_block_hex"\"|bc -l");
"echo \"ibase=16;"this_block_size_hex"\"|bc -l" | getline this_block_size; close("echo \"ibase=16;"this_block_size_hex"\"|bc -l");

First I change the hexnumbers to uppercase, then I send it to the Solaris BC command.
It's not very performant, but it does what it's supposed to do..

Thx :slight_smile: