I am trying to do a for loop in Bash and exit on an if statement but I realised it will break the code before finishing the loop.
#!/bin/bash
for node in $(ps -ef | awk <something>);
do
var=<command>
echo "node ${node}"
if [[ ${var} -gt 300000 ]]; then
echo "WARNING!";
exit 1;
elif [[ ${var} -gt 1000000 ]]; then
echo "CRITICAL!";
exit 2;
else
echo "OK!";
exit 0;
fi
done
My second option is to set a variable instead of the exit outside the loop but then I realised it will override each node status:
#!/bin/bash
for node in $(ps -ef | awk <command>);
do
var=<command>
echo "node ${node}"
if [[ ${var} -gt 300000 ]]; then
echo "WARNING!";
status="warning"
elif [[ ${var} -gt 1000000 ]]; then
echo "CRITICAL!";
status="critical"
else
echo "OK!";
status="ok"
fi
done
if [[ status == "warning" ]]; then
exit 1;
elif [[ status == "critical" ]]; then
exit 2;
elif [[ status == "ok" ]]; then
exit 0;
fi
How do I exit properly on each node?
Here is an alternative. It counts the results and and creates an exit status depending on the counters. I changed the semantic, because your script never reaches the CRITICAL path. Instead the WARNING path was entered for values >1000000:
#!/bin/bash
let ok_count=0 warn_count=0 critical_count=0
for node in $(ps -ef | awk <command>);
do
var=<command>
echo "node ${node}"
# >1000000 must be checked before >300000
if [[ ${var} -gt 1000000 ]]; then
echo "CRITICAL!";
let critical_count++
elif [[ ${var} -gt 300000 ]]; then
echo "WARNING!";
let warn_count++
else
echo "OK!";
let ok_count++
fi
done
if ((critical_count)); then
exit 2
elif ((warn_count)); then
exit 1
else
exit 0
fi
This script can be optimized, if only the exit status is needed:
CRITICAL is the highest warn level. So counting is not necessary.
OK is the fallback. So counting is not necessary.
#!/bin/bash
let warn_count=0
for node in $(ps -ef | awk <command>);
do
var=<command>
echo "node ${node}"
if [[ ${var} -gt 1000000 ]]; then
echo "CRITICAL! -> abort";
exit 2 # no more analysis needed!
elif [[ ${var} -gt 300000 ]]; then
echo "WARNING!";
let warn_count++
fi
done
exit $(( warn_count > 0 ))
Use continue operator, from man bash
...
continue [n]
Resume the next iteration of the enclosing for, while, until, or select loop. If n is specified, resume at the nth enclosing loop. n must be
≥ 1. If n is greater than the number of enclosing loops, the last enclosing loop (the ``top-level'' loop) is resumed. The return value is 0
unless n is not greater than or equal to 1.
Or break if you need to exit from loop
...
break [n]
Exit from within a for, while, until, or select loop. If n is specified, break n levels. n must be ≥ 1. If n is greater than the number of
enclosing loops, all enclosing loops are exited. The return value is 0 unless n is not greater than or equal to 1.
Related
I'm trying to do a script that is used with two arguments - a file and an integer. It should check if the arguments are valid, otherwise exit with 1. Then it should either return 0 if the file is smaller than second argument, or echo size of the file to stdout. The script keeps returning value 123 instead of 1 or 0. Where is the problem? Thanks.
#!/bin/bash
if [ $# -eq 2 ];
then
if test $2 -eq $2 > /dev/null 2>&1
then
if [ -f $1 ];
then
if [ $(stat -c %s $1) -ge $2 ];
then
echo $(stat -c %s $1)
else
exit 0
fi
else
exit 1
fi
else
exit 1
fi
else
echo 042f9
exit 1
fi
I do not know where the "123" output comes from, but I would do it like this:
#!/bin/bash
# Must have 2 arguments
if [[ $# -ne 2 ]]
then
printf "042f9\n"
exit 1
fi
# File must exist
if [[ ! -f "$1" ]]
then
exit 1
fi
# File size > $2 check
filesize=$(stat -c %s "$1")
if [[ $filesize -ge $2 ]]
then
printf "%d" "$filesize"
else
exit 1
fi
A couple notes for your scripts (IMHO):
Like Mat mentioned in the comments, test 1 condition and exit right away. When I read your script, I had to go to the end to see what happens if the number of arguments is wrong. Logically there is nothing wrong with your code, it is just making it easier to read.
For bash, use [[ ]] to test if conditions.
I try never to call a function or command twice. That is why I stored the result of the stat command in a variable. If you use it more than once, store it, do not call the command again.
No need for ; since you put your then on the next line anyway.
Always double-quote your variables, especially if they are filenames. Weird filenames break so many scripts!
Finally use printf instead of echo. For simple cases, its the same, but echo does have some issues (https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo).
Possible return values:
the size of the file, and the exit value is 0 ($?). The file is larger than argument 2 value.
"042f9", and the exit value is 1 ($?). Arguments error.
nothing, and the exit value is 1 ($?). Missing file error, or the file is smaller than argument 2 value.
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
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
I'm writing a simple shell script that should exit with 0 if an input string is found in a file, and exit with 1 if it isn't
INPSTR=$1
cat ~/file.txt | while read line
do
if [[ $line == *$INPSTR* ]]; then
exit 0
fi
done
#string not found
exit 1
What's actually happening is that when the string is found, the loop exits, and the shell then goes to "exit 1". What's the correct way to exit from the shell script entirely while in a loop?
You need to avoid creating sub-shell in your script by avoiding the pipe and un-necessary cat:
INPSTR="$1"
while read -r line
do
if [[ $line == *"$INPSTR"* ]]; then
exit 0
fi
done < ~/file.txt
#string not found
exit 1
Otherwise exit 0 is only exiting from the sub-shell created by pipe and later when loop ends then exit 1 is used from parent shell.
you can catch return code of subshell using $? like this
INPSTR=$1
cat ~/file.txt | while read line
do
if [[ $line == *$INPSTR* ]]; then
exit 0
fi
done
if [[ $? -eq 0 ]]; then
exit 0
else
#string not found
exit 1
fi
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.