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.
Related
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.
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.
This is my bash scripting code so I want to know How to Rewrite the below Bash script using a “for” loop instead of the “while” loop.
#!/bin/bash
if [ $# -gt 0 ]; then
a=0;
if [ -f RandNos ]; then
rm RandNos;
fi
while [ $a -lt $1 ]
do
a='expr $a + 1';
myrand=$RANDOM;
if [ "$2" "1"]; then
echo "No. $a ==> $myrand";
fi
echo $myrand>>RandNos
done
else
echo "please use with an argument..."
fi
Thanks.
The short of it: for counter-based loops, use the C-like form of the for loop:
for (( a = 0; a < $1; a++ )); do
# ... use $a
done
(This replaces while [ $a -lt $1 ]; do a='expr $a + 1' ...; done.)
See below for more on the rules that apply inside (( ... )).
As for the rest of your code:
Conditional [ "$2" "1"] is broken: it's missing the mandatory space before ]
With that fixed, it'll only work if $2 expands to a unary test operator such as -n.
Perhaps you meant if [[ -z $myrand ]]; then, to check if $RANDOM resulted in a nonempty string?
a='expr $a + 1' - which you don't need anymore with the for loop - doesn't actually invoke expr, because you're using single quotes - you'd need backticks (`) instead, or, preferably, the modern equivalent: $(expr $a + 1). However, with arithmetic evaluation, this could be simplified to (( ++a )).
[ ... ] conditionals work in bash, but they're provided for POSIX compatibility - use [[ ... ]] as the bash-specific alternative, which is more robust, has more features, and is faster.
bash statements only need terminating with ; if you place multiple on a single line
Note that bash considers do ... and then ... separate statements, hence you often see if ...; then and for ...; do.
In general, I encourage you to syntax-check your shell code at http://shellcheck.net - it's a great tool for detecting syntax problems.
Note how different rules apply inside (( ... )) compared to elsewhere in bash:
spaces around the = in the variable assignment are allowed.
referencing a variable without the $ prefix (a++) is allowed.
< performs numerical comparison (whereas inside [[ ... ]] it's lexical) -i.e., it's the more natural equivalent to -lt inside [ ... ] or [[ ... ]].
several other mathematical and even bit-wise operators are supported
...
All these different rules apply when bash operates in an arithmetic context, which applies to (( ... )), $(( ... )), array subscripts, and other cases.
For all the rules, run man bash and read the ARITHMETIC EVALUATION section.
Simply rewriting it with a for loop results in:
#!/bin/bash
if [ $# -gt 0 ]; then
if [ -f RandNos ]; then
rm RandNos;
fi
lim=$(expr $1 - 1)
as=$(seq 0 $lim)
for a in $as
do
a='expr $a + 1';
myrand=$RANDOM;
if [ "$2" "1"]; then # <- Caveat: conditional is BROKEN
echo "No. $a ==> $myrand";
fi
echo $myrand>>RandNos
done
else
echo "please use with an argument..."
fi
But there are several things wrong with the script anyhow. Like the last if statement.
if [ $# -lt 1 ];then
echo "First argument must be number".
exit 1;
fi
for a in `seq $1`
do
...
done
Several things can be improved:
#!/bin/bash
if (( $# )); then # anything but 0 is true
rm -f RandNos # remove if existing, otherwise fail silently
for ((a=0; a<$1; a++)); do
myrand=$RANDOM
# what is the intention here?
(( $2 > 1 )) && echo "No. $a ==> $myrand"
echo "$myrand" >> RandNos
done
else
echo "please use with an argument..."
fi
not sure what your intention was with the [ "$2" "1" ] expression. it is probably not what I made from it.
for ((a=1; a<=$1; a++)); do
may reflect your intended logic better, as you use $a for output only after incrementing it. as pointed out and corrected by #mklement0
!/bin/bash
if [ $# -gt 0 ]; then
a=0;
if [ -f RandNos ]; then
rm RandNos;
fi
for (( i=$a; i<$1; i++ ))
do
myrand=$RANDOM;
if [ "$2" = "1" ]; then
echo "No. $a ==> $myrand";
fi
echo $myrand >> RandNos
done
else
echo "please use with an argument..."
fi
Sometimes when making conditionals, I need the code to do nothing, e.g., here, I want Bash to do nothing when $a is greater than "10", print "1" if $a is less than "5", otherwise, print "2":
if [ "$a" -ge 10 ]
then
elif [ "$a" -le 5 ]
then
echo "1"
else
echo "2"
fi
This makes an error though. Is there a command which will do nothing and also not slow down my script?
The no-op command in shell is : (colon).
if [ "$a" -ge 10 ]
then
:
elif [ "$a" -le 5 ]
then
echo "1"
else
echo "2"
fi
From the bash manual:
: (a colon)
Do nothing beyond expanding arguments and performing redirections. The return status is zero.
You can probably just use the true command:
if [ "$a" -ge 10 ]; then
true
elif [ "$a" -le 5 ]; then
echo "1"
else
echo "2"
fi
An alternative, in your example case (but not necessarily everywhere) is to re-order your if/else:
if [ "$a" -le 5 ]; then
echo "1"
elif [ "$a" -lt 10 ]; then
echo "2"
fi
Although I'm not answering the original question concering the no-op command, many (if not most) problems when one may think "in this branch I have to do nothing" can be bypassed by simply restructuring the logic so that this branch won't occur.
I try to give a general rule by using the OPs example
do nothing when $a is greater than "10", print "1" if $a is less than "5", otherwise, print "2"
we have to avoid a branch where $a gets more than 10, so $a < 10 as a general condition can be applied to every other, following condition.
In general terms, when you say do nothing when X, then rephrase it as avoid a branch where X. Usually you can make the avoidance happen by simply negating X and applying it to all other conditions.
So the OPs example with the rule applied may be restructured as:
if [ "$a" -lt 10 ] && [ "$a" -le 5 ]
then
echo "1"
elif [ "$a" -lt 10 ]
then
echo "2"
fi
Just a variation of the above, enclosing everything in the $a < 10 condition:
if [ "$a" -lt 10 ]
then
if [ "$a" -le 5 ]
then
echo "1"
else
echo "2"
fi
fi
(For this specific example #Flimzys restructuring is certainly better, but I wanted to give a general rule for all the people searching how to do nothing.)
instead of :, true, false I use
echo -n ""
It avoid empty line in terminal
How do I change this var ?
max=0;
min=20000000;
cat |while read
do
read a
if [[ $a -gt $max ]]
then
max=a`
fi
`if [[ $a -lt $min ]]
then
min=a
fi
done
echo $max
echo $min
My min and max are still the same, 0 and 2000000. Can anybody help me with this ? I have no idea.
The (main) problem with your script is that setting min and max happens in a subshell, not your main shell. So the changes aren't visible after the pipeline is done.
Another one is that you're calling read twice - this might be intended if you want to skip every other line, but that's a bit unusual.
The last one is that min=a sets min to a, literally. You want to set it to $a.
Using process substitution to get rid of the first problem, removing the (possibly) un-necessary second read, and fixing the assignments, your code should look like:
max=0
min=20000000
while read a
do
if [[ $a -gt $max ]]
then
max=$a
fi
if [[ $a -lt $min ]]
then
min=$a
fi
done < <(cat) # careful with the syntax
echo $max
echo $min