Bash value between two integers, NOT floats - linux

I have this simple comparison operator, it check if number is between 1 and 3 (1 and 3 included too). But if I assign number to 3,2 it still accepts as correct.
It should only accept those values 1-2-3
My code
if (("$number" >= 1 && "$number" <= 3)); then
echo 'Correct number'
fi

(("$number" >= 1 && "$number" <= 3)) is a Bash's stand-alone arithmetic expression.
Within an arithmetic expression, variables are expanded as-is and if they contain valid arithmetic expression's syntax elements, these are also interpreted.
number="3,2" expands as ((3,2)) in an arithmetic expression, where comma , is interpreted as a statements separator by Bash's arithmetic expressions.
Lets see:
$ number="3,2"; echo "$((number))"
2
$ number="3+2"; echo "$((number))"
5
$ number="3*2"; echo "$((number))"
6
The shell only understands integer arithmetic, but 3,2 is not a valid integer value.
It means that if you are unsure a variable contains a valid integer, it is unsafe for use within an arithmetic expression.
Always check that number contains a valid integer (see: Test whether string is a valid integer) before using in an arithmetic expression or within a test comparing integers.
# Check number is a valid integer before testing its range.
[[ $number =~ ^[-+]?[0-9]+$ ]] && (("$number" >= 1 && "$number" <= 3))
The other numeric test method [ "$number" -ge 1 ] && [ "$number" -le 3 ] as suggested in the other answers will error-out with: bash: [: 3,2: integer expression expected.
It also needs testing for valid integer:
[[ $number =~ ^[-+]?[0-9]+$ ]] && [ "$number" -ge 1 ] && [ "$number" -le 3 ]
With POSIX shell, there is no Regex test, so a different approach is needed:
case "${number#[+-]}" in(*[!0123456789]*|'')false;;esac &&
[ "$number" -ge 1 ] && [ "$number" -le 3 ]
An additional note about the pitfalls of using arithmetic expressions to compare numbers. Arithmetic expressions will handle leading 0 of an integer, as an octal:
$ (( 13 >= 12 )); echo $?
0
but
$ (( 013 >= 12 )); echo $?
1

Try this:
if [ "$number" -ge 1 ] && [ "$number" -le 3 ]; then
echo 'Correct number'
fi

You have to rewrite your code as follow:
if [ "$number" -ge 1 ] && [ "$number" -le 3 ]; then
echo 'Correct number'
fi
Look at https://tldp.org/LDP/abs/html/comparison-ops.html for further information.

Related

Why "[ 1 > 2 ]" evaluates to True?

I have two files:
f1.txt:
1
dest/f1.txt:
1
2
When I run wc -l on both of those files in linux terminal - I get my expected results:
$ wc -l < f1.txt
$ 1
$ wc -l < dest/f1.txt
$ 2
But when I run the following .sh file:
#!/bin/bash
if [ $(wc -l < f1.txt) > $(wc -l < dest/f1.txt) ]; then
echo -e "f1 has more lines"
else
echo -e "f1 doesn't have more lines"
fi
The output is:
f1 has more lines
Can you explian how could this be possible?
You should use the -gt for integer comparison in a if clause.
If you use > or < you will end up doing ASCII alphabetic order comparison.
integer comparison
-eq
is equal to
if [ "$a" -eq "$b" ]
-ne
is not equal to
if [ "$a" -ne "$b" ]
-gt
is greater than
if [ "$a" -gt "$b" ]
-ge
is greater than or equal to
if [ "$a" -ge "$b" ]
-lt
is less than
if [ "$a" -lt "$b" ]
-le
is less than or equal to
if [ "$a" -le "$b" ]
<
is less than (within double parentheses)
(("$a" < "$b"))
<=
is less than or equal to (within double parentheses)
(("$a" <= "$b"))
>
is greater than (within double parentheses)
(("$a" > "$b"))
>=
is greater than or equal to (within double parentheses)
(("$a" >= "$b"))
string comparison
=
is equal to
if [ "$a" = "$b" ]
Caution
Note the whitespace framing the =.
if [ "$a"="$b" ] is not equivalent to the above.
==
is equal to
if [ "$a" == "$b" ]
This is a synonym for =.
Note
The == comparison operator behaves differently within a double-brackets test than within single brackets.
[[ $a == z* ]] # True if $a starts with an "z" (pattern matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).
[ $a == z* ] # File globbing and word splitting take place.
[ "$a" == "z*" ] # True if $a is equal to z* (literal matching).
!=
is not equal to
if [ "$a" != "$b" ]
This operator uses pattern matching within a [[ ... ]] construct.
<
is less than, in ASCII alphabetical order
if [[ "$a" < "$b" ]]
if [ "$a" \< "$b" ]
Note that the "<" needs to be escaped within a [ ] construct.
>
is greater than, in ASCII alphabetical order
if [[ "$a" > "$b" ]]
if [ "$a" \> "$b" ]
Note that the ">" needs to be escaped within a [ ] construct.
-z
string is null, that is, has zero length
String='' # Zero-length ("null") string variable.
if [ -z "$String" ]
then
echo "\$String is null."
else
echo "\$String is NOT null."
fi # $String is null.
-n
string is not null.
Source: http://tldp.org/LDP/abs/html/comparison-ops.html
[ is also a command in Bash so [ 1 > 2 ] is the same as [ 1 ] > 2 which would succeed and create a file named 2.
As others pointed out you need to use the following syntax to compare integers:
[ 1 -gt 2 ]
[[ 1 -gt 2 ]]
(( 1 > 2 ))
Try with this code:
#!/bin/bash
if [ $(wc -l < f1.txt) -gt $(wc -l < dest/f1.txt) ]; then
echo -e "f1 has more lines"
else
echo -e "f1 doesn't have more lines"
fi
The -gt will proceed with a numeric compare and not ASCII.

Read string and convert to INT (BASH)

I have a simple script in Bash to read a number in a file and then compare it with a different threshold. The output is this:
: integer expression expected
: integer expression expected
OK: 3
My code is this:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(cat /tmp/wget.txt | awk 'NR==6')
#output=7
echo $output
if [ $output -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ $output -ge 6 ] && [ $output -lt 11 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
rm /tmp/wget.txt
I know what is the problem, I know that I'm reading a string and I try to compare a int. But I don't know how can I do to read this file and convert the number to read in a int var..
Any ideas?
The problem occurs when $output is the empty string; whether or not you quote the expansion (and you should), you'll get the integer expression required error. You need to handle the empty string explictly, with a default value of zero (or whatever default makes sense).
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' < /tmp/get.txt)
output=${output:-0}
if [ "$output" -ge 11 ];then
echo "CRITICAL: $output"
exit 2
elif [ "$output" -ge 6 ];then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi
(If you reach the elif, you already know the value of $output is less than 11; there's no need to check again.)
The problem also occurs, and is consistent with the error message, if output ends with a carriage return. You can remove that with
output=${output%$'\r'}
There are a couple of suggestions from my side regarding your code.
You could explicitly tell bash the output is an integer
declare -i output # See [1]
Change
output=$(cat /tmp/wget.txt | awk 'NR==6') # See [2]
may be better written as
output=$(awk 'NR==6' /tmp/wget.txt )
Change
if [ $output -ge 11 ]
to
if [ "0$output" -ge 11 ] # See [4]
or
if (( output >= 11 )) # Better See [3]
References
Check bash [ declare ].
Useless use of cat. Check [ this ]
Quoting [ this ] answer :
((...)) enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability. Also empty variable automatically defaults to 0 in such a statement.
The zero in the beginning of "0$output" help you deal with empty $output
Interesting
Useless use of cat is a phrase that has been resounding in SO for long. Check [ this ]
[ #chepner ] has dealt with the empty output fiasco using [ bash parameter expansion ] in his [ answer ], worth having a look at.
A simplified script:
#!/bin/bash
wget=$(wget http://10.228.28.8/ -O /tmp/wget.txt 2>/dev/null)
output=$(awk 'NR==6' </tmp/wget.txt )
output="$(( 10#${output//[^0-9]} + 0 ))"
(( output >= 11 )) && { echo "CRITICAL: $output"; exit 2; }
(( output >= 6 )) && { echo "WARNING: $output"; exit 1; }
echo "OK: $output"
The key line to cleanup any input is:
output="$(( 10#${output//[^0-9]} + 0 ))"
${output//[^0-9]} Will leave only digits from 0 to 9 (will remove all non-numeric chars).
10#${output//[^0-9]} Will convert output to a base 10 number.
That will correctly convert numbers like 0019
"$(( 10#${output//[^0-9]} + 0 ))" Will produce a zero for a missing value.
Then the resulting number stored in output will be compared to limits and the corresponding output will be printed.
In BASH, It is a good idea to use double brackets for strings:
if [[ testing strings ]]; then
<whatever>
else
<whatever>
fi
Or double parenthesis for integers:
if (( testing ints )); then
<whatever>
else
<whatever>
fi
For example try this:
var1="foo bar"
if [ $var1 == 'foo bar' ]; then
echo "ok"
fi
Result:
$ bash: [: too many arguments
Now, this:
var2="foo bar"
if [[ $a == "foo bar" ]]; then
echo "ok"
fi
Result:
ok
For that, your code in BASH:
if [[ $output -ge 11 ]]; then
echo "CRITICAL: $output"
exit 2
elif [[ $output -ge 6 ]]; then
echo "WARNING: $output"
exit 1
else
echo "OK: $output"
exit 0
fi

Bash: Why does my If-Statement always evaluate to true?

I'm trying to write what would seem to be a simple if statement in most languages, however in bash this doesnt seem to work at all.
When I run the script it always enters the first if statement. Can anyone offer help me as to what I am doing wrong?
PERC=.5
if [ "$PERC" > "1.00" ]
then
echo "Entered first statement"
else
if [ "$PERC" < "1.00" ]
then
echo "Entered second statement"
fi
fi
Thanks for your help.
> and < compare strings, not numbers (and must be backslashed or quoted in single [...]). Use -gt, -lt etc. to compare numbers, or use arithmetic conditions:
if (( a < b || b <= c )) ; then
Note, however, that bash only handles integer arithmetics. To compare floats, you can use bc:
if [[ 1 == $( bc <<< '1.5 < 1.00' ) ]] ; then
> and < are the I/O redirection operators. So
if [ "$PERC" > "1.0" ]
is executing the command [ "$PERC ], redirecting the output to the file 1.0, and then if is testing whether the command succeeded. [ "$PERC" ] simply tests whether "$PERC" is a non-empty string.
To use them as operators in the test command, you need to quote or escape them:
if [ "$PERC" '>' "1.0" ]
You could also use bash's [[ conditional syntax instead of the [ command:
if [[ $PERC > "1.0" ]]

Unexpected output in bash shell script

For the below script I am expecting the output to be msg_y and msg_z. But it is printing msg_x and msg_z. Can somebody explain to me what is going on?
#!/bin/bash
set -x
vr=2
echo $vr
if [ $vr > 5 ]
then
echo "entered 1st if"
echo "msg_x"
echo "out of 1st if"
if [ $vr < 8 ]; then
echo "in of 2nd if"
echo "msg_y"
else
echo "msg_z"
fi
else
if [ $vr > 1 ]; then echo "msg_y"
else echo "msg_z"
fi
fi
This expression
[ $vr > 5 ]
is being parsed as an output redirection; check to see if you have a file named "5" now. The output redirection is vacuously true. Note that the usual admonition to quote parameters inside a test expression would not help here (but it's still a good idea).
You can escape the > so that it is seen as an operator in the test command:
if [ "$vr" \> 5 ]; then
or you can use the integer comparison operator -gt
if [ "$vr" -gt 5 ]; then.
Since you are using bash, you can use the more robust
conditional expression
if [[ $vr > 5 ]]; then
or
if [[ $vr -gt 5 ]]; then
or use an arithmetic expression
if (( vr > 5 )); then
to do your comparisions (likewise for the others).
Note: although I showed how to make > work as a comparison operator even when surrounded by integers, don't do this. Most of the time, you won't get the results you want, since the arguments are compared lexicographically, not numerically. Try [ 2 \> 10 ] && echo What? Either use the correct integer comparison operators (-gt et al.) or use an arithmetic expression.

How do I play with test command properly?

[root#jiaoyou ~]# test 1 = 1 -a 0 = 1
[root#jiaoyou ~]# if [1 = 1 -a 0 = 1]then echo 1;else echo 2;fi
-bash: syntax error near unexpected token `else'
Why test doesn't give any output and the if statement fails?
Can someone point out what's wrong here?
UPDATE
Can someone illustrate how to simulate complex expressions like 1=1 and (0=1 or 1=1) with [[?
Return codes are not printed; you must echo the value of $? in order to see it.
[ is a command. Just as you don't write echoHelloworld, you can't write [1 ....
There are several equivalent ways of doing the test you're looking for. First, essentially the way you're doing it, but with the syntax fixed (added required spaces around the parameters to the [ (aka test) command, and a ; between ] and then):
if [ 1 = 1 -a 0 = 1 ];then echo 1;else echo 2;fi
Here's a version with explicit grouping in the expression; note that the parentheses have to be escaped because they're special characters in the shell, but in this case we want them passed to the [ command as arguments:
if [ \( 1 = 1 \) -a \( 0 = 1 \) ];then echo 1;else echo 2;fi
Another version with explicit grouping, this time using two separate [ commands connected with bash's && operator (note that this can also connect other commands):
if [ 1 = 1 ] && [ 0 = 1 ];then echo 1;else echo 2;fi
And finally, a couple of examples using bash's [[ operator instead of the [ command; note that [[ isn't a command, so its expression isn't parsed as command arguments, allowing it a more intuitive syntax (e.g. allowing && and || as operators, and parentheses and !<> comparisons without escaping):
if [[ 1 = 1 && 0 = 1 ]];then echo 1;else echo 2;fi
if [[ ( 1 = 1 ) && ( 0 = 1 ) ]];then echo 1;else echo 2;fi
Some more notes/warnings: the = operator being used in all of these examples does string comparisons rather than numeric comparison (i.e. [ 1 = 01 ] is false); if you want numeric comparison use -eq instead (similarly, < and > are string comparisons, while -lt and -gt are numeric). Also, if you're comparing strings that include spaces, '[' can mistake the string for part of the expression under some circumstances (esp. if you don't have the strings wrapped in double-quotes); as far as I know [[ never has this problem. Overall, if you're using shells that support [[, it's much easier to deal with than [.
As #IgnacioVazquez-Abrams correctly states, test just sets the error code.
That said, here's a nice bash-ism for quickly checking the error value:
test [ expr ] && echo SUCCESS || echo FAIL
Due to how && and || short circuit, that will output SUCCESS or FAIL depending on whether test returns 0 or non-0.

Resources