Shell Scripting Ternary operator to get string result - string

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";

Related

Compare values in Linux

I'm using .conf which contain keys and values.
Some keys contains numbers like
deployment.conf
EAR_COUNT=2
EAR_1=xxx.ear
EAR_2=yyy.ear
When I try to retrieve that value using particular key and compare with integer value i.e. natural number.
But Whatever I retrieved values from .conf ,it is should be String datatype.
How should I compare both value in Linux Bash script.
Simply : How should I compare two values in Linux.?
Ex :
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
I'm getting following error :
count : 2
: integer expression expected30: [: 2
They're all strings in bash, notwithstanding your ability to do typeset-type things to flag them differently.
If you want to do numeric comparisons, just use -eq (or its brethren like -gt, -le) rather than ==, != and so on:
if [[ $num -eq 42 ]] ; then
echo Found the answer
fi
The full range of comparison operators can be found in the bash manpage, under CONDITIONAL EXPRESSIONS.
If you have something that you think should be a number and it's not working, I'll warrant it's not a number. Do something like:
echo "[$count]"
to make sure it doesn't have a newline at the end or, better yet, get a hex dump of it in case it holds strange characters, like Windows line endings:
echo -n $count | od -xcb
The fact that you're seeing:
: integer expression expected30: [: 2
with the : back at the start of the line, rather than the more usual:
-bash: [: XX: integer expression expected
tends to indicate the presence of a carriage return in there, which might be from deployment.conf having those Windows line endings (\r\n rather than the UNIXy \n).
The hex dump should make that obvious, at which point you need to go and clean up your configuration file.
Ref : http://linux.die.net/man/1/bash
-eq, -ne, -lt, -le, -gt, or -ge
These are arithmetic binary operators in bash scripting.
I have checked your code,
deployment.conf
# CONF FILE
EAR_COUNT=5
testArithmetic.sh
#!/bin/bash
. ./deployment.conf
count=$EAR_COUNT;
echo "count : $count";
if [ $count -gt 0 ]; then
echo "Test"
fi
running the above script evaluates to numeric comparison for fine. Share us your conf file contents, if you are facing any issues. If you are including the conf file in your script file, note the conf file must have valid BASH assignments, which means, there should be no space before and after '=' sign.
Also, you have mentioned WAR_COUNT=3 in conf part and used 'count=$EAR_COUNT;' in script part. Please check this too.
Most likely you have some non-integer character like \r in your EAR_COUNT variable. Strip all non-digits while assigning to count like this:
count=${EAR_COUNT//[^[:digit:]]/}
echo "count : $count";
if [[ $count -gt 0 ]]; then
echo "Test"
fi

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

ksh - var = $var + 1 returns 1+1 string

My code:
RETVAL1=-1
if [ $RETVAL1 -le 0 ] ; then
RETVAL1=$RETVAL1+1
print "RETVAL1: $RETVAL1"
fi
And it prints RETVAL1: -1+1
Any idea how to repair it please?
To perform arithmetic operation, use the let command: let RETVAL1=RETVAL1+1
Moreover, enclosing the expression between $(( and )) would also interpret it as an arithmetic operation. echo $((RETVAL+1))
Use the let command. This command performs arithmic operations. The + operator performs string addition.
Use it like this:
let RETVAL1=RETVAL1+1
You can also use the expr command for more general expressions.
One way:
((RETVAL1=RETVAL1+1))
Shell variables don't work like variables in most programming languages. If you want to add 1 to an integer stored in a variable, you'll need an arithmetic expression. I'm no ksh wizard, but the usual Bourne-derived-shell arithmetic expression syntax is:
RETVAL1=$((RETVAL1 + 1))
or
((RETVAL1 = RETVAL1 + 1))

How to compare two floating-point values in shell script

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

Resources