Converting from decimal to hexadecimal for ELROND blockchain - bash

I am trying to do some smart contract testing for Elrond blockchain with multiple wallets and different token amounts.
The Elrond blockchain requires some hexadecimal encodings for the Smart Contract interaction. The problem is that my hex encoding matches some conversions like in this webpage http://207.244.241.38/elrond-converters/ but some others are not:
For example here is my for loop:
for line in $file; do
MAIAR_AMOUNT=$(echo $line | awk -F "," '{print $2}')
MAIAR_AMOUNT_HEX=$(printf "%04x" $MAIAR_AMOUNT)
echo -e "$MAIAR_AMOUNT > $MAIAR_AMOUNT_HEX"
done
And here is the output
620000000000 > 905ae13800
1009000000000 > eaed162a00
2925000000000 > 2a907960200
31000000000 > 737be7600
111000000000 > 19d81d9600
The first one is the decimal value I want to convert, the second is the hexadecimal.
Now if I compare the results with http://207.244.241.38/elrond-converters/
A value like 2925000000000 is 02a907960200 not 2a907960200 like I have in my output. (notice the 0 at the beginning)
But a value like 620000000000 is matching with the website 905ae13800
Of course adding a 0 in front of %04x is not gonna help me.
Now if I go to this guy repository (link below) I can see there is a calculus made, but I don't know JavaScript/TypeScript so I don't know how to interpret it in Bash.
https://github.com/bogdan-rosianu/elrond-converters/blob/main/src/index.ts#L30

