Why is the value of "$?" changed in the "if" condition? - linux

I'm confused by the the value of "$?" in my shell script. I wrote a demo script as follows:
#!/bin/bash
echo "good"
if [ $? -ne 0 ];then
echo "$? not equal 0"
else
echo "$? equal 0"
fi
echo "good"
ret=$?
if [ $ret -ne 0 ];then
echo "$ret not equal 0"
else
echo "$ret equal 0"
fi
I know,"$?" is used to save the return value of the last function or command, but why is the output as below?
good
1 equal 0
good
0 equal 0
Does it mean that [ $? -eq 0 ] is also a command or function? I'm puzzled!

Your original code could be rewritten as:
$ if echo "good"; then echo "$? equal 0"; fi
good
0 equal 0
Since your code is only checking the exit value of the echo command, it's almost always going to return 0 (e.g. "true") unless its file descriptor is closed. For example:
$ echo foo; echo $?
foo
0
$ echo bar >&- ; echo $?
-bash: echo: write error: Bad file descriptor
1

Related

Linux Bash, Test command, why [ 0 -ne 0 ] is false instead of true?

When using test command in Linux Bash and numeric comparison between "Zero" equal to "zero" fetches an exit code 0 through echo $?
$[ 0 -eq 0 ]
$echo $?
0
However, when testing the same with an NOT EQUAL, why my exit code shows false and exit with value 1?
$[ 0 -ne 0 ]
$echo $?
1
man Test
INTEGER1 -ne INTEGER2
INTEGER1 is not equal to INTEGER2
Could someone explain the logic behind the not equal to when equating with a same integer?
In bash, 0 (which really means no error) means true, non-zero (such as 1) means there was some error, AKA false.
Try this:
if [ 0 -ne 0 ]
then
echo The above is not true
else
echo The above is true
fi
Or, in one-liner form:
if [ 0 -ne 0 ]; then echo true; else echo false; fi
Convention is that an exit code of zero means success and non-zero means error/failure. Consider:
$ true
$ echo $?
0
$ false
$ echo $?
1
$ if true; then echo ok; else echo no; fi
ok
$ if false; then echo ok; else echo no; fi
no
$ if [ 0 -ne 0 ]; then echo ok; else echo no; fi
no

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

Rebuild shell script to case

I want to rebuild my script a little bit to make it more easier for other people.
I think it will be more easier with case and functions.
IP="192.168.123." #$1 is the last number for the ip-address
regExp="^[0-9]+[-,0-9]*$"
if [ "$#" -eq 0 ]; then
echo "No numbers given "
exit 0
fi
if [ "$1" == "-h" ]; then
echo "Give numbers to test"
exit 0
fi
I want to make something like this:
if [ "$#" -eq 0 ]; then
echo "No numbers given "
# exit 0 --> do I have to write this?
case
-h ) echo "Give numbers to test";
esac
fi
Do I have to write that exit?
Are there things to make it easier?
If all your statements are inside the case then you don't need the exit
case "$1" in
'') echo No numbers given;;
-h) echo Give numbers to test;;
*) ...
esac

Bash script unexpected result for while loop

