Subtract big number in bash - bash

I Want to do a sub with two big number
my purpose is
1805334111369276485744644020321551471447190030955050085289-3369574570478873127315415525946742317481702644901195284480
I try with
echo $((1805334111369276485744644020321551471447190030955050085289-3369574570478873127315415525946742317481702644901195284480))
My result is:
3160661815551241129
but it's not correct.
I can do the similar operation in console chrome
and I Have
-1.5642404591095965e+
How can I do this operation in bash?
I tried even with expr, but without lucky
It's ok to check if a number is greater then another
in shell
echo $((1805334111369276485744644020321551471447190030955050085289>3369574570478873127315415525946742317481702644901195284480))
result: 1
same operation in chrome
1805334111369276485744644020321551471447190030955050085289>3369574570478873127315415525946742317481702644901195284480
result:false
And chrome is right

The ARITHMETIC EVALUATION section of bash's manual explains why you get this result with $((...)) :
Evaluation is done in fixed-width integers with no check for overflow
You may be able to use expr (depending on compile-time options, check #Benjamin W's comment), but you need spaces between the operator and its operands :
$ expr 1805334111369276485744644020321551471447190030955050085289 - 3369574570478873127315415525946742317481702644901195284480
-1564240459109596641570771505625190846034512613946145199191
As #PesaThe mentions you can also use bc, one of its main features being able to handle arbitrary precision arithmetics :
bc <<< "1805334111369276485744644020321551471447190030955050085289 - 3369574570478873127315415525946742317481702644901195284480"
-1564240459109596641570771505625190846034512613946145199191

You can use Perl
$ perl -le ' BEGIN { use Math::BigInt; my $x=Math::BigInt->new("1805334111369276485744644020321551471447190030955050085289"); my $y=Math::BigInt->new("3369574570478873127315415525946742317481702644901195284480"); print $x->bsub($y) } '
-1564240459109596641570771505625190846034512613946145199191
$ perl -le ' BEGIN { use Math::BigInt; my $x=Math::BigInt->new("1805334111369276485744644020321551471447190030955050085289"); my $y=Math::BigInt->new("3369574570478873127315415525946742317481702644901195284480"); printf("%g\n",$x->bsub($y)) } '
-1.56424e+57
$
If you want to pass echo output to Perl, then
$ echo "1805334111369276485744644020321551471447190030955050085289-3369574570478873127315415525946742317481702644901195284480" | perl -ne ' BEGIN { use Math::BigInt } /(\d+)-(\d+)/; $x=Math::BigInt->new($1); $y=Math::BigInt->new($2); printf("%g\n",$x->bsub($y)) '
-1.56424e+57
$
As #PesaThe mentioned you can use bc also
$ bc <<< "1805334111369276485744644020321551471447190030955050085289-3369574570478873127315415525946742317481702644901195284480"
-1564240459109596641570771505625190846034512613946145199191
$

If you're feeling adventurous you can use that good old dc (desk calculator, a cute RPN calculator):
dc <<< "1805334111369276485744644020321551471447190030955050085289 3369574570478873127315415525946742317481702644901195284480 - p"
Answer is: -1564240459109596641570771505625190846034512613946145199191

Mac OSX awk can also handle big numbers:
awk 'BEGIN{print 1805334111369276485744644020321551471447190030955050085289 - \
3369574570478873127315415525946742317481702644901195284480}'
-1.56424045910959651912822682029e+57
Or by using printf:
awk 'BEGIN{printf "%.5e\n", 1805334111369276485744644020321551471447190030955050085289 - \
3369574570478873127315415525946742317481702644901195284480}'
-1.56424e+57
On the other hand GNU awk needs -M switch to support big numbers so use:
gawk -M 'BEGIN{print 1805334111369276485744644020321551471447190030955050085289 - \
3369574570478873127315415525946742317481702644901195284480}'

Related

Convert first character to capital along with special character separator

I would like to convert first character to capital and character coming after dash(-) needs to be converted to capital using bash.
I can split individual elements using - ,
echo "string" | tr [:lower:] [:upper:]
and join all but that doesn't seem effect. Is there any easy way to take care of this using single line?
Input string:
JASON-CONRAD-983636
Expected string:
Jason-Conrad-983636
I recommend using Python for this:
python3 -c 'import sys; print("-".join(s.capitalize() for s in sys.stdin.read().split("-")))'
Usage:
capitalize() {
python3 -c 'import sys; print("-".join(s.capitalize() for s in sys.stdin.read().split("-")))'
}
echo JASON-CONRAD-983636 | capitalize
Output:
Jason-Conrad-983636
In pure bash (v4+) without any third party utils
str=JASON-CONRAD-983636
IFS=- read -ra raw <<<"$str"
final=()
for str in "${raw[#]}"; do
first=${str:0:1}
rest=${str:1}
final+=( "${first^^}${rest,,}" )
done
and print the result
( IFS=- ; printf '%s\n' "${final[*]}" ; )
This might work for you (GNU sed):
sed 's/.*/\L&/;s/\b./\u&/g' file
Lowercase everything. Uppercase first characters of words.
Alternative:
sed -E 's/\b(.)((\B.)*)/\u\1\L\2/g' file
Could you please try following(in case you are ok with awk).
var="JASON-CONRAD-983636"
echo "$var" | awk -F'-' '{for(i=1;i<=NF;i++){$i=substr($i,1,1) tolower(substr($i,2))}} 1' OFS="-"
Although the party is mostly over, please let me join with a perl solution:
perl -pe 's/(^|-)([^-]+)/$1 . ucfirst lc $2/ge' <<<"JASON-CONRAD-983636"
It may be cunning to use the ucfirst function :)

