According to the problem stated in the below link:
https://www.hackerrank.com/contests/bash-and-linux-shell-practice/challenges/bash-tutorials---comparing-numbers/problem
My code is working fine in Mac OSX terminal, but while submitting the same code in Hackerrank one of the test case is failing. I am not sure why this is happening. Would really appreciate any answers.
read X
read Y
if [[ $X > $Y ]]
then
echo "X is greater than Y"
elif [[ $X < $Y ]]
then
echo "X is less than Y"
else
echo "X is equal to Y"
fi
HackerRank Custom Test Case:
Compilation Successful
Input (stdin)
-100
100
Your Output
X is greater than Y
I'm not sure why you're getting that result; I get "X is less than Y" in actual bash. However, your script is actually wrong in a different way: in [[ ]], < and > do alphabetic comparison rather than numeric comparison. To understand the difference, consider that [[ 5 < 1000 ]] will come out as false, because "5" comes after "1" in character sorting order. To do numeric comparison, use -lt and -gt instead.
You can use the Bash double-parenthesis context ((...)) vs test context [[ ... ]] to get more typical arithmetic comparisons:
x=-5
y=5
if ((x>y)); then
echo "X is greater than Y"
elif ((x<y)); then
echo "X is less than Y"
else
echo "X is equal to Y"
fi
Or use an integer comparison inside [[ ... ]] test:
if [[ "$x" -gt "$y" ]]; then
echo "X is greater than Y"
elif [[ "$x" -lt "$y" ]]; then
echo "X is less than Y"
else
echo "X is equal to Y"
fi
Inside [[ ... ]] the <,> or == tests string comparisons.
Both of these methods only work with integers; to use floats, you need to use awk, bc or other float interpreter. Be sure to use double quotes "$x" in the [[ test ]] and the quotes and sigil are not required for (( ))
With user input, be sure to test that $x and $y are actual
numbers. Good tests here...
Related
I wrote a script to compare two numbers:
#!/bin/bash
read X
read Y
if [[ $X -le $Y ]];
then
echo "X is less than Y"
elif [[ $X -ge $Y ]];
then
echo "X is greater than Y"
else
echo "X is equal to Y"
fi
For some reason when the value of X and Y are the same, the else condition is not executed. Instead the if [[ $X -le $Y ]]; is executed.
When I change the position of the if and else conditions:
#!/bin/bash
read X
read Y
if [[ $X -eq $Y ]];
then
echo "X is equal to Y"
elif [[ $X -ge $Y ]];
then
echo "X is greater than Y"
else
echo "X is less than Y"
fi
The else condition is executed for this case. Can someone please give me an explanation to why the else condition is executed for one case but not the other?
-le and -ge are like <= and >=.
-le = less than or equal
-ge = greater than or equal
-lt = less than
-gt = greater than
It'll work if you switch to -lt and -gt:
if [[ $X -lt $Y ]]
then
echo "X is less than Y"
elif [[ $X -gt $Y ]]
then
echo "X is greater than Y"
else
echo "X is equal to Y"
fi
You can also use (( )) for arithmetic operations. It has more natural syntax: you can use < and >, you don't need spaces around the operators, and $ dollar signs are optional.
if ((X < Y))
then
echo "X is less than Y"
elif ((X > Y))
then
echo "X is greater than Y"
else
echo "X is equal to Y"
fi
How do I compare a variable to a string (and do something if they match)?
Using variables in if statements
if [ "$x" = "valid" ]; then
echo "x has the value 'valid'"
fi
If you want to do something when they don't match, replace = with !=. You can read more about string operations and arithmetic operations in their respective documentation.
Why do we use quotes around $x?
You want the quotes around $x, because if it is empty, your Bash script encounters a syntax error as seen below:
if [ = "valid" ]; then
Non-standard use of == operator
Note that Bash allows == to be used for equality with [, but this is not standard.
Use either the first case wherein the quotes around $x are optional:
if [[ "$x" == "valid" ]]; then
or use the second case:
if [ "$x" = "valid" ]; then
Or, if you don't need an else clause:
[ "$x" == "valid" ] && echo "x has the value 'valid'"
a="abc"
b="def"
# Equality Comparison
if [ "$a" == "$b" ]; then
echo "Strings match"
else
echo "Strings don't match"
fi
# Lexicographic (greater than, less than) comparison.
if [ "$a" \< "$b" ]; then
echo "$a is lexicographically smaller then $b"
elif [ "$a" \> "$b" ]; then
echo "$b is lexicographically smaller than $a"
else
echo "Strings are equal"
fi
Notes:
Spaces between if and [ and ] are important
> and < are redirection operators so escape it with \> and \< respectively for strings.
To compare strings with wildcards, use:
if [[ "$stringA" == *"$stringB"* ]]; then
# Do something here
else
# Do something here
fi
I have to disagree one of the comments in one point:
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
No, that is not a crazy oneliner
It's just it looks like one to, hmm, the uninitiated...
It uses common patterns as a language, in a way;
And after you learned the language.
Actually, it's nice to read
It is a simple logical expression, with one special part: lazy evaluation of the logic operators.
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
Each part is a logical expression; the first may be true or false, the other two are always true.
(
[ "$x" == "valid" ]
&&
echo "valid"
)
||
echo "invalid"
Now, when it is evaluated, the first is checked. If it is false, than the second operand of the logic and && after it is not relevant. The first is not true, so it can not be the first and the second be true, anyway.
Now, in this case is the the first side of the logic or || false, but it could be true if the other side - the third part - is true.
So the third part will be evaluated - mainly writing the message as a side effect. (It has the result 0 for true, which we do not use here)
The other cases are similar, but simpler - and - I promise! are - can be - easy to read!
(I don't have one, but I think being a UNIX veteran with grey beard helps a lot with this.)
The following script reads from a file named "testonthis" line by line and then compares each line with a simple string, a string with special characters and a regular expression. If it doesn't match, then the script will print the line, otherwise not.
Space in Bash is so much important. So the following will work:
[ "$LINE" != "table_name" ]
But the following won't:
["$LINE" != "table_name"]
So please use as is:
cat testonthis | while read LINE
do
if [ "$LINE" != "table_name" ] && [ "$LINE" != "--------------------------------" ] && [[ "$LINE" =~ [^[:space:]] ]] && [[ "$LINE" != SQL* ]]; then
echo $LINE
fi
done
You can also use use case/esac:
case "$string" in
"$pattern" ) echo "found";;
esac
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash, IMO.
Here are some examples in Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
Enjoy.
I would probably use regexp matches if the input has only a few valid entries. E.g. only the "start" and "stop" are valid actions.
if [[ "${ACTION,,}" =~ ^(start|stop)$ ]]; then
echo "valid action"
fi
Note that I lowercase the variable $ACTION by using the double comma's. Also note that this won't work on too aged bash versions out there.
I did it in this way that is compatible with Bash and Dash (sh):
testOutput="my test"
pattern="my"
case $testOutput in (*"$pattern"*)
echo "if there is a match"
exit 1
;;
(*)
! echo there is no coincidence!
;;esac
I was struggling with the same situation for a while, here is how I could resolve:
if [ "$var1" == "$var2" ]; then
#dowhateveryouwant
fi
Be careful with the spaces left before and after the comparison sign, otherwise it won't work or it'll give you an unexpected result.
I've spent so much time on using a single equal(=) sign but didn't work. I Hope it can help.
Are you having comparison problems? (like below?)
var="true"
if [[ $var == "true" ]]; then
# It should be working, but it is not...
else
# It is falling here...
fi
Try like the =~ operator (regular expression operator) and it might work:
var="true"
if [[ $var =~ "true" ]];then
# Now it works here!!
else
# No more inequality
fi
Bash regex operator =~ (official reference)
StackOverflow further examples (here)
How do I compare a variable to a string (and do something if they match)?
Using variables in if statements
if [ "$x" = "valid" ]; then
echo "x has the value 'valid'"
fi
If you want to do something when they don't match, replace = with !=. You can read more about string operations and arithmetic operations in their respective documentation.
Why do we use quotes around $x?
You want the quotes around $x, because if it is empty, your Bash script encounters a syntax error as seen below:
if [ = "valid" ]; then
Non-standard use of == operator
Note that Bash allows == to be used for equality with [, but this is not standard.
Use either the first case wherein the quotes around $x are optional:
if [[ "$x" == "valid" ]]; then
or use the second case:
if [ "$x" = "valid" ]; then
Or, if you don't need an else clause:
[ "$x" == "valid" ] && echo "x has the value 'valid'"
a="abc"
b="def"
# Equality Comparison
if [ "$a" == "$b" ]; then
echo "Strings match"
else
echo "Strings don't match"
fi
# Lexicographic (greater than, less than) comparison.
if [ "$a" \< "$b" ]; then
echo "$a is lexicographically smaller then $b"
elif [ "$a" \> "$b" ]; then
echo "$b is lexicographically smaller than $a"
else
echo "Strings are equal"
fi
Notes:
Spaces between if and [ and ] are important
> and < are redirection operators so escape it with \> and \< respectively for strings.
To compare strings with wildcards, use:
if [[ "$stringA" == *"$stringB"* ]]; then
# Do something here
else
# Do something here
fi
I have to disagree one of the comments in one point:
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
No, that is not a crazy oneliner
It's just it looks like one to, hmm, the uninitiated...
It uses common patterns as a language, in a way;
And after you learned the language.
Actually, it's nice to read
It is a simple logical expression, with one special part: lazy evaluation of the logic operators.
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
Each part is a logical expression; the first may be true or false, the other two are always true.
(
[ "$x" == "valid" ]
&&
echo "valid"
)
||
echo "invalid"
Now, when it is evaluated, the first is checked. If it is false, than the second operand of the logic and && after it is not relevant. The first is not true, so it can not be the first and the second be true, anyway.
Now, in this case is the the first side of the logic or || false, but it could be true if the other side - the third part - is true.
So the third part will be evaluated - mainly writing the message as a side effect. (It has the result 0 for true, which we do not use here)
The other cases are similar, but simpler - and - I promise! are - can be - easy to read!
(I don't have one, but I think being a UNIX veteran with grey beard helps a lot with this.)
The following script reads from a file named "testonthis" line by line and then compares each line with a simple string, a string with special characters and a regular expression. If it doesn't match, then the script will print the line, otherwise not.
Space in Bash is so much important. So the following will work:
[ "$LINE" != "table_name" ]
But the following won't:
["$LINE" != "table_name"]
So please use as is:
cat testonthis | while read LINE
do
if [ "$LINE" != "table_name" ] && [ "$LINE" != "--------------------------------" ] && [[ "$LINE" =~ [^[:space:]] ]] && [[ "$LINE" != SQL* ]]; then
echo $LINE
fi
done
You can also use use case/esac:
case "$string" in
"$pattern" ) echo "found";;
esac
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash, IMO.
Here are some examples in Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
Enjoy.
I would probably use regexp matches if the input has only a few valid entries. E.g. only the "start" and "stop" are valid actions.
if [[ "${ACTION,,}" =~ ^(start|stop)$ ]]; then
echo "valid action"
fi
Note that I lowercase the variable $ACTION by using the double comma's. Also note that this won't work on too aged bash versions out there.
I did it in this way that is compatible with Bash and Dash (sh):
testOutput="my test"
pattern="my"
case $testOutput in (*"$pattern"*)
echo "if there is a match"
exit 1
;;
(*)
! echo there is no coincidence!
;;esac
I was struggling with the same situation for a while, here is how I could resolve:
if [ "$var1" == "$var2" ]; then
#dowhateveryouwant
fi
Be careful with the spaces left before and after the comparison sign, otherwise it won't work or it'll give you an unexpected result.
I've spent so much time on using a single equal(=) sign but didn't work. I Hope it can help.
Are you having comparison problems? (like below?)
var="true"
if [[ $var == "true" ]]; then
# It should be working, but it is not...
else
# It is falling here...
fi
Try like the =~ operator (regular expression operator) and it might work:
var="true"
if [[ $var =~ "true" ]];then
# Now it works here!!
else
# No more inequality
fi
Bash regex operator =~ (official reference)
StackOverflow further examples (here)
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.
I have a bash script in which I am attempting to compare a variable containing a whole number
VAR1=1
The real number to compare to, can be a decimal
VAR2=1.5
When I try:
if [[ $VAR1 -ge $VAR2]];
I am presented with a syntax error: invalid arithmetic operator
The problem is, when I try the >= string comparison, the result is always false irregardles of whether it logically is or not.
My question is, how can I fix this and do the arithmatic comparison?
Code Block
if [ $(bc -l <<<"$CPUUSAGE >= $MAXCPU") || $(bc -l <<<"$FREEMEM <= $MAXMEM") || $NUMHTTPD -ge $MAXHTTPD || $NUMMYSQL -ge $MAXMYSQL || $NUMPROCS -ge $MAXPROCESSES ]];
then
SendMessage;
sync ; echo 3 > /proc/sys/vm/drop_caches;
echo "Message Sent";
fi;
Bash doesn't support floating point numbers.
Try bc:
(( $(bc -l <<<"$v1 >= $v2") )) && echo "v1 is greater than or equal to v2"
I have used some bashisms here, notably the (( arithmetic context )) and <<< as an alternative to echoing the string to bc. The output of bc will be 1 or 0, depending on whether the statement is true or false. The message will only be echoed if the result is true.
The -l switch is shorthand for --mathlib, which as hek2mgl rightly asserts, is needed when working with floating point numbers.
If you want a fully-fledged if statement, you can do that as well:
if (( $(bc -l <<<"$v1 >= $v2") )); then
echo "v1 is greater than or equal to v2"
else
echo "v1 is less than v2"
fi
For the example in your question, you could use this:
if (( $(bc -l <<<"$CPUUSAGE >= $MAXCPU || $FREEMEM <= $MAXMEM") )) || [[ $NUMHTTPD -ge $MAXHTTPD || $NUMMYSQL -ge $MAXMYSQL || $NUMPROCS -ge $MAXPROCESSES ]]; then echo; fi
I've combined the two conditions in bc to save you calling the tool twice. I've also wrapped that part in an arithmetic context and used an extended test [[ for the rest.
bash does not support floating point operations. You could use bc for that:
if [ $(bc --mathlib <<< "$var1 >= $var2") = "1" ] ; then
echo "$var2 is greater than or equal to $var2"
fi
Note that unless you pass the --mathlib option, even bc would not support floating point operations.
AWK can do the trick too:
#!/bin/sh
VAR1=1
VAR2=1.5
if awk "BEGIN {exit $VAR1 >= $VAR2 ? 0 : 1}"
then
echo "$VAR1 is greater than or equal to $VAR2"
else
echo "$VAR2 is greater than or equal to $VAR1"
fi