Bash trap - exit only at the end of loop - bash

I´ve got a bash script which has different cases - each with a for or a while loop. I´ve declared a trap function to get the chance to exit the script and the loop. But if I do this the script will exit immediately - I want to exit the loop at the end of the loop run because each loop run takes a long time.
Here is a short version of my script:
CleanUp() {
echo "Trap exit detected"
rm -f $TMPFILE1
rm -f $TMPFILE2
StopPreventSleep
echo "... and ready!" && exit
}
trap CleanUp EXIT INT TERM SIGINT SIGTERM SIGTSTP
case $1 in
check)
for FILES in "${SRCFILES[#]}"
do
[somemagic]
done
;;
read)
for FILES in "${SRCFILES[#]}"
do
[somemagic]
done
;;
write)
while [ -n "$line" ]
do
[somemagic]
done
;;
I want that the script only could exit after doing [somemagic] in each loop (depends on the parameter $1 = which case is choosen).

change the line
echo "... and ready!" && exit
to:
QUIT=1
And after each of your [somemagic], add the some extra logic as below:
...
[somemagic]
if [ ! -z $QUIT ]; then
exit
fi

Related

Is "exit ${?}" correct to use in bash?

Let's say I code something like:
if [ ${?} -ne 0 ]; then
exit ${?}
Would this work properly?
Is this correct "syntax-wise"?
The [ command in your if statement will set $? after it checks. You'll need to save the original exit status before testing.
some_command
es=$?
if [ "$es" -ne 0 ]; then
exit "$es"
fi
The syntax is correct, but $? is reset by the [ ... ] command in the if statement. By definition, if the if block is entered then the [ ... ] test must have been successful, and $? is guaranteed to be 0.
You'll need to save it in a variable.
result=$?
if ((result != 0)); then
exit "$result"
fi
Alternately, it's more idiomatic to test the result of a command directly rather than testing $?. If you do so then you don't have the problem of $? changing.
if command; then
echo success
else
exit # `exit` is equivalent to `exit $?`
fi
If you don't care about success then you can use ||:
command || exit
If you want to exit on error and preserve the EXIT code of the command, you can enable the errexit option:
Either with: set -e
Either with: set -o errexit
See: help set | grep -F -- -e
-e Exit immediately if a command exits with a non-zero status.
errexit same as -e
Alternatively you can trap the ERR signal and use this to exit with the return code of the error.
This will save you from dealing with the -e option consequences.
#!/usr/bin/env bash
err_handler() {
set -- $?
printf 'The error handler caught code #%d\n' "$1" >&2
exit "$1"
}
trap 'err_handler' ERR
create_error() {
[ $# -ne 1 ] && return 0
printf 'Create error #%d\n' "$1"
return "$1"
}
create_error "$#"
Testing with different errors:
for e in 1 0 2; do ./a.sh "$e" ;echo $?; done
Create error #1
The error handler caught code #1
1
Create error #0
0
Create error #2
The error handler caught code #2
2

Nested background processes and SIGINT handling

Here is an example script, simple.sh, that launches a background process, sends it a signal, and verifies that the signal was handled:
#!/bin/bash
function on_signal() {
echo "1 caught $1, exiting!"
exit 0
}
function my_sleep {
trap "on_signal $1" $1
echo "1 mypid=$$"
echo "1 mybashpid=$BASHPID"
echo "1 start sleep"
for i in {1..10};
do
echo "1 sleeping $i"
sleep 1
done
echo "1 failed"
exit 1
}
signal=SIGINT
if [[ ! -z $1 ]]; then
signal=$1
fi
my_sleep $signal &
sleeppid=$!
echo ">>> sleeppid=$sleeppid"
sleep 1 # to give script time to run trap
echo ">>> sending $signal"
kill -$signal $sleeppid
for i in 0.25 0.5 1 2; do
echo ">>> trying $i"
if kill -0 $sleeppid 2> /dev/null; then
echo ">>> still running..."
sleep $i
else
echo ">> success"
exit 0
fi
done
echo "Failure"
exit 1
Now, when I run ./simple.sh, it works fine. When I run it in the background, ./simple.sh &, it works fine as well. However, when I add a wrapper, w1.sh, that runs simple.sh in the background, it suddenly stops working:
#!/bin/bash
./simple1.sh $1 &
Now ./w1.sh results in Failure. Why?
I have read https://www.cons.org/cracauer/sigint.html, and it was very informative, but I still cannot understand how the child process itself can behave differently depending on the context it was called in.
Note: I know a "workaround" to get it to behave the way I want to (w1.sh should call (./simple.sh $1) & in a subshell), but I want to understand what's going on there, and why the workaround works.
Thank you!

Bash loop until a certain command stops failing

I would like to write a loop in bash which executes until a certain command stops failing (returning non-zero exit code), like so:
while ! my_command; do
# do something
done
But inside this loop I need to check which exit code my_command returned, so I tried this:
while ! my_command; do
if [ $? -eq 5 ]; then
echo "Error was 5"
else
echo "Error was not 5"
fi
# potentially, other code follows...
done
But then the special variable ? becomes 0 inside the loop body.
The obvious solution is:
while true; do
my_command
EC=$?
if [ $EC -eq 0 ]; then
break
fi
some_code_dependent_on_exit_code $EC
done
How can I check the exit code of my_command (called in loop header) inside loop body without rewriting this example using a while true loop with a break condition as shown above?
In addition to the well-known while loop, POSIX provides an until loop that eliminates the need to negate the exit status of my_command.
# To demonstrate
my_command () { read number; return $number; }
until my_command; do
if [ $? -eq 5 ]; then
echo "Error was 5"
else
echo "Error was not 5"
fi
# potentially, other code follows...
done
If true command hurt your sensibility, you could write:
while my_command ; ret=$? ; [ $ret -ne 0 ];do
echo do something with $ret
done
This could be simplified:
while my_command ; ((ret=$?)) ;do
echo do something with $ret
done
But if you don't need ResultCode, you could simply:
while my_command ; [ $? -ne 0 ];do
echo Loop on my_command
done
or
while my_command ; (($?)) ;do
echo Loop on my_command
done
And maybe, why not?
while ! my_command ;do
echo Loop on my_command
done
But from there you could better use until as chepner suggest
You can get the status of a negated command from the PIPESTATUS built-in variable:
while ! my_command ; do
some_code_dependent_on_exit_code "${PIPESTATUS[0]}"
done
chepner's solution is better in this case, but PIPESTATUS is sometimes useful for similar problems.
So in my case I also need to ignore some exit codes and want to provide some useful output to the user so I wrote this up:
retrycmd(){
MSG=$1
IGNORE=$2
shift 2
local SLEEP_T=5
local L_CNT=5
local C_CNT=0
while ((C_CNT++ < ${L_CNT})) && ! $#;do
RET=${PIPESTATUS[0]}
#echo "RET: ${RET}"
for I in ${IGNORE//,/ };do # bashism: replace(/) all(/) match(,) with(/) value(<space>)
if ((${RET} == ${I}));then
#echo "${RET} = ${I}"
break 2
fi
done
echo "${MSG} failure ${C_CNT}"
sleep ${SLEEP_T}
done
if ((${C_CNT} > ${L_CNT}));then
echo "${MSG} failed"
poweroff
fi
}
#retrycmd "Doing task" "IGNORE,CSV" <CMD>
retrycmd "Ping google" "0" ping www.google.com

exit statement will not break while loop in unix shell

The exit statements in each status check if statement do not break the while loop and truly exit the script. Is there something I can do to break the loop and exit with that $STATUS code?
EDIT: I've updated my code and it still isn't working. The status check if statements successfully break the loop but when I try to evaluate the $EXIT_STATUS it's always null, likely having something to do with scope. What am I missing here?
if [ $RESTART -le $STEP ]; then
. tell_step
while read XML_INPUT; do
XML_GDG=`get_full_name $GDG_NAME P`
cp $XML_INPUT $XML_GDG
STATUS=$?
EXIT_STATUS=$STATUS
if [ $STATUS -ne 0 ]; then
break
fi
add_one_gen $XML_GDG
STATUS=$?
EXIT_STATUS=$STATUS
if [ $STATUS -ne 0 ]; then
break
fi
done < $XML_STAGE_LIST
echo $EXIT_STATUS
if [ $EXIT_STATUS -ne 0 ]; then
exit $EXIT_STATUS
fi
fi
I had the same problem: when piping into a while loop, the script did not exit on exit. Instead it worked like "break" should do.
I have found 2 solutions:
a) After your while loop check the return code of the while loop and exit then:
somecommand | while something; do
...
done
# pass the exit code from the while loop
if [ $? != 0 ]
then
# this really exits
exit $?
fi
b) Set the bash script to exit on any error. Paste this at the beginning of your script:
set -e
Not really understand why your script dosn't exits on exit, because the next is works without problems:
while read name; do
echo "checking: $name"
grep $name /etc/passwd >/dev/null 2>&1
STATUS=$?
if [ $STATUS -ne 0 ]; then
echo "grep failed for $name rc-$STATUS"
exit $STATUS
fi
done <<EOF
root
bullshit
daemon
EOF
running it, produces:
$ bash testscript.sh ; echo "exited with: $?"
grep failed for bullshit rc-1
exited with: 1
as you can see, the script exited immediatelly and doesn't check the "daemon".
Anyway, maybe it is more readable, when you will use bash functions like:
dostep1() {
grep "$1:" /etc/passwd >/dev/null 2>&1
return $?
}
dostep2() {
grep "$1:" /some/nonexistent/file >/dev/null 2>&1
return $?
}
err() {
retval=$1; shift;
echo "$#" >&2 ; return $retval
}
while read name
do
echo =checking $name=
dostep1 $name || err $? "Step 1 failed" || exit $?
dostep2 $name || err $? "Step 2 failed" || exit $?
done
when run like:
echo 'root
> bullshit' | bash testexit.sh; echo "status: $?"
=checking root=
Step 2 failed
status: 2
so, step1 was OK and exited on the step2 (nonexisten file) - grep exit status 2, and when
echo 'bullshit
bin' | bash testexit.sh; echo "status: $?"
=checking bullshit=
Step 1 failed
status: 1
exited immediatelly on step1 (bullshit isn't in /etc/passwd) - grep exit status 1
You'll need to break out of your loop and then exit from your script. You can use a variable which is set on error to test if you need to exit with an error condition.
I had a similar problem when pipelining. My guess is a separate shell is started when piplining. Hopefully it helps someone else who stumbles across the problem.
From jm666's post above, this will not print 'Here I am!':
while read name; do
echo "checking: $name"
grep $name /etc/passwd >/dev/null 2>&1
STATUS=$?
if [ $STATUS -ne 0 ]; then
echo "grep failed for $name rc-$STATUS"
exit $STATUS
fi
done <<EOF
root
yayablah
daemon
EOF
echo "Here I am!"
However the following, which pipes the names to the while loop, does. It will also exit with a code of 0. Setting the variable and breaking doesn't seem to work either (which makes sense if it is another shell). Another method needs to be used to either communicate the error or avoid the situation in the first place.
cat <<EOF |
root
yayablah
daemon
EOF
while read name; do
echo "checking: $name"
grep $name /etc/passwd >/dev/null 2>&1
STATUS=$?
if [ $STATUS -ne 0 ]; then
echo "grep failed for $name rc-$STATUS"
exit $STATUS
fi
done
echo "Here I am!"

Shell Script: Exit call not working on using back quotes

I am using exit 1 to stop a shell script execution when error occured.
Shell Script
test() {
mod=$(($1 % 10))
if [ "$mod" = "0" ]
then
echo "$i";
exit 1;
fi
}
for i in `seq 100`
do
val=`test "$i"`
echo "$val"
done
echo "It's still running"
Why it's not working?. How can I stop the shell script execution?
The shell that exit is exiting is the one started by the command substitution, not the shell that starts the command substitution.
Try this:
for i in `seq 100`
do
val=`test "$i"` || exit
echo "$val"
done
echo "It's still running"
You need to explicitly check the exit code of the command substitution (which is passed through by the variable assignment) and call exit again if it is non-zero.
Incidentally, you probably want to use return in a function rather than exit. Let the function caller decide what to do, unless the error is so severe that there is no logical alternative to exiting the shell:
test () {
if (( $1 % 10 == 0 )); then
echo "$i"
return 1
fi
}
The exit command terminates only the (sub)shell in which it is executed.
If you want to terminate the entire script, you have to check the exit status
($?) of the function and react accordingly:
#!/bin/bash
test() {
mod=$(($1 % 10))
if [ "$mod" -eq "0" ]
then
echo "$i";
exit 1;
fi
}
for i in `seq 100`
do
val=`test "$i"`
if [[ $? -eq 1 ]]
then
exit 1;
fi
echo "$val"
done
echo "It's still running"

Resources