currency parsing and conversion using shell commands

I'm looking for a shell one-liner that will parse the following example currency string PHP10000 into $245. I need to parse the number from the string, multiply it with a preset conversion factor then add a "$" prefix to the result.
So far, what I have is only this:
echo PHP10000 | sed -e 's/PHP//'
which gives 10000 as result.
Now, I'm stuck on how to do multiplication on that result.
I'm thinking awk could also give a solution to this but I'm a beginner at shell commands.
Update:
I tried:
echo PHP10000 | expr `sed -e 's/PHP//'` \* 2
and the multiplication works properly only on whole numbers. I can't use floating point numbers as it gives me this error: expr: not a decimal number: '2.1'.
value=PHP10000
factor=40.82
printf -v converted '$%.2f' "$(bc <<< "${value#PHP} / $factor")"
echo $converted # => $244.98
the ${value#PHP} part is parameter expansion that removes the PHP string from the front of the $value string
the <<< part is a bash here-string, so you're passing the formula to the bc program
bash does not do floating point arithmetic, so call bc to perform the calculation
printf -v varname is the equivalent of other languages varname = sprintf(...)
One way:
echo "PHP10000" | awk -F "PHP" '{ printf "$%d\n", $2 * .0245 }'
Results:
$245
Or to print to two decimal places:
echo "PHP10000" | awk -F "PHP" '{ printf "$%.2f\n", $2 * .0245 }'
Results:
$245.00
EDIT:
Bash doesn't support floating point operations. Use bc instead:
echo "PHP10000" | sed 's/PHP\([0-9]\+\)/echo "scale=2; \1*.0245\/1" | bc/e'
Results:
245.00
Something like:
echo PHP10000 | awk '/PHP/ { printf "$%.0f\n", .0245 * substr($1,4) }'
It can be easily extended to a multi-currency version that converts into one currency (known as quote currency), e.g.:
awk '
BEGIN {
rates["PHPUSD"]=.01
rates["GBPUSD"]=1.58
}
/[A-Z]{3}[0-9.]+/ {
pair=substr($1,1,3) "USD"
amount=substr($1,4)
print "USD" amount * rates[pair]
}
' <<EOF
PHP100
GBP100
EOF
Outputs:
USD1
USD158
Yet another alternative:
$ echo "PHP10000" | awk 'sub(/PHP/,""){ print "$" $0 * .0245 }'
$245

Extract version number from file in shell script

I'm trying to write a bash script that increments the version number which is given in
{major}.{minor}.{revision}
For example.
1.2.13
Is there a good way to easily extract those 3 numbers using something like sed or awk such that I could increment the {revision} number and output the full version number string.
$ v=1.2.13
$ echo "${v%.*}.$((${v##*.}+1))"
1.2.14
$ v=11.1.2.3.0
$ echo "${v%.*}.$((${v##*.}+1))"
11.1.2.3.1
Here is how it works:
The string is split in two parts.
the first one contains everything but the last dot and next characters: ${v%.*}
the second one contains everything but all characters up to the last dot: ${v##*.}
The first part is printed as is, followed by a plain dot and the last part incremented using shell arithmetic expansion: $((x+1))
Pure Bash using an array:
version='1.2.33'
a=( ${version//./ } ) # replace points, split into array
((a[2]++)) # increment revision (or other part)
version="${a[0]}.${a[1]}.${a[2]}" # compose new version
I prefer "cut" command for this kind of things
major=`echo $version | cut -d. -f1`
minor=`echo $version | cut -d. -f2`
revision=`echo $version | cut -d. -f3`
revision=`expr $revision + 1`
echo "$major.$minor.$revision"
I know this is not the shortest way, but for me it's simplest to understand and to read...
Yet another shell way (showing there's always more than one way to bugger around with this stuff...):
$ echo 1.2.3 | ( IFS=".$IFS" ; read a b c && echo $a.$b.$((c + 1)) )
1.2.4
So, we can do:
$ x=1.2.3
$ y=`echo $x | ( IFS=".$IFS" ; read a b c && echo $a.$b.$((c + 1)) )`
$ echo $y
1.2.4
Awk makes it quite simple:
echo "1.2.14" | awk -F \. {'print $1,$2, $3'} will print out 1 2 14.
flag -F specifies separator.
If you wish to save one of the values:
firstVariable=$(echo "1.2.14" | awk -F \. {'print $1'})
I use the shell's own word splitting; something like
oIFS="$IFS"
IFS=.
set -- $version
IFS="$oIFS"
although you need to be careful with version numbers in general due to alphabetic or date suffixes and other annoyingly inconsistent bits. After this, the positional parameters will be set to the components of $version:
$1 = 1
$2 = 2
$3 = 13
($IFS is a set of single characters, not a string, so this won't work with a multicharacter field separator, although you can use IFS=.- to split on either . or -.)
Inspired by the answer of jlliagre I made my own version which supports version numbers just having a major version given. jlliagre's version will make 1 -> 1.2 instead of 2.
This one is appropriate to both styles of version numbers:
function increment_version()
local VERSION="$1"
local INCREMENTED_VERSION=
if [[ "$VERSION" =~ .*\..* ]]; then
INCREMENTED_VERSION="${VERSION%.*}.$((${VERSION##*.}+1))"
else
INCREMENTED_VERSION="$((${VERSION##*.}+1))"
fi
echo "$INCREMENTED_VERSION"
}
This will produce the following outputs:
increment_version 1 -> 2
increment_version 1.2 -> 1.3
increment_version 1.2.9 -> 1.2.10
increment_version 1.2.9.101 -> 1.2.9.102
Small variation on fgm's solution using the builtin read command to split the string into an array. Note that the scope of the IFS variable is limited to the read command (so no need to store & restore the current IFS variable).
version='1.2.33'
IFS='.' read -r -a a <<<"$version"
((a[2]++))
printf '%s\n' "${a[#]}" | nl
version="${a[0]}.${a[1]}.${a[2]}"
echo "$version"
See: How do I split a string on a delimiter in Bash?
I'm surprised no one suggested grep yet.
Here's how to get the full version (not limited to the length of x.y.z...) from a file name:
filename="openshift-install-linux-4.12.0-ec.3.tar.gz"
find -name "$filename" | grep -Eo '([0-9]+)(\.?[0-9]+)*' | head -1
# 4.12.0

How can I bump a version number using bash

I would like to know how to bump the last digit in a version number using bash.
e.g.
VERSION=1.9.0.9
NEXT_VERSION=1.9.0.10
EDIT: The version number will only contain natural numbers.
Can the solution be generic to handle any number of parts in a version number.
e.g.
1.2
1.2.3
1.2.3.4
1.2.3.4.5
TL;DR:
VERSION=1.9.0.9
echo $VERSION | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.
# will print 1.9.0.10
For a detailed explanation, read on.
Let's start with the basic answer by froogz3301:
VERSIONS="
1.2.3.4.4
1.2.3.4.5.6.7.7
1.9.9
1.9.0.9
"
for VERSION in $VERSIONS; do
echo $VERSION | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g'
done
How can we improve on this? Here are a bunch of ideas extracted from the copious set of comments.
The trailing '1' in the program is crucial to its operation, but it is not the most explicit way of doing things. The odd '1' at the end is a boolean value that is true, and therefore matches every line and triggers the default action (since there is no action inside braces after it) which is to print $0, the line read, as amended by the previous command.
Hence, why not this awk command, which obviates the sed command?
awk -F. '{$NF+=1; OFS="."; print $0}'
Of course, we could refine things further — in several stages. You could use the bash '<<<' string redirection operator to avoid the pipe:
awk -F. '...' <<< $VERSION
The next observation would be that given a series of lines, a single execution of awk could handle them all:
echo "$VERSIONS" | awk -F. '/[0-9]+\./{$NF+=1;OFS=".";print}'
without the for loop. The double quotes around "$VERSION" preserve the newlines in the string. The pipe is still unnecessary, leading to:
awk -F. '/[0-9]+\./{$NF+=1;OFS=".";print}' <<< "$VERSIONS"
The regex ignores the blank lines in $VERSION by only processing lines that contain a digit followed by a dot. Of course, setting OFS in each line is a tad clumsy, and '+=1' can be abbreviated '++', so you could use:
awk -F. '/[0-9]+\./{$NF++;print}' OFS=. <<< "$VERSIONS"
(or you could include 'BEGIN{OFS="."}' in the program, but that is rather verbose.
The '<<<' notation is only supported by Bash and not by Korn, Bourne or other POSIX shells (except as a non-standard extension parallelling the Bash notation). The AWK program is going to be supported by any version of awk you are likely to be able to lay hands on (but the variable assignment on the command line was not supported by old UNIX 7th Edition AWK).
I have come up with this.
VERSIONS="
1.2.3.4.4
1.2.3.4.5.6.7.7
1.9.9
1.9.0.9
"
for VERSION in $VERSIONS; do
echo $VERSION | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g'
done
if [[ "$VERSION" == *.* ]]; then
majorpart="${VERSION%.*}."
else
majorpart=""
fi
minorpart="${VERSION##*.}"
NEXT_VERSION="$majorpart$((minorpart+1))"
Warning: if the minor part of the version number isn't in the expected format (integer, no leading zeros), this may have trouble. Some examples: "1.033" -> "1.28" (since 033 is octal for 27), "1.2.b" -> "1.2.1" (unless b is a defined variable, it'll be treated as 0), "1.2.3a" -> error ("3a" isn't a number). Depending on how many cases you want to cover, this can be made arbitrarily complex.
Well, Jonathan Leffler already answered the question, however I've generalized the solution to accept an arbitrary diff (passed as an awk parameter versionDiff):
VERSION="1.4.1.2"
awk -v versionDiff="0.1" -F. -f bump.awk OFS=. <<< "$VERSION"
the result will be:
1.5.0.0
as the numbers after last non-zero versionDiff number are zeroed.
and the bump.awk:
/[0-9]+\./ {
n = split(versionDiff, versions, ".")
if(n>NF) nIter=n; else nIter=NF
lastNonzero = nIter
for(i = 1; i <= nIter; ++i) {
if(int(versions[i]) > 0) {
lastNonzero = i
}
$i = versions[i] + $i
}
for(i = lastNonzero+1; i <= nIter; ++i) {
$i = 0
}
print
}

Setting a BASH environment variable directly in AWK (in an AWK one-liner)

I have a file that has two columns of floating point values. I also have a C program that takes a floating point value as input and returns another floating point value as output.
What I'd like to do is the following: for each row in the original, execute the C program with the value in the first column as input, and then print out the first column (unchanged) followed by the second column minus the result of the C program.
As an example, suppose c_program returns the square of the input and behaves like this:
$ c_program 4
16
$
and suppose data_file looks like this:
1 10
2 11
3 12
4 13
What I'd like to return as output, in this case, is
1 9
2 7
3 3
4 -3
To write this in really sketchy pseudocode, I want to do something like this:
awk '{print $1, $2 - `c_program $1`}' data_file
But of course, I can't just pass $1, the awk variable, into a call to c_program. What's the right way to do this, and preferably, how could I do it while still maintaining the "awk one-liner"? (I don't want to pull out a sledgehammer and write a full-fledged C program to do this.)
you just do everything in awk
awk '{cmd="c_program "$1; cmd|getline l;print $1,$2-l}' file
This shows how to execute a command in awk:
ls | awk '/^a/ {system("ls -ld " $1)}'
You could use a bash script instead:
while read line
do
FIRST=`echo $line | cut -d' ' -f1`
SECOND=`echo $line | cut -d' ' -f2`
OUT=`expr $SECOND \* 4`
echo $FIRST $OUT `expr $OUT - $SECOND`
done
The shell is a better tool for this using a little used feature. There is a shell variable IFS which is the Input Field Separator that sh uses to split command lines when parsing; it defaults to <Space><Tab><Newline> which is why ls foo is interpreted as two words.
When set is given arguments not beginning with - it sets the positional parameters of the shell to the contents of the arguments as split via IFS, thus:
#!/bin/sh
while read line ; do
set $line
subtrahend=`c_program $1`
echo $1 `expr $2 - $subtrahend`
done < data_file
Pure Bash, without using any external executables other than your program:
#!/bin/bash
while read num1 num2
do
(( result = $(c_program num2) - num1 ))
echo "$num1 $result"
done
As others have pointed out: awk is not not well equipped for this job. Here is a suggestion in bash:
#!/bin/sh
data_file=$1
while read column_1 column_2 the_rest
do
((result=$(c_program $column_1)-$column_2))
echo $column_1 $result "$the_rest"
done < $data_file
Save this to a file, say myscript.sh, then invoke it as:
sh myscript.sh data_file
The read command reads each line from the data file (which was redirected to the standard input) and assign the first 2 columns to $column_1 and $column_2 variables. The rest of the line, if there is any, is stored in $the_rest.
Next, I calculate the result based on your requirements and prints out the line based on your requirements. Note that I surround $the_rest with quotes to reserve spacing. Failure to do so will result in multiple spaces in the input file to be squeezed into one.

Resources