When programmers think about bitwise manipulation, they usually think about using C or C++ to solve their problem since both programming languages provide a rich set of features which make it easy to perform bitwise manipulation.
However it is possible to just as easily perform such operations using the ksh93 shell. This post will explain what bitwise manipulation and number conversion facilities are available in ksh93 and provide some hopefully useful utilities and examples.
Many programmers are unaware that ksh93 has builtin support for different numeral radices (AKA arithmetic bases) as shown in the following example.
$ print $(( 10 + 20 )) 30 $ print $(( 2#10 + 20 )) 22 $ print $(( 2#10 + 4#20 )) 10 $
where base 2 is denoted by 2#, base 4 by #4 and so on. The general case syntax is [base#]n where base is a decimal number between 2 and 36 representing the arithmetic base and n is a number in that base. You can mix and match bases within expressions. As you would expect, the default base, i.e. if [base#] is omitted, is decimal(10).
Here is an example of how to convert decimal 255 to base 2, base 8 and base 16 respectively using the typeset command.
$ typeset -i2 a=255 $ echo $a 2#11111111 $ typeset -i8 a=255 $ echo $a 8#377 $ typeset -i16 a=255 $ echo $a 16#ff $
Note that ksh93 displays the base prefix before each number unless it is a decimal number, i.e. base 10.
The bitwise manipulation operators in ksh93 are AND, OR, XOR, SHIFT LEFT and SHIFT RIGHT. There are no SHIFT ROTATE or COMPLEMENT operators. The following is the list of supported bitwise operators in decreasing order of precedence which, by the way, is the same as in the C programming language.
<< >> | Bitwise shift left, shift right | |
& | Bitwise AND | |
^ | Bitwise XOR | |
| | Bitwise OR |
The following shell script includes both logical and bitwise operators together with the expected output for each statement. Notice the difference in output between a logical and bitwise operator.
#!/bin/ksh93 integer -i2 a=5 integer -i2 b=6 # expected output 1 print $(( a < b )) # expected output 0 print $(( a > b )) # expected output 3 print $(( a ^ b )) # expected output 4 print $(( a & b )) # expected output 1 print $(( a && b )) # expected output 7 print $(( a | b )) # expected output 1 print $(( a || b ))
A useful feature of bitwise operators is that by using the shift left and shift right bitwise operators you can mimic multiplication or division by 2 or a power of 2.
$ x=4 $ print $((x << 1)) 8 $ print $(( x << 2 )) 16 $ print $(( x >> 1 )) 2 $ print $(( x >> 2 )) 1
Here is a fairly simple shell script which uses numeric base conversion and bitwise operators to calculate the network and broadcast addresses for a given IP address and subnet mask.\
#!/bin/ksh93 typeset -i2 mask=255 [[ $# != 2 ]] && { echo "Usage: $0 ipaddress subnetmask" exit 1 } SaveIFS=$IFS IFS=. typeset -a IParr=($1) typeset -a NMarr=($2) IFS=$SaveIFS typeset -i2 ip1=${IParr[0]} typeset -i2 ip2=${IParr[1]} typeset -i2 ip3=${IParr[2]} typeset -i2 ip4=${IParr[3]} typeset -i2 nm1=${NMarr[0]} typeset -i2 nm2=${NMarr[1]} typeset -i2 nm3=${NMarr[2]} typeset -i2 nm4=${NMarr[3]} echo echo " IP Address: $1" echo " Subnet Mask: $2" echo " Network Address: $((ip1 & nm1)).$((ip2 & nm2)).$((ip3 & nm3)).$((ip4 & nm4))" echo "Broadcast Address: $((ip1 | (mask ^ nm1))).$((ip2 | (mask ^ nm2))).$((ip3 | (mask ^ nm3))).$(( ip4 | (mask ^ nm4)))" echo exit 0
Some sample output for this script.
$ ./calculate-address 10.150.12.1 255.255.255.0 IP Address: 10.150.12.1 Subnet Mask: 255.255.255.0 Network Address: 10.150.12.0 Broadcast Address: 10.150.12.255 $ ./calculate-address 10.150.12.1 255.255.254.0 IP Address: 10.150.12.1 Subnet Mask: 255.255.254.0 Network Address: 10.150.12.0 Broadcast Address: 10.150.13.255
The next example calculates the network and broadcast addresses for a Classless Inter-Domain Routing (CIDR) compliant IP address. Note the use of the shift right operator in calculating the network mask.
#!/bin/ksh93 [[ $# != 1 ]] && { echo "Usage: $0 ipaddress/netmask" exit 1 } SaveIFS=$IFS IFS="./" typeset -a IParr=($1) IFS=$SaveIFS typeset -i2 ip1=${IParr[0]} typeset -i2 ip2=${IParr[1]} typeset -i2 ip3=${IParr[2]} typeset -i2 ip4=${IParr[3]} typeset -i2 cidr=${IParr[4]} typeset -i2 nm1=0 nm2=0 nm3=0 nm4=0 typeset -i quad=$(( cidr / 8 )) sigbits=$(( cidr % 8 )) if (( sigbits != 0 )); then slot=$(( 256 - ( 256 >> $sigbits ) )) fi for (( i=1; i < 5; i++ )) do nameref nm=nm${i} if (( quad != 0 )); then nm=255 (( --quad )) elif (( quad == 0 )); then nm=slot break fi done typeset -i2 mask=255 print print " IP Address: $((ip1)).$((ip2)).$((ip3)).$((ip4))" print " CIDR Netmask Mask: $((nm1)).$((nm2)).$((nm3)).$((nm4))" print " CIDR Network (Route): $((ip1 & nm1)).$((ip2 & nm2)).$((ip3 & nm3)).$((ip4 & nm4))" print " Broadcast Address: $((ip1 | (mask ^ nm1))).$((ip2 | (mask ^ nm2))).$((ip3 | (mask ^ nm3))).$((ip4 | (mask ^ n m4)))" print exit 0
Sample output:
$ ./calculate-address "192.168.21.12/18" IP Address: 192.168.21.12 CIDR Network Mask: 255.255.192.0 CIDR Network (Route): 192.168.0.0 Broadcast Address: 192.168.63.255
For my example, suppose you are given a hexadecimal string 3AF9:3B01 and you want to expand that string to produce a list of all the values between 3AF9 and 3B01. The wrinkle is that you want all letters in the hexadecimal numbers to be uppercased.
#!/bin/ksh93 x=3AF9:3B01 typeset -i16 a=0x${x%:*} typeset -i16 b=0x${x#*:} typeset -u c for (( ; a <= b; a++ )) do c=${a:3} echo "$c" done
Here is the output:
$ ./hexlist 3AF9 3AFA 3AFB 3AFC 3AFD 3AFE 3AFF 3B00 3B01
Here is another interesting example of using bit arithmetic to solve a problem. In this particular case, the user wanted to have a shell script which accepted a 32-bit hexadecimal number as input and assigns consecutive blocks of 4 bits of the equivalent binary number to each of 8 variables.
#!/bin/ksh93 integer -i2 mask=16#F0000000 integer -i2 v0 v1 v2 v3 v4 v5 v6 v7 read hn?"Enter a 32-bit hex decimal number: " integer -i2 in=16#$hn for ((i=0; i < 8; i++)) do nameref var=v${i} var=$(( ((in << (i*4)) & mask) >> 28 )) done printf "%032.0.2d\n" $in printf "%04.0.2d %04.0.2d %04.0.2d %04.0.2d %04.0.2d %04.0.2d %04.0.2d %04.0.2d\n" \ $v0 $v1 $v2 $v3 $v4 $v5 $v6 $v7
Note the use of a third specifier with %d. In ksh93, each of the integral format specifiers can have a third modifier after width and precision that specifies the base of the conversion from 2 to 64. In addition the use of the # modifier as the third specifier will cause base# to be prepended to the value.
Here is sample output for this script:
$ ./hexblock Enter a 32-bit hex decimal number: abcdabcd 10101011110011011010101111001101 1010 1011 1100 1101 1010 1011 1100 1101 $ ./hexblock Enter a 32-bit hex decimal number: AFC92317 10101111110010010010001100010111 1010 1111 1100 1001 0010 0011 0001 0111
Suppose instead that you wanted to store the individual bits in an array. Here is one way to do this is:
#!/bin/ksh93 read num?"Enter binary 32 bit number: " integer -i2 a=2#$num integer -i2 mask=2#00000000000000000000000000000001 typeset -ui barray=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) for (( i=0; i < 32; i++, mask=$(( mask << 1)) )) do nameref var=barray[$i] (( $mask & $a )) && var=1 done # how to test the array values if (( ${barray[0]} )) then echo "barray[0] is 1" else echo "barray[0] is 0" fi
This example calculates the binary and decimal equivalent of an IP address:
#!/bin/ksh93 [[ $# != 1 ]] && { echo "Usage: $0 ipaddress" exit 1 } SaveIFS=$IFS IFS="." typeset -a IParr=($1) IFS=$SaveIFS typeset -i2 ip1=${IParr[0]} typeset -i2 ip2=${IParr[1]} typeset -i2 ip3=${IParr[2]} typeset -i2 ip4=${IParr[3]} typeset -ui2 d=$(( (ip1 << 24) | (ip2 << 16) | (ip3 << 8) | ip4 )) print "Inputted IP$ address: $((ip1)).$((ip2)).$((ip3)).$((ip4))" print " Binary equivalent: $d" print " Decimal equivalent: $((d))" exit 0
Here is sample output:
$ ./example 172.16.120.100 Inputted IP$ address: 172.16.120.100 Binary equivalent: 2#10101100000100000111100001100100 Decimal equivalent: 2886760548
Finally, I would be amiss if I did not point out that both the bash and zsh shells also provide a full set of bitwise operators and support number base conversion.