ge vs double equal in bash - bash

I am trying to solve a hackerrank exercise.
If n is odd, print Weird
If n is even and in the inclusive range of 2 to 5, print Not Weird
If n is even and in the inclusive range of 6 to 20, print Weird
If n is even and greater than 20, print Not Weird
My code is as follows:
read n
if [ $n%2==0 ]; then
if [ $n -ge 6 ] && [ $n -le 20 ]; then
echo "Weird"
else
echo "Not Weird"
fi
else
echo "Weird"
fi
When I give the input as 3, the result I get is Not Weird which is not correct same for 1 I get Not Weird. However, when I try this:
read n
if [ $(($n%2)) -eq 0 ]; then
if [ $n -ge 6 ] && [ $n -le 20 ]; then
echo "Weird"
else
echo "Not Weird"
fi
else
echo "Weird"
fi
I get the right result. What is the difference?

[ ] (or test) builtin:
==, or to be POSIX compliant =, does a string comparison
-eq does a numeric comparison
Note: == and -eq (and other comparisons) are parameters to the [ command, so they must be separated by whitespace, so $n%2==0 is invalid.
[[ ]] keyword:
is as [ except that it does pattern matching. Being a keyword rather than a builtin, expansion with [[ is done earlier in the scan.
(( )) syntax
Carries out arithmetic evaluation as with the let builtin. Whitespace separators are not mandatory. Using a leading $ to expand a variable is not necessary and is not recommended since it changes the expansion order.

For truth evaluation inside if-else, bash provides ((..)) operators with no need of a $ on the front.
n=5
if (( (n % 2) == 0 )); then
echo "Something"
if (( n >= 6 )) && (( n <= 20 )); then
echo "Some other thing"
else
echo "Other else thing"
fi
else
echo "Something else"
fi
Read here for more information.

Related

Multiple conditions with arithmetic, comparison and regular operators in if statement

I am trying to write a script that reads three integers, then checks if the sum of any two of those numbers is greater than the third one. If that is true, then it checks if those numbers are equal, and prints a message. If not, it checks whether any two of the numbers are equal and prints another message. If all of the above are false, it prints a message saying that all numbers are different.
I have tried to put that in the following nested conditional:
read X
read Y
read Z
if [ $X + $Y > $Z ] && [ $X + $Z > $Y ] && [ $Y + $Z > $X ]
then
if [ $X = $Y = $Z ]
then
echo "All numbers are equal."
elif [ [ $X = $Y ] && [ $X != $Z ] ] || [ [ $X = $Z ] && [ $X != $Y ] ] || [ [ $Z = $Y ] && [ $X != $Y ] ]
then
echo "Two of the numbers are equal."
else
echo "None of the numbers is equal to another."
fi
fi
I have tried all types of combinations with brackets and parentheses (the above is just one of them), but none of them has worked so far.
I have already taken a look at related posts:
Bash if statement with multiple conditions throws an error
Bash: Two conditions in if
How to represent multiple conditions in a shell if statement?
but I haven't found any that are covering conditions with arithmetic operators in them.
Can anyone please tell me what is the right way?
(Edit: I forgot to mention in the original post that I am new to bash, so please excuse me for any profound mistakes I might have made. I am still trying to figure out how things are working.)
Here is a variation on Inian's answer taking advantage of arithmetic operations not requiring $ to expand variables, them accepting logical operators, and the fact that the first test for equality of all numbers allows the following test to be simplified.
Please note that checking if the values read actually are integer would be a good idea to avoid unexpected behavior.
#!/bin/bash
read X
read Y
read Z
if
(( X+Y>Z || X+Z>Y || Y+Z>X ))
then
if
(( X==Y && Y==Z ))
then
echo "All numbers are equal"
elif
(( X==Y || X==Z || Z==Y ))
then
echo "Two of the numbers are equal"
else
echo "All three numbers are different"
fi
fi
The $(( )) for of arithmetic expression expands to the result of the evaluation of the expression found inside. The (( )) for acts as a command that returns 0 if the expression is a test that results in a "true" value OR if it evaluates to a non-zero number, and a non-zero value otherwise. This second form is very useful for tests.
As an aside, I like using the properties of (( )) to handle on/off options in scripts. For instance, ((state_variable)) will evaluate to "false" if the variable is null or 0, and "true" otherwise, which maps nicely to how such a variable is intuitively expected to behave.
You could also do it a different way by just incrementing for matches and using a case statement.
Should make it easier to scale with more variables as well.
#!/bin/bash
read X
read Y
read Z
((Matches+=(X==Y)))
((Matches+=(Y==Z)))
((Matches+=(X==Z)))
case "$Matches" in
0) echo "None of the numbers is equal to another.";;
1) echo "Two of the numbers are equal.";;
3) echo "All numbers are equal.";;
esac
A completely different approach to tackle the problem.
#!/bin/bash
declare -A a # declare associative array a
read x; a[$x]=$x
read x; a[$x]=$x
read x; a[$x]=$x
case ${#a[#]} in
1) echo "All numbers are equal." ;;
2) echo "Two of the numbers are equal." ;;
3) echo "None of the numbers is equal to another." ;;
esac
One-liner function, condensed from 123's answer:
n=(No Two "" All)
3a(){ echo "${n[$((($1==$2)+($1==$3)+($3==$2)))]} numbers are equal." ; }
Show all three cases:
3a 1 1 1 ; 3a 1 1 2 ; 3a 1 2 3
All numbers are equal.
Two numbers are equal.
No numbers are equal.

