I have a small piece of code which checks IP address validity :
function valid_ip()
{
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]; then
stat=1
else
stat=0
fi
fi
return $stat
}
But I am having problems with its usage in bash conditionals. I have tried many techniques to test its return value but most of them fail on me.
if [[ !$(valid_ip $IP) ]]; then
if [[ $(valid_ip IP) -eq 1 ]]; then
etc. etc. Can anyone suggest what should I do here ?
EDIT
Following your suggestions I have used something like :
if valid_ip "$IP" ; then
... do stuff
else
perr "IP: \"$IP\" is not a valid IP address"
fi
and I get errors like
IP: "10.9.205.228" is not a valid IP address
The return code is available in the special parameter $? after the command exits. Typically, you only need to use it when you want to save its value before running another command:
valid_ip "$IP1"
status1=$?
valid_ip "$IP2"
if [ $status1 -eq 0 ] || [ $? -eq 0 ]; then
or if you need to distinguish between various non-zero statuses:
valid_ip "$IP"
case $? in
1) echo valid_IP failed because of foo ;;
2) echo valid_IP failed because of bar ;;
0) echo Success ;;
esac
Otherwise, you let the various operators check it implicitly:
if valid_ip "$IP"; then
echo "OK"
fi
valid_IP "$IP" && echo "OK"
Here is a simple, idiomatic way of writing valid_ip:
valid_ip () {
local ip=$1
[[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && {
IFS='.' read a b c d <<< "$ip"
(( a < 255 && b < 255 && c < 255 && d << 255 ))
}
}
There are two expressions, the [[...]] and the { ... }; the two are joined by &&. If the first fails, then valid_ip fails. If it suceeds, then the second expression (the compound statement) is evaluated. The read splits the string into four variables, and each is tested separately inside the arithmetic expression. If all are true, then the ((...)) succeeds, which means the && list succeeds, which means that valid_ip succeeds. No need to store or return explicit return codes.
No parentheses needed if the exit status is inspected:
if valid_ip $IP ; then
...
Just call the function in the way you would call any other command.
Related
I want to invoke a function with some input checks (the input should be an integer between 1-21). If ok, then do the echo "invoke", else just print a message about invalid input.
I tried with the following simplified example, it works for the invalid case, but does not invoke for the valid case. what is wrong?
function _check_num ()
{
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ] || echo "input should be (1-21)" && return 1 // one-liner
}
function _call()
{
_check_num $1 && echo "invoke only if input is 1-21" // does not invoke given valid input
}
Note: please explain me the root cause of this one-liner case.
Do not use chains of && and || as a replacement for an if statement.
&& and || have equal precedence, so a && b || c runs c if either a or b fail; it is not equivalent to if a; then b; else c; fi.
a && b || c && d is parsed as ((a && b) ||c) && d, not (a && b) || (c && d).
Use an expicit if statement to make your code readable. (Also, don't use -a inside [...]; it is considered ambiguous and obsolete.)
function _check_num ()
{
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 21 ]; then
return 0
else
echo "input should be (1-21)" >&2
return 1
fi
}
The less readable version would be something like the following, uses braces to properly group the commands.
function _check_num ()
{
{
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 21 ]
} || {
echo "input should be (1-21)" >&2 && return 1
}
}
function _check_num ()
{
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ]; then
return 0
else
echo "input should be (1-21)"
return 1
fi
}
I thin you missed your return 0 statement
See chepner answer: problem is priority of && / || operators
Normally the return status of the first part should be 0 but idk something could go wrong.
You can use group last echo and return in {...} to make it work:
function _check_num () {
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 -a "$1" -le 21 ] ||
{ echo "input should be (1-21)" && return 1; }
}
Without {...} last return 1 is always returning 1 whether value is valid or invalid.
If you put your "one liner" inside a script to test it:
function _check_num () {
[[ "$1" =~ ^[0-9]+$ ]] &&
[ "$1" -ge 1 -a "$1" -le 21 ] ||
echo "input should be (1-21)" &&
return 1 # one-liner
}
_check_num "$1"; echo "exit value $?"
we get:
$ ./script.sh qwe
input should be (1-21)
exit value = 1
$ ./script.sh 112
input should be (1-21)
exit value = 1
$ ./script.sh 12
exit value = 1
As you can see, the exit value is always 1 (not a successful result).
Therefore you can not use the exit value as the trigger for other code.
Where?
The core issue is in this structure:
[ "$1" -ge 1 -a "$1" -le 21 ] || echo "input should be (1-21)" && return 1
which could be reduced to:
[ … ] || echo && return 1
The two possible exit values (success or not) of the […] could be tested with:
f(){ true || echo && return 1; }; f; echo "$?" # prints a 1.
f(){ false || echo && return 1; }; f; echo "$?" # *also* a 1[1].
[1] After also printing a blank line from the internal echo.
Why ?
Because of the "short circuit" effect of the shell «AND and OR lists»
From bash manual:
Lists
Of these list operators, && and || have equal precedence …
The return status is the exit status of the last command executed.
AND and OR lists are sequences of one of more pipelines separated by the && and || control operators, respectively.
What is crucial to understand the issue is this part (for AND):
An AND list has the form
command1 && command2
command2 is executed if, and only if, command1 returns an exit status of zero.
And for OR
An OR list has the form
command1 || command2
command2 is executed if and only if command1 returns a non-zero exit status.
And the natural understanding of which should be the present exit status at any position:
The return status of AND and OR lists is the exit status of the last command executed in the list.
If you split any AND and OR list at any position, the exit status at such position is the exit status of the last previous command executed.
Some commands may get bypassed.
Step by step:
The first command is a test [[…]], the exit value at this point is its exit value.
This first command is connected to the next with an OR (||).
If the exit value of the first command is fail (not 0) the next command (echo) will be executed.
The exit code of echo is always success.
The next connection is an AND (&&).
At that point the exit value is true, the next command will be executed.
If the exit value of the first command is success (0) the next command will not be executed.
But the next one (return 1) will because the connection is an AND.
In both cases the return 1 is the last command executed.
The return value is always 1.
Precedence
Precedence may affect the order of the commands executed, but that does not explain why some commands are not executed.
Precedence in an "AND and OR list" is the same for && and ||.
So, operators will be considered in the left to right order they are found.
Associativity
Associativity in a shell "AND and OR list" is "left-associative".
That means that: if there are no parenthesis, operations are grouped from the left.
The first command is grouped with the second.
The result of that is grouped with the third, etc.
But even grouped as explained here. That does not explain why some commands are bypassed.
References
Advanced Bash-Scripting Guide: Chapter 26. List Constructs
Precedence
Operator associativity
Maybe you should consider using a function like:
function _check_num () {
declare -i num
if [[ $1 =~ ^[+]?(0+)?([0-9]+)$ ]]; then
num=${BASH_REMATCH[2]}
if ! (( num >=1 && num <= 21 )); then
echo "input should be (1-21)"
return 1
fi
return 0
else
echo "input should be a number"
return 2
fi
}
COUNTER=0
let COUNTER=COUNTER+1
count=`ssh -i /var/www/.ssh/id_rsa_root -o stricthostkeychecking=no $host $cmd`
count1=`echo $count | awk '{print $4}'`
printf "count1 : $count1\n"
result1=${count1/.*}
if [ "$result1" -ge "0" ]; then
echo $host
else
echo $host
exit
fi
If the value of $result1 is INTEGER and greater than zero, it'll goto IF loop (works fine for me)
But when it is not INTEGER, it is coming to else loop (which it is suppose to do) with the following error in the Output
line 55: [: : integer expression expected
but i dont want the above error in my output. I tried to use 2>/dev/null with this but no luck.
please help!
If you want to handle an empty result gracefully, check for it explicitly:
if [ -z "$result1" ]; then
: "ignoring empty string"
elif [ "$result1" -ge 0 ]; then
printf '%s\n' "$host"
else
printf '%s\n' "$host"
exit
fi
You could also check if result1 is a valid integer before making arithmetic comparisons:
function isNumber () {
[[ $1 =~ ^-?[0-9]+$ ]]
}
if ! isNumber "$result1"; then
echo "not a number"
elif [ "$result1" -ge "0" ]; then
echo "null or positive"
else
echo "negative"
fi
Change if [ "$result1" -ge "0" ]; then to
if (( result1 >= 0 )); then
This syntax won't throw any errors if result1 isn't defined (or empty) or happen to be a string somehow.
I am attempting to run a block of code if one flag is set to true and the other is set to false. ie
var1=true
var2=false
if [[ $var1 && ! $var2 ]]; then var2="something"; fi
Since that did not evaluate the way that I expected I wrote several other test cases and I am having a hard time understanding how they are being evaluated.
aa=true
bb=false
cc="python"
if [[ "$aa" ]]; then echo "Test0" ; fi
if [[ "$bb" ]]; then echo "Test0.1" ; fi
if [[ !"$aa" ]]; then echo "Test0.2" ; fi
if [[ ! "$aa" ]]; then echo "Test0.3" ; fi
if [[ "$aa" && ! "$bb" ]]; then echo "Test1" ; fi
if [[ "$aa" && ! "$aa" ]]; then echo "Test2" ; fi
if [[ "$aa" ]] && ! [[ "$bb" ]]; then echo "test3" ; fi
if [[ "$aa" ]] && ! [[ "$cc" ]]; then echo "test4" ; fi
if [[ $aa && ! $bb ]]; then echo "Test5" ; fi
if [[ $aa && ! $aa ]]; then echo "Test6" ; fi
if [[ $aa ]] && ! [[ $bb ]]; then echo "test7" ; fi
if [[ $aa ]] && ! [[ $cc ]]; then echo "test8" ; fi
When I run the preceding codeblock the only output I get is
Test0
Test0.1
Test0.2
however, my expectation is that I would get
Test0
Test1
Test3
Test5
Test7
I have tried to understand the best way to run similar tests, however most examples I have found are set up in the format of
if [[ "$aa" == true ]];
which is not quite what I want to do. So my question is what is the best way to make comparisons like this, and why do several of the test cases that I would expect to pass simply not?
Thank you!
Without any operators, [[ only checks if the variable is empty. If it is, then it is considered false, otherwise it is considered true. The contents of the variables do not matter.
Your understanding of booleans in shell context is incorrect.
var1=true
var2=false
Both the above variables are true since those are non-empty strings.
You could instead make use of arithmetic context:
$ a=1
$ b=0
$ ((a==1 && b==0)) && echo y
y
$ ((a==0 && b==0)) && echo y
$
$ ((a && !(b))) && echo y; # This seems to be analogous to what you were attempting
y
The shell does not have Boolean variables, per se. However, there are commands named true and false whose exit statuses are 0 and 1, respectively, and so can be used similarly to Boolean values.
var1=true
var2=false
if $var1 && ! $var2; then var2="something"; fi
The difference is that instead of testing if var1 is set to a true value, you expand it to the name of a command, which runs and succeeds. Likewise, var2 is expanded to a command name which runs and fails, but because it is prefixed with ! the exit status is inverted to indicate success.
(Note that unlike most programming languages, an exit status of 0 indicates success because while most commands have 1 way to succeed, there are many different ways they could fail, so different non-zero values can be assigned different meanings.)
true and false are evaluated as strings ;)
[[ $var ]] is an equivalent of [[ -n $var ]] that check if $var is empty or not.
Then, no need to quote your variables inside [[. See this reminder.
Finally, here is an explication of the difference between && inside brackets and outside.
The closest you can come seems to be use functions instead of variables because you can use their return status in conditionals.
$ var1() { return 0; }
$ var2() { return 1; } # !0 = failure ~ false
and we can test this way
$ var1 && echo "it's true" || echo "it's false"
it's true
$ var2 && echo "it's true" || echo "it's false"
it's false
or this way
$ if var1; then echo "it's true"; else echo "it's false"; fi
it's true
$ if var2; then echo "it's true"; else echo "it's false"; fi
it's false
Hope this helps.
I have a small piece of code which checks IP address validity :
function valid_ip()
{
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]; then
stat=1
else
stat=0
fi
fi
return $stat
}
But I am having problems with its usage in bash conditionals. I have tried many techniques to test its return value but most of them fail on me.
if [[ !$(valid_ip $IP) ]]; then
if [[ $(valid_ip IP) -eq 1 ]]; then
etc. etc. Can anyone suggest what should I do here ?
EDIT
Following your suggestions I have used something like :
if valid_ip "$IP" ; then
... do stuff
else
perr "IP: \"$IP\" is not a valid IP address"
fi
and I get errors like
IP: "10.9.205.228" is not a valid IP address
The return code is available in the special parameter $? after the command exits. Typically, you only need to use it when you want to save its value before running another command:
valid_ip "$IP1"
status1=$?
valid_ip "$IP2"
if [ $status1 -eq 0 ] || [ $? -eq 0 ]; then
or if you need to distinguish between various non-zero statuses:
valid_ip "$IP"
case $? in
1) echo valid_IP failed because of foo ;;
2) echo valid_IP failed because of bar ;;
0) echo Success ;;
esac
Otherwise, you let the various operators check it implicitly:
if valid_ip "$IP"; then
echo "OK"
fi
valid_IP "$IP" && echo "OK"
Here is a simple, idiomatic way of writing valid_ip:
valid_ip () {
local ip=$1
[[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] && {
IFS='.' read a b c d <<< "$ip"
(( a < 255 && b < 255 && c < 255 && d << 255 ))
}
}
There are two expressions, the [[...]] and the { ... }; the two are joined by &&. If the first fails, then valid_ip fails. If it suceeds, then the second expression (the compound statement) is evaluated. The read splits the string into four variables, and each is tested separately inside the arithmetic expression. If all are true, then the ((...)) succeeds, which means the && list succeeds, which means that valid_ip succeeds. No need to store or return explicit return codes.
No parentheses needed if the exit status is inspected:
if valid_ip $IP ; then
...
Just call the function in the way you would call any other command.
Is it possible to override Bash's test builtin? So that
[[ $1 = 'a' ]]
not just does the test but also outputs which result was expected when it fails? Something like
echo "Expected $1 to be a.'
EDIT
I know this is bad :-).
The test expression compound command does real short-circuiting that affects all expansions.
$ set -x
$ [[ 0 -gt x=1+1 || ++x -eq $(tee /dev/fd/3 <<<$x) && $(echo 'nope' >&3) ]] 3>&1
+ [[ 0 -gt x=1+1 ]]
++ tee /dev/fd/2
2
+ [[ ++x -eq 2 ]]
So yes you could do anything in a single test expression. In reality it's quite rare to have a test produce a side-effect, and almost never used to produce output.
Also yes, reserved words can be overridden. Bash is more lenient with ksh-style function definitions than POSIX style (which still allows some invalid names).
function [[ { [ "${#:1:${##}-1}" ]; }; \[[ -a -o -a -o -a ]] || echo lulz
Yet another forky bomb.
if function function if function if if \function & then \if & fi && \if & then \function & fi && then \function fi
Something like this?
if [[ $1 == 'a' ]]; then
echo "all right";
else
echo 'Expected $1 to be "a"'
fi
Anyway, what's the point of the test if you only expect one answer? Or do you mean that for debugging purposes?
[[ 'a' = 'a' ]] || echo "failed"
[[ 'b' = 'a' ]] || echo "failed"
failed