How to compare two floating-point values in shell script - linux

I had to do a division in shell script and the best way was:
result1=`echo "scale=3; ($var1 / $total) * 100"| bc -l`
result2=`echo "scale=3; ($var2 / $total) * 100"| bc -l`
but I want to compare the values of $result1 and $result2
Using if test $result1 -lt $result2 or if [ $result1 -gt $result2 ] didn't work :(
Any idea how to do that?

You can compare floating-point numbers using expr(1):
: nr#yorkie 3724 ; expr 3.1 '<' 3.3
1
: nr#yorkie 3725 ; expr 3.1 '<' 3.09
0
You can also have bc do the comparisons as well as the calculations:
if [ "$(echo $result1 '<' $result2 | bc -l)" -eq 1 ];then ... fi
Finally, ksh93 can do arithmetic evaluation $(($result1 < $result2)) with floating-point numbers, although bash cannot.

note that you've gotta be a bit careful when dealing with floating point numbers and if you are testing for equality you really want to decide on some precision and then compare using that. Something like:
if (abs(x1-x2) < 0.0001) then equal # pseudo-code
the reason being that with computers we're dealing with limited-precision binary fractions not true mathematical reals. Limiting the precision in bc with the scale=3 will have this effect.
I'd also advise against trying to do this stuff in shell script. It's not that you can't do it but you'll have to fork off lots of little sub commands to do the tricky bits and that's slow to execute and generally a pain to write - you spend most of your time trying to get the shell to do what you want rather than writing the code you really want. Drop into a more sophisticated scripting language instead; my language of choice is perl but there are others. like this...
echo $var1 $var2 $total | perl -ne 'my ($var1, $var2, $tot) = split /\s+/; if ($var1/$tot == $var2/$tot) { print "equal\n"; }'
also note that you're dividing by the same value ($total in your question) so the whole comparison can be done against the numerators (var1 and var2) provided $total is positive

Posting a new answer since I cannot yet comment...
#Norman Ramsey's answer is not quite accurate:
expr will perform an integer or string comparison, not a floating-point comparison.
Here's what the man page says:
expr1 {=, >, >=, <, <=, !=} expr2
Return the results of integer comparison if both arguments are integers; otherwise, returns the results of string comparison using the locale-specific collation sequence.
(just try expr 8.9 '<' 10 and get 0 where it should be 1).
bcworks great, but isn't always installed.
So another alternative is using perl -e:
perl -e 'print expression' will print 1 if expression is true and nothing (empty string) otherwise.
e.g. perl -e 'print 8.9 < 10' - prints "1", while perl -e 'print 2>4' prints nothing.
And when used in if statement:
if [ $(perl -e "print $result1 < $result2") ];then ... fi

Related

Shell Scripting Ternary operator to get string result

