Bash Not Trapping Interrupts During Rsync/Subshell Exec Statements

Bash not trapping interrupts during rsync/subshell exec statements

How about just having all the output from point X be redirected to tee without having to repeat it everywhere and mess with all the sub-shells and execs ... (hope I didn't miss something)

#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir

exec > >(exec tee -a $logfile) 2>&1

cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap cleanup EXIT

sleep 10
rsync --progress -av --delete $directory1 /var/tmp/$directory2

Parent trap visible but not run by subshell

You'll notice that the traps trigger exactly according to spec. It's just the output from trap that's unexpected.

This is a feature in Bash 4.2 (release notes):

b.  Subshells begun to execute command substitutions or run shell functions or
builtins in subshells do not reset trap strings until a new trap is
specified. This allows $(trap) to display the caller's traps and the
trap strings to persist until a new trap is set.

Normally, people would take this for granted. Consider this totally unsurprising Bash exchange:

bash$ trap
trap -- 'foo' EXIT
trap -- 'bar' SIGINT

bash$ trap | grep EXIT
trap -- 'foo' EXIT

Now look at the result in other shells like Dash, Ksh or Zsh:

dash$ trap
trap -- 'foo' EXIT
trap -- 'bar' INT

dash$ trap | grep EXIT
(no output)

This is perhaps more correct, but I doubt many people would expect it.

rsync suppress SIGINT for trap outside

Why does it first return code 20 didn't make the loop break?

You are correct that rsync does catch certain signals and exit with RERR_SIGNAL (20).

How to let the trap catch the SIGINT signal but rsync, if not, what should I do?

Since rsync has its own handlers, you can't do anything ( could use some hacks to override signal handlers within rsync with LD_PRELOAD for example. But it may be unnecessarily complicated). Since your traps are in the current shell, you wouldn't know whether the "command" was signaled or exit with non-zero.

I'd assume you want to your retry to be generic and you don't want special handling of rsync (e.g., a different command may exit with 75 on signals and you don't want to dealing with special cases).

The problem is your trap handlers isn't active as the the signal is received by the current process running process (rsync). You could instead run your command in the background and wait for it to complete. This would allow your catch signals from retry. On receiving a signal, it simply kills the child process.

#!/bin/bash

retry()
{
declare -i number=$1
declare -i i
declare -i pid
declare -i interrupted=0

trap "echo Exiting...; interrupted=1" SIGINT SIGTERM SIGQUIT
shift

# Turn off "monitor mode" so the shell doesn't report terminating background jobs.
set +m

for ((i = 0; i < number; ++i)); do
echo "\n-- Retry ${i}th time(s) --\n"
$@ &
pid=$!

# If command succeeded, break
wait $pid && break

# If we receive one of the signals, break
[[ $interrupted == 1 ]] && kill $pid && break
done

# Switch back to default behaviour
set -m
trap - SIGINT SIGTERM SIGQUIT
}

Note that SIGKILL can't be caught. So there's no point in setting a trap for it. So I have removed it.

Exiting a function with trap and its error code

You must use exit instead of return. So this would be

trap 'printf "\nAborting\n"; exit 1' SIGINT

bash script exits with zero status even after kill signal

I assume the missing quote in echo "Script Status: $1 | tee -a ${LOG} is not relevant to the question.

When you want a function to handle the trap, you need to export that function.

And when you want to kill children, you should add these in your trap-function.

I tested these adjustments with a sleep command, it should work for rsync too.

#!/bin/bash

exitscript() {
echo "Script Status: $1"
(( $pleasekill > 0 )) && kill ${pleasekill}
echo "> End Date: $(date +'%d.%m.%Y %H:%M:%S')"
exit $1
}

# Export the function exitscript
export exitscript

######START#######
pleasekill=0
trap "exitscript 1" 1 2 23 24 25
# Start I/O-friendly rsync function
sleep 30 &
pleasekill=$!
wait

exitscript 2

When you test this with the first script, use ^C or kill -1 pid_of_backup.

EXIT trap (killing background loop) not also killing processes run by that loop

Not as simple...

Problem is: when using ( ... ) syntax, you will create a subshell... Let's show:

In some terminal console:

tty
/dev/pts/24
ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
29322 pts/24 R+ 0:00 \_ ps --tty pts/24 fw

( echo foo; sleep 300 ) &
[1] 30214
foo

ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
30214 pts/24 S 0:00 \_ bash
30215 pts/24 S 0:00 | \_ sleep 300
30377 pts/24 R+ 0:00 \_ ps --tty pts/24 fw

ps $!
PID TTY STAT TIME COMMAND
30214 pts/24 S 0:00 bash

So $! is bash subshell, and sleep has another pid.

So killing $! will only kill bash:

kill $!
[1]+ Terminated ( echo foo; sleep 300 )

but

ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
30793 pts/24 R+ 0:00 \_ ps --tty pts/24 fw
30215 pts/24 S 0:00 sleep 300

sleep is still there.

Solution #1:

Use exec on last long-running command:

( echo foo; exec sleep 300 ) &
[1] 31557
foo

ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
31557 pts/24 S 0:00 \_ sleep 300
31558 pts/24 R+ 0:00 \_ ps --tty pts/24 fw

then

kill $!
[1]+ Terminated ( echo foo; exec sleep 300 )

ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
31706 pts/24 R+ 0:00 \_ ps --tty pts/24 fw

Seem ok.

For loop, things are more, hem...

ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
1176 pts/24 R+ 0:00 \_ ps --tty pts/24 fw

for ((i=3;i--;)){ sleep 300 & trap "kill $!;exit" 0 1 2 3 6 9 15;wait $!;} &
[1] 1178

ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
1178 pts/24 S 0:00 \_ bash
1179 pts/24 S 0:00 | \_ sleep 300
1180 pts/24 R+ 0:00 \_ ps --tty pts/24 fw

Well, now:

kill -1 $!
[1]+ Done for ((i=3; i--; 1))
do
sleep 300 & trap "kill $!;exit" 0 1 2 3 6 9 15; wait $!;
done

ps --tty pts/24 fw
PID TTY STAT TIME COMMAND
28983 pts/24 Ss 0:00 bash
1299 pts/24 R+ 0:00 \_ ps --tty pts/24 fw

Seem fine!



Related Topics



Leave a reply



Submit