Convert a decimal number to hexadecimal and binary in a shell script - bash

I have a decimal number in each line of a file.txt:
1
2
3
I am trying (for too long now) to write a one-liner script to have an output where each row has a column with the decimal, hexadecimal and the binary. To ease the task we can say that the original number is expressed in a byte. So the maximum value is 255.
I first try to decode each number as a bynary with prepended 0 so to have an 8 bits pattern:
awk '{print "ibase=10;obase=2;" $1}' $1 | bc | xargs printf "%08d\n"
where the outer $1 in the awk statement is file.txt. The output is :
00000001
00000010
00000011
Same for hex with one prepended 0
awk '{printf("0x%02x\n", $1)}' $1
Same as before. The Output is :
0x01
0x02
0x03
Well, the decimal should be just a print:
1
2
3
What I'd like to have is one liner where I have:
1 00000001 0x01
2 00000001 0x02
so basically to put 1. 2. and 3. in each line of the output.
I tried to execute bc (and other command) within awk using system() without success. And a zillion other ways. What is the way you would do it?

The following one-liner should work:
printf "%s %08d 0x%02x\n" "$1" $(bc <<< "ibase=10;obase=2;$1") "$1"
Example output:
$ for i in {1..10}; do printf "%s %08d 0x%02x\n" "$i" $(bc <<< "ibase=10;obase=2;$i") "$i"; done
1 00000001 0x01
2 00000010 0x02
3 00000011 0x03
4 00000100 0x04
5 00000101 0x05
6 00000110 0x06
7 00000111 0x07
8 00001000 0x08
9 00001001 0x09
10 00001010 0x0a

So I searched for a short and elegant awk binary converter. Not satisfied considered this as a challenge, so here you are. A little bit optimzed for size, so I put a readable version below.
The printf at the end specifies how large the numbers should be. In this case 8 bits.
Is this bad code? Hmm, yeah... it's awk :-)
Does of course not work with very huge numbers.
67 characters long awk code:
awk '{r="";a=$1;while(a){r=((a%2)?"1":"0")r;a=int(a/2)}printf"%08d\n",r}'
Edit: 55 characters awk code
awk '{r="";a=$1;while(a){r=a%2r;a=int(a/2)}printf"%08d\n",r}'
Readable version:
awk '{r="" # initialize result to empty (not 0)
a=$1 # get the number
while(a!=0){ # as long as number still has a value
r=((a%2)?"1":"0") r # prepend the modulos2 to the result
a=int(a/2) # shift right (integer division by 2)
}
printf "%08d\n",r # print result with fixed width
}'
And the asked one liner with bin and hex
awk '{r="";a=$1;while(a){r=a%2r;a=int(a/2)}printf"%08d 0x%02x\n",r,$1}'

You don't need bc. Here's a solution using only awk:
Fetch the bits2str function available in the manual
Add this minimal script:
{
printf("%s %s %x\n", $1, bits2str($1), $1)
}
This produces:
$ awk -f awkscr.awk nums
1 00000001 1
2 00000010 2
3 00000011 3

Related

Check if a file's hex dump has modulo 4 length