In shell scripting, I am using ternary operator like this:
(( numVar == numVal ? (resVar=1) : (resVar=0) ))
I watched shell scripting tutorial by Derek Banas and got the above syntax at 41:00 of the video
https://www.youtube.com/watch?v=hwrnmQumtPw&t=73s
The above code works when we assign numbers to resVar, but if I try to assign a string to resVar, it always returns 0.
(( numVar == numVal ? (resVar="Yop") : (resVar="Nop") ))
and also tried
resVar=$(( numVar == numVal ? (echo "Yop") : (echo "Nop") ))
So which is the right way to do this?
You didn't tell us what shell you use but it's possible you use
bash or something similar. Ternary operator in Bash works only with numbers as
explained in man bash under ARITHMETIC EVALUATION section:
The shell allows arithmetic expressions to be evaluated, under
certain circumstances (see the let and declare builtin commands and
Arithmetic Expansion). Evaluation is done in fixed-width integers
with no check for over- flow, though division by 0 is trapped and
flagged as an error. The operators and their precedence,
associativity, and values are the same as in the C language. The
following list of operators is grouped into levels of
equal-precedence operators. The levels are listed in order of
decreasing precedence.
(...)
expr?expr:expr
conditional operator
And the reason that resVar is assigned 0 when you use "Yop" or
"Nop" is because such string is not a valid number in bash and
therefore it's evaluated to 0. It's also explained in man bash in
the same paragraph:
A null value evaluates to 0.
It's also explained in this Wikipedia
article if you find it
easier to read:
A true ternary operator only exists for arithmetic expressions:
((result = condition ? value_if_true : value_if_false))
For strings there only exist workarounds, like e.g.:
result=$([ "$a" == "$b" ] && echo "value_if_true" || echo
"value_if_false")
(where "$a" == "$b" can be any condition test, respective [, can
evaluate.)
Arkadiusz already pointed out that ternary operators are an arithmetic feature in bash, not usable in strings. If you want this kind of functionality in strings, you can always use arrays:
$ arr=(Nop Yop)
$ declare -p arr
declare -a arr='([0]="Nop" [1]="Yop")'
$ numVar=5; numVal=5; resvar="${arr[$((numVar == numVal ? 1 : 0))]}"; echo "$resvar"
Yop
$ numVar=2; numVal=5; resvar="${arr[$((numVar == numVal ? 1 : 0))]}"; echo "$resvar"
Nop
Of course, if you're just dealing with two values that can be in position 0 and 1 in your array, you don't need the ternary; the following achieves the same thing:
$ resvar="${arr[$((numVar==numVal))]}"
you can use this simple expression :
resVar=$([ numVar == numVal ] && echo "Yop" || echo "Nop")
Running with Gohti's idea to make the script more readable:
#!/bin/bash
declare -a resp='([0]="not safe" [1]="safe")'
temp=70; ok=$(( $temp > 60 ? 1 : 0 ))
printf "The temperature is $temp Fahrenheit, it is ${resp[$ok]} to go outside\n";
temp=20; ok=$(( $temp > 60 ? 1 : 0 ))
printf "The temperature is $temp Fahrenheit, it is ${resp[$ok]} to go outside\n";

What is the gt for here? "if [ $VARIABLE -gt 0 ]; then"

What does the -gt mean here:
if [ $CATEGORIZE -gt 0 ]; then
This is part of a bash script I'm working with.
Also, where can I find a list of "flags" that go in there so I can have for reference in the future?
-gt is an arithmetic test that denotes greater than.
Your condition checks if the variable CATEGORIZE is greater than zero.
Quoting from help test (the [ is a command known as test; help is a shell builtin that provides help on shell builtins):
arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
-lt, -le, -gt, or -ge.
-eq: Equal
-ne: Not equal
-lt: Less than
-le: Less than or equal to
-gt: Greater than
-ge: Greater than or equal to
You could also express the condition in an arithmetic context1 by saying:
if ((CATEGORIZE > 0)); then
instead of
if [ $CATEGORIZE -gt 0 ]; then
1 Quoting from help '((':
(( ... )): (( expression ))
Evaluate arithmetic expression.
The EXPRESSION is evaluated according to the rules for arithmetic
evaluation. Equivalent to "let EXPRESSION".
Exit Status:
Returns 1 if EXPRESSION evaluates to 0; returns 0 otherwise.
-gt means "greater than", compared arithmetically
[ is (peculiarly) an alias of test (with a mandatory last argument of ], to make it look like a pair of brackets).
bash has its own "builtin" version of [/test, so any bash reference (e.g man bash, info bash, or http://www.gnu.org/software/bash/manual/) will document that, or man [/man test should give you the documentation for the standard standalone version.
Specifically, this page gives an overview of the command, as implemented by bash, and this page lists the available operators.
As well as arithmetic and string tests, you may come across the -e test, for "file exists", as in [ -e /hard/coded/path/$variable_filename ]
bash also includes a slightly extended version, in the form of [[ ... ]].

How can I do division with variables in a Linux shell?

When I run commands in my shell as below, it returns an expr: non-integer argument error. Can someone please explain this to me?
$ x=20
$ y=5
$ expr x / y
expr: non-integer argument
Those variables are shell variables. To expand them as parameters to another program (ie expr), you need to use the $ prefix:
expr $x / $y
The reason it complained is because it thought you were trying to operate on alphabetic characters (ie non-integer)
If you are using the Bash shell, you can achieve the same result using expression syntax:
echo $((x / y))
Or:
z=$((x / y))
echo $z
I believe it was already mentioned in other threads:
calc(){ awk "BEGIN { print "$*" }"; }
then you can simply type :
calc 7.5/3.2
2.34375
In your case it will be:
x=20; y=3;
calc $x/$y
or if you prefer, add this as a separate script and make it available in $PATH so you will always have it in your local shell:
#!/bin/bash
calc(){ awk "BEGIN { print $* }"; }
Why not use let; I find it much easier.
Here's an example you may find useful:
start=`date +%s`
# ... do something that takes a while ...
sleep 71
end=`date +%s`
let deltatime=end-start
let hours=deltatime/3600
let minutes=(deltatime/60)%60
let seconds=deltatime%60
printf "Time spent: %d:%02d:%02d\n" $hours $minutes $seconds
Another simple example - calculate number of days since 1970:
let days=$(date +%s)/86400
Referencing Bash Variables Requires Parameter Expansion
The default shell on most Linux distributions is Bash. In Bash, variables must use a dollar sign prefix for parameter expansion. For example:
x=20
y=5
expr $x / $y
Of course, Bash also has arithmetic operators and a special arithmetic expansion syntax, so there's no need to invoke the expr binary as a separate process. You can let the shell do all the work like this:
x=20; y=5
echo $((x / y))
To get the numbers after decimal point, you can do this:-
read num1 num2
div=`echo $num1 / $num2 | bc -l`
echo $div
let's suppose
x=50
y=5
then
z=$((x/y))
this will work properly .
But if you want to use / operator in case statements than it can't resolve it.
In that case use simple strings like div or devide or something else.
See the code

csh inline math

I need to do some integer math in csh (and no, other shells are not an option, nor is bc, nor is perl, nor is python, period).
In bash my task would look like
seq 1 1 10 > m.txt #supplied from elsewhere
a=2 #supplied from elsewhere
b=3 #supplied from elsewhere
head -n $[$a*$b] m.txt # the line in question
then the question is Is there an expression in csh that computes $[$a*$b] inline?
I know that I can do # c = $a * $b in csh, but that's not inline. I did a little bit of googling and searching SO, but no success so far, so any help is greatly appreciated!
Are your use of square-brackets meant to indicate an array notation or matrix math? csh has no such built-in features.
ELSE, if you mean like bash $(($a * $b)), you can use csh cmd-substitution with backquotes to give you
head -n `expr $a \* $b` m.txt
Note that if your goal was to avoid spawning extra processes, this does not meet your goal, but it is "in-line"
Edit I see I mistyped as $( $a * $b ), see inline correction above.
IHTH.
Without using something outside of the shell, no.
The usual culprit for math from old school shell scripts is expr:
head -n `expr $a \* $b` m.txt
but if that's just as verboten as bc et al, then you're out of luck. Period.
Yes, but it's not pretty:
% seq 1 1 10 > m.txt
% set a = 2
% set b = 3
% head -n `# tmp = $a * $b ; echo $tmp ; unset tmp` m.txt
1
2
3
4
5
6
Note that this will clobber $tmp if you happen to have a variable of that name, so choose a unique name.
(Though I wonder why bc, perl, and python are not an option.)

Improve my password generation script

I have created a little password generation script. I'm curious to what improvements can be made for it except input error handling, usage information etc. It's the core functionality I'm interested in seeing improvements upon.
This is what it does (and what I like it to do):
Keep it easy to change which Lowercase characters (L), Uppercase characters (U), Numbers (N) and Symbols (S) that are used in passwords.
I'd like it to find a new password of legnth 10 for me in max two seconds.
It should take a variable length of the password string as an argument.
Only a password containing at least one L, U, N and S should be accepted.
Here is the code:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
S="\-/\\)?=+.%#"
until [ $(echo $password | grep [$L] | grep [$U] | grep [$N] | grep -c [$S] ) == 1 ]; do
password=$(cat $RNDSOURCE | tr -cd "$L$U$N$S" | head -c $PASSWORDLENGTH)
echo In progress: $password # It's simply for debug purposes, ignore it
done
echo Final password: $password
My questions are:
Is there a nicer way of checking if the password is acceptable than the way I'm doing it?
What about the actual password generation?
Any coding style improvements? (The short variable names are temporary. Though I'm using uppercase names for "constants" [I know there formally are none] and lowercase for variables. Do you like it?)
Let's vote on the most improved version. :-)
For me it was just an exercise mostly for fun and as a learning experience, albeit I will start using it instead of the generation from KeepassX which I'm using now. It will be interesting to see which improvements and suggestions will come from more experienced Bashistas (I made that word up).
I created a little basic script to measure performance: (In case someone thinks it's fun)
#!/bin/bash
SAMPLES=100
SCALE=3
echo -e "PL\tMax\tMin\tAvg"
for p in $(seq 4 50); do
bcstr=""; max=-98765; min=98765
for s in $(seq 1 $SAMPLES); do
gt=$(\time -f %e ./genpassw.sh $p 2>&1 1>/dev/null)
bcstr="$gt + $bcstr"
max=$(echo "if($max < $gt ) $gt else $max" | bc)
min=$(echo "if($min > $gt ) $gt else $min" | bc)
done
bcstr="scale=$SCALE;($bcstr 0)/$SAMPLES"
avg=$(echo $bcstr | bc)
echo -e "$p\t$max\t$min\t$avg"
done
You're throwing away a bunch of randomness in your input stream. Keep those bytes around and translate them into your character set. Replace the password=... statement in your loop with the following:
ALL="$L$U$N$S"
password=$(tr "\000-\377" "$ALL$ALL$ALL$ALL$ALL" < $RNDSOURCE | head -c $PASSWORDLENGTH)
The repetition of $ALL is to ensure that there are >=255 characters in the "map to" set.
I also removed the gratuitous use of cat.
(Edited to clarify that what appears above is not intended to replace the full script, just the inner loop.)
Edit: Here's a much faster strategy that doesn't call out to external programs:
#!/bin/bash
PASSWORDLENGTH=$1
RNDSOURCE=/dev/urandom
L="acdefghjkmnpqrtuvwxy"
U="ABDEFGHJLQRTY"
N="012345679"
# (Use this with tr.)
#S='\-/\\)?=+.%#'
# (Use this for bash.)
S='-/\)?=+.%#'
ALL="$L$U$N$S"
# This function echoes a random index into it's argument.
function rndindex() { echo $(($RANDOM % ${#1})); }
# Make sure the password contains at least one of each class.
password="${L:$(rndindex $L):1}${U:$(rndindex $U):1}${N:$(rndindex $N):1}${S:$(rndindex $S):1}"
# Add random other characters to the password until it is the desired length.
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
password=$password${ALL:$(rndindex $ALL):1}
done
# Now shuffle it.
chars=$password
password=""
while [[ ${#password} -lt $PASSWORDLENGTH ]]
do
n=$(rndindex $chars)
ch=${chars:$n:1}
password="$password$ch"
if [[ $n == $(( ${#chars} - 1 )) ]]; then
chars="${chars:0:$n}"
elif [[ $n == 0 ]]; then
chars="${chars:1}"
else
chars="${chars:0:$n}${chars:$((n+1))}"
fi
done
echo $password
Timing tests show this runs 5-20x faster than the original script, and the time is more predictable from one run to the next.
you could just use uuidgen or pwgen to generate your random passwords, maybe later shuffling some letters around or something of the sort
secpwgen is very good (it can also generate easier to remember diceware passwords) - but has almost disappeared from the net. I managed to track down a copy of the 1.3 source & put it on github.
It is also now part of Alpine Linux.

Resources