It looks the converted hex string should have even number of digits.
Then would you please try:
MAIAR_AMOUNT_HEX=$(printf "%x" "$MAIAR_AMOUNT")
(( ${#MAIAR_AMOUNT_HEX} % 2 )) && MAIAR_AMOUNT_HEX="0$MAIAR_AMOUNT_HEX"
The condition (( ${#MAIAR_AMOUNT_HEX} % 2 )) is evaluated to be true if
$MAIAR_AMOUNT_HEX has odd length. Then 0 is prepended to adjust
the length.

The 4 in %04x is how many digits to pad the output to. Use %012x to pad the output to 12 hex digits.
❯ printf '%012x' 2925000000000
02a907960200

Related

How to printf fixed number or digits in floating in bash

I want to output floating numbers with exact number of digits without padding in the front, since I want to prepend $ sign
For example I read numbers:
1.2345
12.345
123.456
and I want output:
$1.23450
$12.3450
$123.456
I tried many different variations in printf but can't fix number of total digits. I also didn't find answer to this particular problem
printf "$%.7f\n" 1.2345 12.345 123.456
neither with "$%.7f\n "$%07f\n "$%6.4f\n "$%.7g\n
Do I need to get different formatting for for different magnitude of numbers ie 1, 10, 100?
How to printf fixed number or digits in floating in bash
Print first 7 characters from the printed number.
printf "$%.7s\n" "$(printf "%.6f" 1234.2345)"
Do I need to get different formatting for for different magnitude of numbers ie 1, 10, 100?
Yes, you should implement your own formatting function for custom formatting requirements. There is no such printf formatting specifier for what you want.
Here is an alternative to using floating-point formatting in Bash:
#!/usr/bin/env bash
float_numbers=(1.2345 12.345 123.456)
# Get the longest number's length
for float in "${float_numbers[#]}"; do
max_length=$((${#float} > max_length ? ${#float} : max_length))
done
for float in "${float_numbers[#]}"; do
# Generate needed padding
printf -v padding '%-*s' $((max_length - ${#float})) ''
# Print $float followed by its 0 padding
printf '$%s%s\n' "$float" "${padding// /0}"
done

Convert a decimal number into one with decimalplaces after last digits

I currently have a bash script which gives me numbers such as 26084729776630091742. This number is converted 26,084729776630091742 with the last 18 digits being decimal places, but how can I convert this in bash?
(The number I want to convert is an ethereum unit called WEI and the 18 last numbers are the decimal places. So it doesnt work adding a dot after the first 2 digits.)
EDIT2:
echo "128084729776630091742" | awk '{gsub(/,/,"");print substr($0,1,length($0)-18) "," substr($0,length($0)-17)}'
EDIT: As OP changed the sample so adding this code now.
echo "128,6436xxxx" | awk '{gsub(/,/,"");print substr($0,1,2) "," substr($0,3)}'
If you are looking for only this specific example then following may help you.
echo "26084729776630091742" | awk '{print substr($0,1,2) "," substr($0,3)}'
Solution 2nd: Using sed.
echo "26084729776630091742" | sed 's/\(..\)\(.*\)/\1,\2/'
what you are looking for is something like: "${str:0:2},${str:2}"
Example in your case:
str="26084729776630091742"
echo "${str:0:2},${str:2}"
prints 26,084729776630091742
EDIT as question was precised
str="26084729776630091742133"
echo "${str:0:${#str}-18},${str:${#str}-18}"
prints 26084,729776630091742133 with 18 decimal places (works fine if ${#str} > 18)

Is there a way to implement a counter in bash but for letters instead of numbers?

I'm working with an existing script which was written a bit messily. Setting up a loop with all of the spaghetti code could make a bigger headache than I want to deal with in the near term. Maybe when I have more time I can clean it up but for now, I'm just looking for a simple fix.
The script deals with virtual disks on a xen server. It reads multipath output and asks if particular LUNs should be formatted in any way based on specific criteria. However, rather than taking that disk path and inserting it, already formatted, into a configuration file, it simply presents every line in the format
'phy:/dev/mapper/UUID,xvd?,w',
UUID, of course, is an actual UUID.
The script actually presents each of the found LUNs in this format expecting the user to copy and paste them into the config file replacing each ? with a letter in sequence. This is tedious at best.
There are several ways to increment a number in bash. Among others:
var=$((var+1))
((var+=1))
((var++))
Is there a way to do the same with characters which doesn't involve looping over the entire alphabet such that I could easily "increment" the disk assignment from xvda to xvdb, etc?
To do an "increment" on a letter, define the function:
incr() { LC_CTYPE=C printf "\\$(printf '%03o' "$(($(printf '%d' "'$1")+1))")"; }
Now, observe:
$ echo $(incr a)
b
$ echo $(incr b)
c
$ echo $(incr c)
d
Because, this increments up through ASCII, incr z becomes {.
How it works
The first step is to convert a letter to its ASCII numeric value. For example, a is 97:
$ printf '%d' "'a"
97
The next step is to increment that:
$ echo "$((97+1))"
98
Or:
$ echo "$(($(printf '%d' "'a")+1))"
98
The last step is convert the new incremented number back to a letter:
$ LC_CTYPE=C printf "\\$(printf '%03o' "98")"
b
Or:
$ LC_CTYPE=C printf "\\$(printf '%03o' "$(($(printf '%d' "'a")+1))")"
b
Alternative
With bash, we can define an associative array to hold the next character:
$ declare -A Incr; last=a; for next in {b..z}; do Incr[$last]=$next; last=$next; done; Incr[z]=a
Or, if you prefer code spread out over multiple lines:
declare -A Incr
last=a
for next in {b..z}
do
Incr[$last]=$next
last=$next
done
Incr[z]=a
With this array, characters can be incremented via:
$ echo "${Incr[a]}"
b
$ echo "${Incr[b]}"
c
$ echo "${Incr[c]}"
d
In this version, the increment of z loops back to a:
$ echo "${Incr[z]}"
a
How about an array with entries A-Z assigned to indexes 1-26?
IFS=':' read -r -a alpharray <<< ":A:B:C:D:E:F:G:H:I:J:K:L:M:N:O:P:Q:R:S:T:U:V:W:X:Y:Z"
This has 1=A, 2=B, etc. If you want 0=A, 1=B, and so on, remove the first colon.
IFS=':' read -r -a alpharray <<< "A:B:C:D:E:F:G:H:I:J:K:L:M:N:O:P:Q:R:S:T:U:V:W:X:Y:Z"
Then later, where you actually need the letter;
var=$((var+1))
'phy:/dev/mapper/UUID,xvd${alpharray[$var]},w',
The only problem is that if you end up running past 26 letters, you'll start getting blanks returned from the array.
Use a Bash 4 Range
You can use a Bash 4 feature that lets you specify a range within a sequence expression. For example:
for letter in {a..z}; do
echo "phy:/dev/mapper/UUID,xvd${letter},w"
done
See also Ranges in the Bash Wiki.
Here's a function that will return the next letter in the range a-z. An input of 'z' returns 'a'.
nextl(){
((num=(36#$(printf '%c' $1)-9) % 26+97));
printf '%b\n' '\x'$(printf "%x" $num);
}
It treats the first letter of the input as a base 36 integer, subtracts 9, and returns the character whose ordinal number is 'a' plus that value mod 26.
Use Jot
While the Bash range option uses built-ins, you can also use a utility like the BSD jot utility. This is available on macOS by default, but your mileage may vary on Linux systems. For example, you'll need to install athena-jot on Debian.
More Loops
One trick here is to pre-populate a Bash array and then use an index variable to grab your desired output from the array. For example:
letters=( "" $(jot -w %c 26 a) )
for idx in 1 26; do
echo ${letters[$idx]}
done
A Loop-Free Alternative
Note that you don't have to increment the counter in a loop. You can do it other ways, too. Consider the following, which will increment any letter passed to the function without having to prepopulate an array:
increment_var () {
local new_var=$(jot -nw %c 2 "$1" | tail -1)
if [[ "$new_var" == "{" ]]; then
echo "Error: You can't increment past 'z'" >&2
exit 1
fi
echo -n "$new_var"
}
var="c"
var=$(increment_var "$var")
echo "$var"
This is probably closer to what the OP wants, but it certainly seems more complex and less elegant than the original loop recommended elsewhere. However, your mileage may vary, and it's good to have options!

Value too great for base (error token is "09")

When running this part of my bash script am getting an error
Script
value=0
for (( t=0; t <= 4; t++ ))
do
d1=${filedates[$t]}
d2=${filedates[$t+1]}
((diff_sec=d2-d1))
SEC=$diff_sec
compare=$((${SEC}/(60*60*24)))
value=$((value+compare))
done
Output
jad.sh: line 28: ((: 10#2014-01-09: value too great for base (error token is "09")
jad.sh: line 30: /(60*60*24): syntax error: operand expected (error token is "/(60*60*24)")
d1 and d2 are dates in that form 2014-01-09 and 2014-01-10
Any solution please?
Prepend the string "10#" to the front of your variables. That forces bash to treat them as decimal, even though the leading zero would normally make them octal.
What are d1 and d2? Are they dates or seconds?
Generally, this error occurs if you are trying to do arithmetic with numbers containing a zero-prefix e.g. 09.
Example:
$ echo $((09+1))
-bash: 09: value too great for base (error token is "09")
In order to perform arithmetic with 0-prefixed numbers you need to tell bash to use base-10 by specifying 10#:
$ echo $((10#09+1))
10
As others have said, the error results from Bash interpreting digit sequences with leading zeros as octal numbers. If you have control over the process creating the date values and you're using date, you can prefix the output format string with a hyphen to remove leading zero padding.
Without prefixing date format with hyphen:
$ (( $(date --date='9:00' +%H) > 10 )) && echo true || echo oops
-bash: ((: 09: value too great for base (error token is "09")
oops
With prefixing date format with hyphen:
$ (( $(date --date='9:00' +%-H) > 10 )) && echo true || echo oops
true
From the date man page:
By default, date pads numeric fields with zeroes. The following
optional flags may follow '%':
- (hyphen) do not pad the field
d1 and d2 are dates in that form 2014-01-09 and 2014-01-10
and then
((diff_sec=d2-d1))
What do you expect to get? ((diffsec=2014-01-09-2014-01-10)) ??
You need to convert the dates to seconds first:
d1=$( date -d "${filedates[$t]}" +%s )
d2=$( date -d "${filedates[$t+1]}" +%s )
(( compare = (d2 - d1) / (60*60*24) ))
(( value += compare ))
Posting some tips here related to the title of this question, but not directly related to the details of the original question. I realize that's a bit controversial action on Stack Overflow, however these related questions:
convert octal to decimal in bash [duplicate]
Value too great for base (error token is "08") [duplicate]
point to this one, and yet they are closed and hence, I could not post this answer there. Therefore, this seemed like a logical place (at least to me) to post this information that may help others in a similar situation, especially new-to-BaSH programmers.
An alternative approach to ensuring a number is treated as a 10-base integer is to use printf. This command instructs printf to treat $num as an integer and round it to 0 decimal places.
num="$(printf "%.0f" "$num")"
Or, if you want to also ensure there are no non-numeric characters in the string, you can do this:
num="$(printf "%.0f" "${num//[!0-9]/}")"
Both commands will strip out leading zeroes and round decimal values to the nearest whole number. Note the first (simpler) solution works with negative numbers, but the second does not (it will always return absolute value).
Note that printf rounds down, meaning .01 to 0.5 is rounded down to 0, while .51 to .99 is rounded up to 1. Basically, the difference between rounding up versus down in this case is that printf rounds down 0.5 and any below. I mention this because 0.5 rounded up is a more common practice.
Now, addressing the OP's specific scenario.... Combining printf with awk allows arithmetic expressions not possible with printf alone.
This
compare=$((${SEC}/(606024)))
could be alternatively be expressed as
compare=$(awk -v sec=$SEC 'BEGIN { print int(sec/(60*60*24))}')
or
compare="$(printf "%.0f" "$(awk "BEGIN { print ( $SEC / ( 60 * 60 * 24 ) ) }")")"
Meanwhile,
value=$((value+compare))
Could be calculated as
value="$(printf "%.0f" "$(awk "BEGIN { print ( $value + $compare ) }")")"
You don't need the $ and the {} in an arithmetic expansion expression. It should look like this:
compare=$((SEC/(60*60*24)))
For 'mm' and 'dd' values in dates, I use this trick:
mm="1${date:5,2}" # where 5 is the offset to mm in the date
let mm=$mm-100 # turn 108 into 8, and 109 into 9

Convert string into integer in bash script - "Leading Zero" number error

In a text file, test.txt, I have the next information:
sl-gs5 desconnected Wed Oct 10 08:00:01 EDT 2012 1001
I want to extract the hour of the event by the next command line:
hour=$(grep -n sl-gs5 test.txt | tail -1 | cut -d' ' -f6 | awk -F ":" '{print $1}')
and I got "08". When I try to add 1,
14 echo $((hour+1))
I receive the next error message:
./test2.sh: line 14: 08: value too great for base (error token is "08")
If variables in Bash are untyped, why?
See ARITHMETIC EVALUATION in man bash:
Constants with a leading 0 are interpreted as octal numbers.
You can remove the leading zero by parameter expansion:
hour=${hour#0}
or force base-10 interpretation:
$((10#$hour + 1))
what I'd call a hack, but given that you're only processing hour values, you can do
hour=08
echo $(( ${hour#0} +1 ))
9
hour=10
echo $(( ${hour#0} +1))
11
with little risk.
IHTH.
You could also use bc
hour=8
result=$(echo "$hour + 1" | bc)
echo $result
9
Here's an easy way, albeit not the prettiest way to get an int value for a string.
hour=`expr $hour + 0`
Example
bash-3.2$ hour="08"
bash-3.2$ hour=`expr $hour + 0`
bash-3.2$ echo $hour
8
In Short: In order to deal with "Leading Zero" numbers (any 0 digit that comes before the first non-zero) in bash
- Use bc An arbitrary precision calculator language
Example:
a="000001"
b=$(echo $a | bc)
echo $b
Output: 1
From Bash manual:
"bc is a language that supports arbitrary precision numbers with interactive execution
of statements. There are some similarities in the syntax to the C programming lan-
guage. A standard math library is available by command line option. If requested, the
math library is defined before processing any files. bc starts by processing code from
all the files listed on the command line in the order listed. After all files have
been processed, bc reads from the standard input. All code is executed as it is read.
(If a file contains a command to halt the processor, bc will never read from the standard input.)"
Since hours are always positive, and always 2 digits, you can set a 1 in front of it and subtract 100:
echo $((1$hour+1-100))
which is equivalent to
echo $((1$hour-99))
Be sure to comment such gymnastics. :)
The leading 0 is leading to bash trying to interpret your number as an octal number, but octal numbers are 0-7, and 8 is thus an invalid token.
If I were you, I would add some logic to remove a leading 0, add one, and re-add the leading 0 if the result is < 10.
How about sed?
hour=`echo $hour|sed -e "s/^0*//g"`

Resources