Wednesday, December 31, 2008

Looping in Hex

In Linux, you have seq utility (btw, I realised OpenSolaris 200811 comes with seq) that can print a sequence of numbers. This is a very useful command especially if you need to do loops. With seq, you can avoid doing any increment of counters. You can do this with
for i in `seq 1 10`; do echo $i; done

However, seq can only handle decimal (base 10) input arguments. What if I want seq to do stuff in hex (base 16), eg. loop from 00 to FF in step of 5. At first I was thinking of creating two functions (hex2dec and dec2hex) so that I can do this:

  1. input arguments in hex convert to decimal (use hex2dec)
  2. increment the looping counter in decimal
  3. convert any ouput to hex (use dec2hex)

The above steps work fine but it will involve too many processes to be forked out from the parent shell. Although I was using bc (arbitrary precision calculator language) to do conversion between base10 and base16, I did not explore the looping within the bc.

Do you know that bc language provides "for" loop construct ? Now I can bring my loop out of the shell script into bc and I do not have to do any counter increment. Since bc cannot handle both ibase=16 and obase=16 together, I need to convert the input hex number to decimal and let bc to work out all the output in hex. I even include an optional -w flag to equalise the width by padding with leading zeroes.

Here is my seq2hex.sh

#! /bin/sh


hex2dec()
{
    hex=`echo $1 | tr '[a-z]' '[A-Z]'`
    echo "ibase=16;$hex" | bc
}


usage()
{
    echo "Usage: $0 [-w width] start end"
    echo "Usage: $0 [-w width] start step end"
}
    

width=""
while getopts w: c
do
    case $c in
    w)   width=$OPTARG ;;
    \?)  usage; exit 1  ;;
    esac
done
shift `expr $OPTIND - 1`


# input arguments in hex, convert to decimal
if [ $# -eq 2 ]; then
    start=`hex2dec $1`
    end=`hex2dec $2`
    step=1
elif [ $# -eq 3 ]; then
    start=`hex2dec $1`
    step=`hex2dec $2`
    end=`hex2dec $3`
else
    usage
    exit 2
fi


echo "obase=16; for(i=$start;i<=$end;i+=$step) { i }" | \
    bc | \
    awk '{printf("%0'$width's\n",$1)}'

seqhex.sh in action:

$ ./seqhex.sh
Usage: ./seqhex.sh [-w width] start end
Usage: ./seqhex.sh [-w width] start step end

$ ./seqhex.sh 6 F
6
7
8
9
A
B
C
D
E
F

$ ./seqhex.sh AA BB
AA
AB
AC
AD
AE
AF
B0
B1
B2
B3
B4
B5
B6
B7
B8
B9
BA
BB

$ ./seqhex.sh AA 5 BB
AA
AF
B4
B9

$ ./seqhex.sh -w 4 AA 5 BB
00AA
00AF
00B4
00B9

Labels: ,

0 Comments:

Post a Comment

<< Home