bash if statement doesn't work as expected - bash

I'm new in bash scripting.
I use this code:
#!/bin/bash
count=0
ende="100"
while true; do
out=$(php '/var/www/testsh.php' $count)
if [ "$out"!=="$ende" ]
then
echo "$out i break"
break
fi
echo "sleeping"
sleep 10
((count++))
done
In my PHP: echo '100';
./test.sh gives me:
100 i break
but it should output sleeping till $out is not 100

"$out"!=="$ende" gets substituted into 100!==100, which is a non-empty string, which [ evaluate the same as -n 100!==100 - true, because it is a non-zero string.
"$out" != "$ende" would get substituted into 100 != 100 which is an operator with two operands, and evaluates to false (as it compares two strings).
In a different scenario, != might produce a logical error, since it compares operands as strings. Fortunately, equality of strings in your case is identical to equality of integers (for which you would use -ne), but don't count for it always to be the case.
In [, operators need their space
In [, there's no such thing as !==
For integers, use -eq, -ne, -lt, -le, -gt, -ge
Also, you might like seq:
for count in $(seq 100)
do
# will happen 100 times, with `count` from `0` to `99`
done

Related

sh shell double if statement

Can anyone see what I did wrong here? I keep getting the following error message: [[: not found
read INPUT
if [[ "$INPUT" -ge 1 ]] && [[ "$INPUT" -le 10 ]]; then
Do something
else
printf "Please enter a value between 1 and 10"
fi
[[ is not available in scripts which start with #!/bin/sh, or which are started with sh yourscript. Start your script with #!/bin/bash if you want to use it.
See also http://mywiki.wooledge.org/BashGuide/Practices#Choose_Your_Shell
If you are going to use bash, by the way, there's a better syntax for numeric comparisons:
if (( input >= 1 && input <= 10 )); then ...
Note that lower-case variable names are preferred for local use -- all-upper-case names are reserved for environment variables and shell builtins.
If you're not going to use bash, use the POSIX test operator:
if [ "$input" -ge 1 ] && [ "$input" -le 10 ]; then ...
Note that when using [ ] correct quoting is essential, whereas with [[ ]] it is often superfluous; also, [ ] is missing some extensions such as pattern-matching and regular-expression operators.
It's complicated:
First, there are three separate ways of constructing your if statement. Each way has its own unique syntax on how to join two booleans. (Actually, there are four ways since one way allows you to use list operators).
A little background...
The if command is a compound command built into the shell. The if command executes the commands following the if. If that command returns a zero value, the if statement is considered true and the then clause executes. Otherwise, if it exists, the else clause will execute. Remember, the if is just a command. You can do things like this:
if ! mv "$foo" "$bar"
then
echo "I can't move $foo to $bar"
exit 2
fi
What we need is a command to do some testing for us. If the test succeeds, that test command returns an exit code of zero. If not, it returns a non-zero exit code. Then, it could be used with the if command!
The test command (Yes, there's really one!).
The [ is an alias for the test command which was created to allow you to test files, strings, and numbers for the if statement. (This is now a built in command in Bash, but its roots are actually part of /bin/test and /bin/[). These are the same:
if test "$foo" -eq "$bar"
then
...
fi
and
if [ "$foo" -eq "$bar" ]
then
...
fi
The test command (if you read the manpage has a -a And test and a -o Or test. You could have done:
if [ "$INPUT" -ge 1 -a "$INPUT" -le 10 ]
then
....
fi
This is a single test statement with three test parameters (-ge, -a, and -le).
Using List Operators
This isn't the only way to do a compound boolean test. The Bash shell has two list operators: && and ||. The list operators go in between two commands. If you use && and the left hand command returns a non-zero exit code, the right hand command is not executed, and the entire list returns the exit value of the left-hand command. If you use ||, and the left hand command succeeds, the right hand command is not executed, and the entire list returns a zero exit value. If the first command returns a non-zero exit value, the right-hand command is executed, and the entire list returns the exit value of the right-hand command.
That's why you can do things like this:
[ $bar -eq 0 ] || echo "Bar doesn't have a zero value"!
Since [ ... ] is just a command that returns a zero or non-zero value, we can use these list operators as part of our test:
if [ "$INPUT" -ge 1 ] && [ "$INPUT" -le 10 ]
then
...
fi
Note that this is two separate tests and are separated by a && list operator.
Bash's Special [[ compound command
In Kornshell, Zsh, and Bash, there are special compound commands for testing. These are the double square brackets. They appear to be just like the single square brackets command, but because they're compound commands, parsing is affected.
For example:
foo="This has white space"
bar="" #No value
if [ ! $foo = $bar ] # Doesn't work!
then
The shell expands $foo and $bar and the test will become:
if [ This has white space = ]
which just doesn't work. However,
if [[ $foo != $bar ]]
works fine because of special parsing rules. The double brackets allow you to use parentheses for grouping and && and || as boolean operators. Thus:
if [[ $INPUT -ge 1 && $INPUT -le 10 ]]
then
...
fi
Note that the && appears inside a single set of double square brackets. (Note there's no need for quotation marks)
Mathematical Boolean Expression
Bash has built in mathematical processing including mathematical boolean expressions. If you put something between double parentheses, Bash will evaluate it mathematically:
if (( $INPUT >= 1 && $INPUT <= 10 ))
then
...
fi
In this case, (( $INPUT >= 1 && $INPUT <= 10 )) is evaluated. If $INPUT is between 1 and 10 inclusively, the mathematical expression will evaluate as true (zero exit code), and thus the then clause will be executed.
So, you can:
Use the original test (single square brackets) command and use the -a to string together two boolean statements in a single test.
Use list operators to string together two separate test commands (single square brackets).
Use the newer compound test command (double square brackets) that now include && and || as boolean operators, so you have a single compound test.
Forget about test command and just use mathematical evaluation (double parentheses) to evaluate boolean expressions.
Test Constructs Can Vary by Shell
As has been mentioned in other posts, [[ is a Bash shell keyword that isn't present in the Bourne shell. You can see this from a Bash prompt with:
type '[['
[[ is a shell keyword
In a Bourne shell, you will instead get "command not found."
Be More Portable: Use the -a Test Operator
A more portable construct is to use the -a test operator to join conditions (see man test for details). For example:
if [ "$INPUT" -ge 1 -a "$INPUT" -le 10 ]; then
: # do something when both conditions are true
else
: # do something when either condition is false
fi
This will work in every Bourne-compatible shell I've ever used, and on any system that has a /bin/\[ executable.

Two while loops behaving strangely, Bash script

I'm new to Bash scripting. I have written a script to help me get some info using ssh from bunch of servers. the IP address of first set of devices are from 101 to 148, and the other set are from 201 to 210.
#!/bin/bash
BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START
while [[ $SD_counter -le $SD_END ]]
do
ip=$BASE.$SD_counter
ssh $ip command1
SD_counter=$(($SD_counter +1))
if [ "$SD_counter"==148 ]
then
while [[ $HD_counter -le $HD_END ]]
do
ip=$BASE.$HD_counter
ssh $ip command2
HD_counter=$(($HD_counter +1))
done
fi
done > log_SD_HD
echo "Done!"
But for some reason command1 is executed on 192.168.11.101 first, then command2 is executed on ip range 192.168.11.201-192.168.11.210 which is the second while loop.
After that the first while loop continues till the end.
Why is this happening? I want the first while loop to be done before the second while loop. Could someone please point out what I'm doing wrong?
#0x1cf's answer provides the right pointer:
[ "$SD_counter"==148 ] doesn't work as expected.
Specifically: "$SD_counter"==148, based on bash's string synthesizing rules, is expanded to a single string literal: the value of $SD_counter is concatenated with literal ==148, and the resulting string literal is treated as a Boolean.
Since a non-empty string in a Boolean context always evaluates to true, [ "$SD_counter"==148 ] always evaluates to true due to lack of spaces around the ==.
Aside from that: in bash you should use [[ ... ]] rather than [ ... ] - it is more robust and provides more features.
Also note (as #0x1cf notes too) that - if using [ ... ] or [[ ... ]] - using the arithmetic operators is the right choice when dealing with numbers: -eq, -ne, -lt, -le, -gt, or -ge.
Generally, though, using (( ... )) expressions - arithmetic evaluation - provides more flexibility with numbers - see below.
That said, your code can be greatly simplified by using arithmetic evaluation - (( ... )) (see section ARITHMETIC EVALUATION in man bash):
It allows you to use C-style arithmetic and Boolean expressions.
If we combine this with bash's array variables, your code can be simplified to:
#!/usr/bin/env bash
BASE=192.168.11
START_INDICES=( 101 201 )
END_INDICES=( 148 210 )
COMMANDS=( command1 command2 )
numRanges=${#START_INDICES[#]}
for (( range = 0; range < numRanges; ++range )); do
cmd=${COMMANDS[range]}
for (( i=${START_INDICES[range]}; i<=${END_INDICES[range]}; ++i )); do
ip=$BASE.$i
ssh $ip $cmd
done
done > log_SD_HD
Note:
(( ... )) expressions DIFFER from normal bash assignments and conditionals in that you:
need NOT reference variables with $
need NOT double-quote variable references
you MAY have spaces around the assignment operator (=)
you MAY omit spaces around relational operators: (( SD_counter==148 )) DOES work.
( string1 ... ) creates an array with elements string1, ...; ${#arrayVar[#]} returns the count of elements of array variable arrayVar; ${arrayVar[ndx]} returns the element with (0-based) index ndx.
It's better to avoid ALL-UPPERCASE variable names such as BASE, as they may conflict with environment variables, which are by convention typically all-uppercase.
UPDATE
Hint: You can always use #!/bin/bash -x to trace and debug your scripts.
Maybe using two while loop is a good idea, just as V_Maenolis showed. However, to answer your question about what's wrong with your script, try this
Replace
if [ "$SD_counter"==148 ]
with
if [ "$SD_counter" -gt 148 ]
which works for me.
So there are two errors
There should be a space before and after == operator, that is to say, using A == B NOT A==B
The logic of comparing SD_counter == 148 is incorrect. Because when SD_counter hits 148, your script will run into the second while loop, and you'll get 147, 201, ..., 210, 148. Using -gt instead avoids the problem.
There is no reason to nest the loops from what you showed:
#!/bin/bash
BASE=192.168.11
SD_START=101
SD_END=148
HD_START=201
HD_END=210
SD_counter=$SD_START
HD_counter=$HD_START
while [[ $SD_counter -le $SD_END ]]
do
ip=$BASE.$SD_counter
ssh $ip command1
SD_counter=$(($SD_counter +1))
done> log_SD_HD
while [[ $HD_counter -le $HD_END ]]
do
ip=$BASE.$HD_counter
ssh $ip command2
HD_counter=$(($HD_counter +1))
done>> log_SD_HD
echo "Done!"

Binary operator compatibility for shell

I can't help noticing there are so many shell codes using comparison operators as test command's arguments, not with signs.
For example, to test if no arguments are received in shell using test:
if test $# -eq 0
then
echo "No arguments received"
fi
Why can't we replace -eq with more, say, traditional, intuitive, familiar, universal, and, readable sign with ==? So that we have the following:
if test $# == 0
then
echo "No arguments received"
fi
The same goes with other comparison operators < <= => >.
I'm assuming there must be some technical reasons behind this (perhaps compatibility issue?) to favor -eq format over ==, but I'm not aware of them.
First of all, use $#, not $#.
The technical reason for the difference is that the operators define how the operands are interpretted.
For <, = and >, the operands are considered strings. This means that 10 < 2 because 1-something comes before 2-something in the alphabet, and 1 != 01 because the strings are of different lengths.
For -eq, -gt, -lt, the operands are considered integers. This means that 2 -lt 10 because the number 2 is smaller than the number 10, and 1 -eq 01 because these are both numerically equivalent ways of writing 1.
You can use (( and )) (arithmetic processor) in BASH that supports all the operators like ==, >=, <=, <, > etc:
if (( $# == 0 ))
then
echo "No arguments received"
fi
PS: # of parameters is represented by $#

bash, 2 clause if statement

if [[ $GreaterThanTwenty==1 && $LessThanThirty==1 ]]
then
echo "within limits"
else
echo "Outside limits"
fi
echo $GreaterThanTwenty
echo $LessThanThirty
Why is Within limits triggered even though the echoes show that at this point LessThanThirty is actually 0?
You should compare arithmetic values using one of these: -eq, -ne, -lt, -le, -gt or -ge, meaning equal, not equal, less than, less than or equal, greater than and greater than or equal, respectively.
Reference: http://www.ibm.com/developerworks/library/l-bash-test/index.html

Simple logical operators in Bash

I have a couple of variables and I want to check the following condition (written out in words, then my failed attempt at bash scripting):
if varA EQUALS 1 AND ( varB EQUALS "t1" OR varB EQUALS "t2" ) then
do something
done.
And in my failed attempt, I came up with:
if (($varA == 1)) && ( (($varB == "t1")) || (($varC == "t2")) );
then
scale=0.05
fi
What you've written actually almost works (it would work if all the variables were numbers), but it's not an idiomatic way at all.
(…) parentheses indicate a subshell. What's inside them isn't an expression like in many other languages. It's a list of commands (just like outside parentheses). These commands are executed in a separate subprocess, so any redirection, assignment, etc. performed inside the parentheses has no effect outside the parentheses.
With a leading dollar sign, $(…) is a command substitution: there is a command inside the parentheses, and the output from the command is used as part of the command line (after extra expansions unless the substitution is between double quotes, but that's another story).
{ … } braces are like parentheses in that they group commands, but they only influence parsing, not grouping. The program x=2; { x=4; }; echo $x prints 4, whereas x=2; (x=4); echo $x prints 2. (Also braces require spaces around them and a semicolon before closing, whereas parentheses don't. That's just a syntax quirk.)
With a leading dollar sign, ${VAR} is a parameter expansion, expanding to the value of a variable, with possible extra transformations.
((…)) double parentheses surround an arithmetic instruction, that is, a computation on integers, with a syntax resembling other programming languages. This syntax is mostly used for assignments and in conditionals.
The same syntax is used in arithmetic expressions $((…)), which expand to the integer value of the expression.
[[ … ]] double brackets surround conditional expressions. Conditional expressions are mostly built on operators such as -n $variable to test if a variable is empty and -e $file to test if a file exists. There are also string equality operators: "$string1" == "$string2" (beware that the right-hand side is a pattern, e.g. [[ $foo == a* ]] tests if $foo starts with a while [[ $foo == "a*" ]] tests if $foo is exactly a*), and the familiar !, && and || operators for negation, conjunction and disjunction as well as parentheses for grouping. Note that you need a space around each operator (e.g. [[ "$x" == "$y" ]], not [[ "$x"=="$y" ]]), and a space or a character like ; both inside and outside the brackets (e.g. [[ -n $foo ]], not [[-n $foo]]).
[ … ] single brackets are an alternate form of conditional expressions with more quirks (but older and more portable). Don't write any for now; start worrying about them when you find scripts that contain them.
This is the idiomatic way to write your test in bash:
if [[ $varA == 1 && ($varB == "t1" || $varC == "t2") ]]; then
If you need portability to other shells, this would be the way (note the additional quoting and the separate sets of brackets around each individual test, and the use of the traditional = operator rather than the ksh/bash/zsh == variant):
if [ "$varA" = 1 ] && { [ "$varB" = "t1" ] || [ "$varC" = "t2" ]; }; then
very close
if [[ $varA -eq 1 ]] && [[ $varB == 't1' || $varC == 't2' ]];
then
scale=0.05
fi
should work.
breaking it down
[[ $varA -eq 1 ]]
is an integer comparison
where as
$varB == 't1'
is a string comparison.
otherwise, I am just grouping the comparisons correctly.
Double square brackets delimit a Conditional Expression. And, I find the following to be a good reading on the subject: "(IBM) Demystify test, [, [[, ((, and if-then-else"
A very portable version (even to legacy bourne shell):
if [ "$varA" = 1 -a \( "$varB" = "t1" -o "$varB" = "t2" \) ]
then do-something
fi
This has the additional quality of running only one subprocess at most (which is the process [), whatever the shell flavor.
Replace = with -eq if variables contain numeric values, e.g.
3 -eq 03 is true, but
3 = 03 is false. (string comparison)
Here is the code for the short version of if-then-else statement:
( [ $a -eq 1 ] || [ $b -eq 2 ] ) && echo "ok" || echo "nok"
Pay attention to the following:
|| and && operands inside if condition (i.e. between round parentheses) are logical operands (or/and)
|| and && operands outside if condition mean then/else
Practically the statement says:
if (a=1 or b=2) then "ok" else "nok"
if ([ $NUM1 == 1 ] || [ $NUM2 == 1 ]) && [ -z "$STR" ]
then
echo STR is empty but should have a value.
fi

Resources