I am trying to write a script that, taking a file as argument, checks if the hex dump of the file has a mod 4 length. If the length is not mod 4 it must add 00 to the end of the dump to make it mod 4
I tried with hexdump
D=$(hexdump filename)
if [ $((${#D}%4)) != 0 ]
then
D+=00
fi
and with od
D=$(od -t x filename)
if [ $((${#D}%4)) != 0 ]
then
D+=00
fi
but both methods not working
I think the problems are as follows:
I include the offset columns in variable D when I only have to consider the hexdump.
For example:
cat file.txt
// Hello World!
//This is a file .txt
od -t x file.txt
//0000000 6c6c6548 6f77206f 21646c72 6968540a
//0000020 73692073 66206120 20656c69 7478742e
//0000040 0000000a
//0000041
The columns 0000000 0000020 0000040 0000041 must not be inside D.
In practice, inside D there must be 6c6c65486f77206f21646c726968540a736920736620612020656c697478742e0000000a
Also, I have to modify that dump so that it has a mod 4 lenght and I don't know if it is enough to simply add 00 at the end.
Any idea how this can be done?
D=$(xxd -p $1|tr -d '\n')
while [ $(bc<<<$(echo -n $D|wc -c)%4) != 0 ];do D+=00;done
does not include columns
Why do you need to examine the hex dump? Just pad the raw file with zeros if necessary.
padump () {
local size=$(stat -f '%z' "$1")
local i
( cat "$1"
for ((i=size; i%2>0; i++)); do
printf '\0'
done ) |
hexdump
}
Usage: padump filename
The argument to stat is unfortunately not portable; on Linux, try stat -c '%s' "$1" (the code above was written on macOS).
The modulo of the above is 2 because four hex digits corresponds to two bytes of file.
Anyway, I'll note that in your attempt, od already adds more padding than you bargained for, and outputs the bytes in the wrong order. The final value at offset 0x0041 is the single byte 0x0a and the zeros before it actually belong after it, and are padding. The other hex values are similarly swapped; 0x6c is the character l, but occurs first in the dump - read as a regular hex dump, 6c6c6548 spells lleH. You can fix this with options;
#!/bin/sh
od -t 2x --endian=big "$1" |
sed 's/^[^ ]* //;s/ //g;$d'
Because od already adds the padding, this is probably the simplest solution. The above also discards the offsets (the first column; sed 's/^[^ ]* //') and removes any remaining spaces between the hex digits (s/ //g), and the final line with just an offset ($d). However, the --endian=big option is Linux only. Alternatively, use -t 1x and add padding as above.

How can I read Hexidecimal numbers with the 0x0 extension as either normal Hex numbers or integers

I'm trying to read values in i2c addresses from my raspberry pi. Through the use of i2ctools, I can take an address and store it in a variable.
reg_state=$(i2cget -y 1 0x20 0x09)
echo "$reg_state"
:~/ $ 0x0a
However, although the address reads in properly, reg_state will keep the hex extension of "0x0" and this hinders doing operations. Say 0x0a was in the register before, and I want to add "1" to that value, the operation won't complete. I think this has to do with the way I'm trying to achieve my goal. Right now my code looks like this:
7 addition(){
8 reg_state=$(i2cget -y 1 0x20 0x09)
9 i=$(echo "obase=10; $reg_state"| bc)
10 write=$(i+adj )
}
...
25
26#Main Shell Script
27
28 op=$1
29 adj=$2
30 if [ $1 -gt 0 ]
31 then
32 addition
33 fi
What I'm attempting to do is read in the value of the register at an address, convert it to a decimal number and then add it with any number I want. However, I noticed that I am unable to use echo "obase=10; $reg_state | bc" because of the presence of '0x'. When converting hex numbers without the extension everything works fine, and they can be added like normal.
That being said, is there anyway I can get rid of the '0x' part and just have what's left so I can do my arithmetic in peace?
You shouldn't need bc to do integer math, even on hex numbers. Just use shell arithmetic expansion. echo "$(( 0xA + 1))" will display 11, for example. And if you need the result in hex:
printf "0x%X" $(( 0xA + 1))
will print 0xB.

How to awk through multiple files?

I have hundreds of .dat file with data in side and I want to awk them with the command
awk 'NR % 513 == 99' data.0003.127.dat > data.0003.127.Ma.dat
I tried to write a script file like
for i in {1 ... 9}; do
i=i*3
datafile = sprintf("data.%04d.127.dat",i)
outfile = sprintf("data.%04d.127.Ma.dat",i)
awk 'NR % 513 == 99' datafile > outfile
done
I only need 0003 0006 0009 ... files, but the above script doesn't work fine. The error says
bash: ./Ma_awk.sh: line 3: syntax error near unexpected token `('
bash: ./Ma_awk.sh: line 3: `datafile = sprintf("data.%04d.127.dat",i)'
What shall I do next? I use ubuntu 14.04.
In bash (since v4) you can write a sequence expression with an increment:
$ echo {3..27..3}
3 6 9 12 15 18 21 24 27
You can also include leading zeros, which will be preserved:
$ echo {0003..0027..3}
0003 0006 0009 0012 0015 0018 0021 0024 0027
So you could use the following:
for i in {0003..0027..3}; do
awk 'NR % 513 == 99' "data.$i.127.dat" > "data.$i.127.Ma.dat"
done
There are multiple issues with your code, simply because they are bash syntax errors.
Don't use spaces around variable assignment
Brace expension looks like {1..9}
Capturing stdout into a variable is done through =$() notation
Reference variables with the dollar sign $
Especially for filenames use double quotes " to ensure that the argument is considered as one
Now I have not considered the actual validity of your awk program but fixing the bash syntax errors would look something like the following
#!/usr/bin/env bash
for i in {1..9}; do
datafile=$(printf "data.%04d.127.dat" $i)
outfile=$(printf "data.%04d.127.Ma.dat" $i)
awk 'NR % 513 == 99' "$datafile" > "$outfile"
done
This does not take care of the correct iteration bounds with an increment of three, but since you have not specified an upper bound I will leave that as an exercise to you

DEC to HEX conversion with filling empty with 0 in bash

I have to convert decimal number to hexadecimal, but with filling eventual voids with zeroes:
Example:
I've tried this:
printf "%X\n" 190
Output is:
BE
i need it to look like this:
00BE
In short, output should have 4 hex symbols, if less, it should be filled with zeroes at the beginning
How to do that in bash?
Use format specifiers:
$ printf "%04X\n" 190
00BE
$ printf "%04X\n" 1
0001
$ printf "%04X\n" 42
002A

bash: how to print an integer in hex to a specific length

I am trying to dump the decimal integer values from one file in a hex format.
I do have a file with integer values in decimal.
$ more test.dat_trim
2 9
0 -11
7 -17
14 -1
I am trying to print this integer in hex. I know also that the integer values are small enough to fit on 2 bytes. I want the output to be on 2 bytes. But then when i am trying:
declare -i i;for i in $(<test.dat_trim);do printf "%.2x\n" $i; done;
02
09
00
fffffffffffffff5
07
ffffffffffffffef
0e
ffffffffffffffff
Basically printf "%.2x\n" it is only working for positive number. How can i make it work for negative also?
Just to clarify what i am expecting: The result should be like this:
02
09
00
f5
07
ef
0e
ff
meaning that i want for the negative values to be sign extended only on 1 byte.
Printing signed hex values is uncommon, so there is no conversion specifier providing this.
You could use the following work around:
for i in $(<test.dat_trim); do
if [ $i -ge 0 ]; then
printf " 0x%02x\n" $i;
else
printf "%c0x%02x\n" '-' $[$i * -1];
fi
done;
Referrig the update to the question:
Just replace this line
printf "%c0x%02x\n" '-' $[$i * -1];
with this
printf " 0x%02x\n" $[256 + $i];
This however, only works for the numbers >= -256.
It can be done in awk, that handles negative numbers also:
awk '{printf "0x%x%s0x%x\n", $1, OFS, $2}' OFS='\t' file
0x2 0x9
0x0 0xfffffff5
0x7 0xffffffef
0xe 0xffffffff
Kinda silly but what the heck:
xargs -a test.dat_trim bash -c 'printf %.2s\\n $(printf %02x\\n $* | rev) | rev' _
Have you tried printf("%04x\n",i)?

Resources