Shell Script command not found error

Below my script for up to n prime numbers. When I run it, it always shows an error that command not found in line 12 and 18 both. What am I doing wrong?
clear
echo"enter the number upto which you want prime numbers : "
read n
for((i=1;i<=n;i++))
do
flag=0
for((j=2;j<i;j++))
do
if [expr $i % $j-eq 0]
then
flag=1
fi
done
if [$flag-eq 0]
then
echo $i
fi
done
As pointed out in comments, you must use spaces around [ and ], as well as the comparison operators. Even more safe when using [ and ] is quoting your variables to avoid word splitting (not actually required in this specific case, though).
Additionally, you want to compare the output of expr to 0, so you have to use command substitution:
if [ $(expr "$i" % "$j") -eq 0 ]
and
if [ "$flag" -eq 0 ]
Since you're using Bash, you can use the (( )) compound command:
if (( i % j == 0 ))
and
if (( flag == 0 ))
No expr needed, no command substitution, no quoting required, no $ required, and the comparison operators have their "normal", expected meaning.
There are a number of syntax errors other than the brackets of if statement. Kindly go through the piece of code below. I have checked it running on my system.
#!/bin/sh
echo "enter the number upto which you want prime numbers : "
read n
for((i=1;i<=n;i++))
do
flag=0
for((j=2;j<i;j++))
do
if [ `expr $i % $j` -eq 0 ]
then flag=1
fi
done
if [ $flag -eq 0 ]
then echo $i
fi
done

Why does [ a -gt b ] (not [ "$a" -gt "$b" ]) appear to work?