I have following bash script to stop Apache server. I am not getting error but an unexpected result.
#!/bin/bash
/etc/init.d/httpd stop
if [[ $? -ne 0 ]]
then
exit 1
fi
RTN=10
while [[ $RTN -gt 0 ]]
echo "Before" $RTN
do
sleep 5
RTN=`ps -C httpd | wc -l`
RTN=$(( RTN - 1 ))
echo "After" $RTN
done
exit 0
I got the following answer which I do not expect to be, with an infinite printing:
Before 10
After 0
Before 0
After 0
Before 0
After 0
I expect to print:
Before 10
After -1
#and exit here
Could anybody please tell me what is happening?
This doesn't work the way you seem to think it does:
while [[ $RTN -gt 0 ]]
echo "Before" $RTN
do
You want the echo to come after the do. With it before the do, it's part of the list-1 condition rather than the list-2 body. And, as per the bash docs (my bold):
The while command continuously executes the list list-2 as long as the last command in the list list-1 returns an exit status of zero.
You can see the difference between the following script, similar to yours:
#!/usr/bin/bash
RTN=2
while [[ $RTN -gt 0 ]]
echo "Before" $RTN
do
sleep 1
RTN=$(( RTN - 1 ))
echo "After" $RTN
done
which outputs (ad infinitum):
Before 2
After 1
Before 1
After 0
Before 0
After -1
Before -1
After -2
Before -2
When you move the echo to inside the body:
#!/usr/bin/bash
RTN=2
while [[ $RTN -gt 0 ]]
do
echo "Before" $RTN
sleep 1
RTN=$(( RTN - 1 ))
echo "After" $RTN
done
it then terminates properly:
Before 2
After 1
Before 1
After 0
<returns to prompt>
Once that change is made, the loop terminates correctly, given the values being generated by your ps command.
Additionally, if you want to find out what's in the process list (and probably causing an result of zero rather than negative one), output the process list before checking the count:
:
ps -C httpd # Add this line temporarily.
RTN=`ps -C httpd | wc -l`
:
Your ps -C httpd command always returns a line: PID TTY TIME CMD (basically the heading of the ps output) which is counted by wc -l as 1 when there is no process running. If httpd was running the line count would be greater than 1.
Thus $((RTN -1)) becomes 0.
Edit:
I notice, you have an error in your while loop:
while [[ $RTN -gt 0 ]]
echo "Before" $RTN
do
Change it to:
while [[ $RTN -gt 0 ]]
do
echo "Before" $RTN

How to do string comparison properly in shell script?

I am executing my url through shell script and storing the response in a variable.
http://hostname.domain.com:8080/beat
After hitting the above url, I will be getting the below response which I need to parse it and extract value of state from it
num_retries_allowed: 3 count: 30 count_behind: 100 state: POST_INIT num_rounds: 60 hour_col: 2 day_col: 0
Now I am extracting value of state variable from the above string using grep.
#send the request, put response in variable
DATA=$(wget -O - -q -t 1 http://hostname.domain.com:8080/beat)
#grep $DATA for state
state=$(grep -oP 'state: \K\S+' <<< "$DATA")
[[ -z "$state" ]] && state=0
echo $state
Also if in $DATA variable state: string is not there by any chance, then I want to assign 0 to state variable. After that I want to verify the conditionals and exit out of the script depending on that.
If state is equal to POST_INIT then exit successfully out of the shell script or if state is equal to 0, then exit successfully as well.
if [[ $state -eq "POST_INIT" || $state -eq "0" ]]; then exit 0; fi
So my above if condition is not working somehow. Since what I have noticed is if my state variable value is IN_INIT, then also it is exiting out of the shell script? Is there anything wrong I am doing here in the string comparison?
-eq is for comparing numbers. = is for comparing strings.
If you were using [ instead of [[ you would be getting an error for a value of POST_INIT and IN_INIT.
$ state=POST_INIT
$ [ $state -eq 0 ]
-bash: [: POST_INIT: integer expression expected
$ echo $?
2
What I believe [[ is doing is actually being more clever and more annoying.
I believe it is expanding the variable and then using the expanded value in an arithmetic context (in which variables are expanded even from bare words) and since the variable POST_INIT doesn't have a value that gets expanded to 0 by default and your check passes.
$ state=POST_INIT
$ [[ $state -eq 0 ]]; echo $?
0
$ POST_INIT=5
$ [[ $state -eq 0 ]]; echo $?
1
$ POST_INIT=0
$ [[ $state -eq 0 ]]; echo $?
0
trying this
if [[ "$state" == "POST_INIT" || "$state" == "0" ]];
will help because if you use something like [ $state == "POST_INIT" ] , it ignores $state if it is null and would rather read the statement as [ == "POST_INIT". including " " ,prevents that case.

Resources