Bash fail completely if any subshell fails - bash

I want to run some scripts in parallel and if all succeeds I will execute some extra commands. However if any of the subshells fails I want to exit immediately.
I guess it is easier to explain with an example:
exit_in_1() {
sleep 1
echo "This should be the last thing printed"
exit 1
}
exit_in_2() {
sleep 2
echo "This should not be printed"
exit 1
}
exit_in_1 &
exit_in_2 &
echo "This should be printed immediately"
sleep 3
echo "This should not be printed also"
Above script prints all the echos.

Thanks to #Mansuro's comment I solved my problem like this:
exit_later() {
sleep $1
echo "This should be printed twice"
exit $(($1-1))
}
pids=""
for i in {1..10}; do
exit_later $i &
pids+=" $!"
done
echo "This should be printed immediately"
for p in $pids; do
if ! wait $p; then
kill $pids
exit 1
fi
done
echo "This should not be printed"

With GNU Parallel this might work:
exit_in_1() {
sleep 1
echo "This should be the last thing printed"
exit 1
}
exit_in_2() {
sleep 2
echo "This should not be printed"
exit 1
}
export -f exit_in_1 exit_in_2
parallel --halt-on-error 2 ::: 'echo "This should be printed immediately"' exit_in_1 exit_in_2 || exit 1
echo "This should not be printed also"
Or:
exit_later() {
sleep $1
echo "This should be printed twice"
exit $(($1-1))
}
export -f exit_later
echo "This should be printed immediately"
parallel -j0 --halt-on-error 2 exit_later ::: {1..10} || exit 1
echo "This should not be printed"

Related

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!

How to print in the same line as a progress in bash script

I want to print the text in the following manner
Waiting for completion.
Waiting for completion..
Waiting for completion...
[Note :Not more than three dots]
The above should be in the same line and in a loop.
When the loop condition is false I want to get the following in the same line as well :
Waiting for completion... [OK]
How do I achieve this in bash script?
You should use carriage return. Search information in echo about \r.
for example maybe you want something like this:
#!/bin/bash
while [ 3 -gt 2 ];
do
echo -n -e 'Esperando.\r'
sleep 1
echo -n -e 'Esperando..\r'
sleep 1
echo -n -e 'Esperando...\r'
sleep 1
echo -n -e ' \r'
done
You need to sleep cause if you dont sleep you won't be able to watch the changing dots.
How about this:
#!/usr/bin/env bash
trap ctrl_c INT
ctrl_c()
{
flag=1
}
dots()
{
if [ "$1" -eq 1 ]
then
echo .
fi
if [ "$1" -eq 2 ]
then
echo ..
fi
if [ "$1" -eq 3 ]
then
echo ...
fi
}
flag=0
dots_count=1
while [ "$flag" -eq 0 ]
do
if [ "$dots_count" -eq 4 ]
then
dots_count=1
fi
printf "\r%sWaiting for completion%s" "$(tput el)" "$(dots "$dots_count")"
dots_count=$((dots_count + 1))
sleep 1
done
printf "\r%sWaiting for completion... [OK]\n" "$(tput el)"
It will continuously print Waiting for completion followed by up to
three dots in the same line. When Control-c is
pressed then "Waiting for completion... [OK]" will be printed.
Use echo's "-n" switch
next echo will print on the same line

Bash trap - exit only at the end of loop

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

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