DEC to HEX conversion with filling empty with 0 in bash - 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

Related

Conversion of decimal to binary format with predefined amount of bits in shell?

Yes, I have seen Bash shell Decimal to Binary base 2 conversion , but I cannot tell how to do the following easily:
Say, I want to convert the decimal number 47803625 to a 32-bit binary string, that is 0b00000010110110010110110011101001. Closest I could get to is this:
$ printf "0b%32s\n" $(echo "obase=2; 47803625" | bc)
0b 10110110010110110011101001
... however, this pads the string on the left with spaces, not zeroes.
(sidenote: if I try the above with the %d format specifier, it fails:
$ printf "0b%32d\n" $(echo "obase=2; 47803625" | bc)
bash: printf: warning: 10110110010110110011101001: Numerical result out of range
0b 9223372036854775807
)
What would be an easy way to do this kind of a conversion from the command line? I'd be fine with a Python or other solution as well - as long as it is a one-liner I can type in a terminal ...
Eh, found it - simply prepend a 0 to the %32s format specifiers, then zeroes are used to left pad, even for a string %s format specifier:
$ printf "0b%032s\n" $(echo "obase=2; 47803625" | bc)
0b00000010110110010110110011101001

How to loop through character in string and still detect null char in Bash

I have this function:
function convert_ascii_string_to_decimal {
ascii=$1
unset converted_result
while IFS="" read -r -n 1 char; do
decimal=$(printf '%d' "'$char")
echo $decimal
converted_result="$converted_result $decimal"
done < <(printf %s "$ascii")
converted_result=$(echo $converted_result | xargs) #strip leading and trailing
}
It is meant to take an ascii string variable, loop through every character, and concatenate the ascii decimal representation to a string. However, this while loop seems to ignore null chars, ie characters with ascii 0. I want to be able to read every single ascii there is, including null.
To get all characters of a string as decimal number, you can use hexdump to parse a string:
echo -e "hello \x00world" | hexdump -v -e '1/1 "%d "'
104 101 108 108 111 32 0 119 111 114 108 100 10
This also works for parsing a file:
echo '05 04 03 02 01 00 ff' | xxd -r -ps > file
hexdump --no-squeezing --format '1/1 "%d "' file
5 4 3 2 1 0 255
hexdump explanation:
options -v and --no-squeezing prints all bytes (without skipping duplicated bytes)
options -e and --format allows giving a specific format
format is 1/1 "%d " which means
Iteration count = 1 (process the byte only once)
Byte count = 1 (apply this format for each byte)
Format = "%d" (convert to decimal)
You can't store the null character in a bash variable, which is happening in your script with the $char variable.
I suggest using xxd instead of writing your own script:
echo -ne "some ascii text" | xxd -p
If we echo a null charcter:
$ echo -ne "\0" | xxd -p
00

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)?

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

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

How to use "cmp" to compare two binaries and find all the byte offsets where they differ?

I would love some help with a Bash script loop that will show all the differences between two binary files, using just
cmp file1 file2
It only shows the first change I would like to use cmp because it gives a offset an a line number of where each change is but if you think there's a better command I'm open to it :) thanks
I think cmp -l file1 file2 might do what you want. From the manpage:
-l --verbose
Output byte numbers and values of all differing bytes.
The output is a table of the offset, the byte value in file1 and the value in file2 for all differing bytes. It looks like this:
4531 66 63
4532 63 65
4533 64 67
4580 72 40
4581 40 55
[...]
So the first difference is at offset 4531, where file1's decimal octal byte value is 66 and file2's is 63.
Method that works for single byte addition/deletion
diff <(od -An -tx1 -w1 -v file1) \
<(od -An -tx1 -w1 -v file2)
Generate a test case with a single removal of byte 64:
for i in `seq 128`; do printf "%02x" "$i"; done | xxd -r -p > file1
for i in `seq 128`; do if [ "$i" -ne 64 ]; then printf "%02x" $i; fi; done | xxd -r -p > file2
Output:
64d63
< 40
If you also want to see the ASCII version of the character:
bdiff() (
f() (
od -An -tx1c -w1 -v "$1" | paste -d '' - -
)
diff <(f "$1") <(f "$2")
)
bdiff file1 file2
Output:
64d63
< 40 #
Tested on Ubuntu 16.04.
I prefer od over xxd because:
it is POSIX, xxd is not (comes with Vim)
has the -An to remove the address column without awk.
Command explanation:
-An removes the address column. This is important otherwise all lines would differ after a byte addition / removal.
-w1 puts one byte per line, so that diff can consume it. It is crucial to have one byte per line, or else every line after a deletion would become out of phase and differ. Unfortunately, this is not POSIX, but present in GNU.
-tx1 is the representation you want, change to any possible value, as long as you keep 1 byte per line.
-v prevents asterisk repetition abbreviation * which might interfere with the diff
paste -d '' - - joins every two lines. We need it because the hex and ASCII go into separate adjacent lines. Taken from: Concatenating every other line with the next
we use parenthesis () to define bdiff instead of {} to limit the scope of the inner function f, see also: How to define a function inside another function in Bash?
See also:
https://superuser.com/questions/125376/how-do-i-compare-binary-files-in-linux
https://unix.stackexchange.com/questions/59849/diff-binary-files-of-different-sizes
The more efficient workaround I've found is to translate binary files to some form of text using od.
Then any flavour of diff works fine.

Resources