I have one problem in unix shell scripting. Let me ask you with very simple example.
suppose, I am getting user input and comparing two numbers.
echo "Enter the first number"
read a
echo "Enter the second number"
read b
if [ a -gt b ]---------------------->I have not used $ before variable name.
then
echo "a is greater"
else
echo "b is greater"
fi
In this example, i should have used $ to get the value of variable. By mistake, I forgot. Still, it gives me right result, how and why?
I tried to debug by using sh -x filename. but, it doesn't show any value while comparing(because i havn't used $ sign).
How the shell decide which is greater and vice versa?
How it works internally?
Thanks.
Notably, you could use this without a $ if you did the comparison in a numeric context.
In bash:
if (( a > b )); then
echo "a is greater"
else
echo "b is greater"
fi
...would be correct, as (( )) (double parens) enters a numeric context within which all textual strings are treated as variables and automatically dereferenced (and within which operators such as > and < have their typical arithmetic meanings, rather than performing redirections).
What you're doing now is heavily implementation-dependent. [ a '>' b ] would be a lexographic comparison between the letters a and b. [ a -gt b ] is an arithmetic test.
Many shells will not allow this at all. For instance:
$ a=2; b=3; [ a -gt b ]; echo $?
-bash: [: a: integer expression expected
2
Notably, this is different inside of bash's [[ ]] test context (which doesn't carry the POSIX semantics of [ ]). There, what you're doing actually does work, though it's harder to read than the (( )) form:
$ a=2; b=3; [[ a -gt b ]]; echo $?
1
$ a=3; b=2; [[ a -gt b ]]; echo $?
0
If you're limited to POSIX sh, you don't have (( )), but you do have $(( )), which you can use almost the same way:
$ a=2; b=3; result=$(( a > b ? 1 : 0 )); echo "$result"
0
$ a=3; b=2; result=$(( a > b ? 1 : 0 )); echo "$result"
1
There is a simple explanation why your test appears to work: it blindly executes the else clause.
The [ ... ] built-in has no way to report the error to the caller except by exiting with a non-zero exit status. Incidentally, a non-zero exit status is also used to indicate "false". This means that if cannot distinguish between a false and an erroneous evaluation of its condition, which is why your comparison always prefers the else branch and thus appears to work. You can easily test it by reverting the arguments:
$ if [ b -gt a ]
then
echo "b is greater"
else
echo "a is greater"
fi
dash: 11: [: Illegal number: b
a is greater
Note that the error message printed to standard error is, as far as the if statement is concerned, a mere side effect of the test command, and is entirely ignored.

bash while within while

I have a condition to test. I want variable A to equal Variable B. It might take a few tests in quick succession before A equals B. I don't want it to try more than 10 times, though.
A=1
B=2
while [ $A != $B ]
do
echo "hi there"
done
So obviously that is just going to keep looping unless A ever equals B. Just in case A is not going to equal B anytime soon, I want to limit the number of tries. I was thinking of something like this:
A=1
B=2
COUNT=0
while [ "$COUNT" -le 10 ]
do
while [ $A != $B ]
do
echo "hi there"
COUNT=`expr $COUNT + 1`
done
done
I found answers to similar questions here, but not quite this one.
Thanks!
A=1
B=2
COUNT=0
while [ "$COUNT" -le 10 ] || [ $A != $B ]
do
echo "hi there"
COUNT=`expr $COUNT + 1`
done
If I understand right you don't want to do too loops, but one loop with two breaking conditions.
while (and many other compound commands) can be nested legally. Your logic isn't quite right for what you're trying to accomplish.
n=
while [[ $a != "$b" && ++n -le 10 ]]; do
echo 'hi there'
done
For purely numeric comparisons, [ and [[ shouldn't be used in Bash.
for (( n = 0; a != b && ++n <= 10; )); do
...
There are many possibilities. As an aside, never use expr or backticks unless you require compatibility with ancient non-POSIX Bourne shells.

How to if/else statement in shell script

I'm receiveing an error on a simple script when using if/else statement.
The code:
#!/bin/sh
count=100
if [$count > 3]; then
echo "Test IF"
fi
The error: /bin/ash: line 6: [100: not found
#!/bin/sh
count=100;
if [ "$count" -gt 3 ]; then
echo "Test IF";
fi
Correct your syntax: spaces must be used around [ and ], parameter expansions must be quoted, and -gt is appropriate for numeric comparisons inside of [ ]. > in sh is used as redirection operator; if you want to use it in arithmetical comparison, you must use the bash-only syntax
$(( $count > 3 ))
#!/bin/sh
if [ $var -eq 12 ]; then
echo "This is a numeric comparison if example"
fi
if [ "$var" = "12" ]; then
echo "This is a string if comparison example"
fi
if [[ "$var" = *12* ]]; then
echo "This is a string regular expression if comparison example"
fi
The if statement in shell uses the command [.
Since [ is a command (you could also use 'test'), it requires a space before writing the condition to test.
To see the list of conditions, type:
man test
You'll see in the man page that:
s1 > s2 tests if string s1 is after string s2
n1 gt n2 tests if integer n1 is greater than n2
In your case, using > would work, because string 100 comes after string 3, but it is more logical to write
if [ $count -gt 3 ]; then
echo "test if"
fi
this will also do!
#!/bin/sh
count=100
if [ $count -gt 3 ];
then
echo "Test IF"
fi
if [[ "$x" == "true" ]]; then
echo "value matched"
else
echo "value doesn't matched"
fi

Resources