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
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 question about simple shell script.
this is the source code of rand.sh below
#!/bin/bash
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
>&2 echo "The error was using magic numbers"
exit 1
fi
echo "Everything went accrding to plan"
and i'm going to make a new shell script, let me call it quiz.sh.
quiz.sh should loop until n==42. if n==42, save the stdout("Something went wrong") and stderr("The error was using magic numbers")
and it finally terminated with printing out those stdout,stderr and Total execution count.
here is my quiz.sh
#!/bin/bash
cnt=0
while [[ "${n}" -ne 42 ]]
do
(( cnt = "${cnt}"+1 ))
source ./rand.sh &> error.txt
done
cat error.txt
echo "${cnt}"
but this is not working. because of exit 1 in rand.sh, the program is terminated before executing cat and echo which is at the end two line.
how can i fix it?? please let me know!
I want to make happen cat error.txt and echo "${cnt}" as well
Run the loop in a subshell
(
while something; do
something
exit 1 # exits only from the subshell
done
)
Note: parent shell doesn't access/inherit child process environment. So cnt is going to be empty in parent shell. Transfer it some other way.
(
cnt=0
while ((n != 42)); do
((cnt++))
echo "$cnt" > cntfile.txt
# >& is deprecated
source myrand > error.txt 2>&1
done
)
cnt=$(<cntfile.txt)
cat error.txt
echo "$cnt"
Reference Bash manual command grouping.
As KamilCuk pointed out correctly, you should use $n instead of n.
Furthermore, I personally would add that using source ./rand.sh &> error.txt is kind of weird in this case. If you want to run it as a background process, use:
./rand.sh &> error.txt &
wait $! # $! is the pid
Otherwise, just make a function out of it:
#!/bin/bash
function myrand {
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
>&2 echo "The error was using magic numbers"
return 1
fi
echo "Everything went accrding to plan"
return 0
}
cnt=0
while [[ "${n}" -ne 42 ]]
do
(( cnt = "${cnt}"+1 ))
myrand() &> error.txt
done
cat error.txt
echo "${cnt}"
p.s. code not tested, but I guess it works.
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.
from my experience in bash script
some times exit command not exit from the script because using a pipe lines , sub shell , or even deep loops , etc
in spite this is rare case , I want to avoid to use the exit command and think to use to kill the script process itself
but I am asking if this approach to use - kill -SIGPIPE $$ will do the Job ? , or there are some risks here
example function with exit
function check_status_of_command
{
result=$1
if [[ $result -ne 0 ]]
then
echo "Error ...... "
exit 1
else
return 0
fi
}
example function withou exit , but we set the - kill -SIGPIPE $$ insted
function check_status_of_command
{
result=$1
if [[ $result -ne 0 ]]
then
echo "Error ...... "
kill -SIGPIPE $$
else
return 0
fi
}
example how to call the function
cp file file.bck
check_status_of_command
So I need my while loop to continually loop but it stops after the first user's input, example:
[user]$: ./pleasefix.sh
Enter Input:test
test is writeable.
[user]$:
Heres my script as it is:
if [ "$#" -ne 0 ]
then
echo "$0" "is expecting no arguments; found $# $*"
echo "Usage: "$0""
exit 2
fi
while read -p "Enter Input:" userString
do
if [ -w "$userString" ]
then
echo ""$userString" is writeable."
exit 0
else
echo ""$userString" is nonexistent or not writeable."
exit 1
fi
done
What can I add to my while to make it actually loop and re prompt the user for another file name? Basically I want it to last forever until a EOF is sent (crtl + D)
Take the exit 0 and exit 1 out
You are using "exit" at both case if-else. You can remove one of them.
Remove the "exit 0" and the "exit 1"? Those will cause it to exit your script.