Shell – Delay termination of script

openbsdshell-scriptsignalstrap:

I have a script like this (written for /bin/sh on OpenBSD) which first updates a local copy of some CVS repositories using rsync, and then updates the checked-out version of these on my machine. The script is run as root through OpenBSD's doas (a sudo "equivalent" on OpenBSD):

#!/bin/sh

int_handler () {
    echo 'Wait...' >&2
    quit=1
}

quit=0
trap int_handler INT

for r in CVSROOT src xenocara ports; do
    su cvsuser -c 'rsync --archive --itemize-changes --delete --omit-dir-times \
        "rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
        "/extra/cvs/$r/"'
done

trap - INT

[ "$quit" -eq 1 ] && exit

# rest of script updates checked-out CVS repositories from local copy

The idea is that I may press Ctrl+C while the rsync loop is running to later skip the second part of the script (which I may not always want to run, depending on what has been updated in the repositories) without interrupting the loop and its su+rsync processes.

This does not work and the rsync process receives the signal and terminates (and the next iteration of the for loop continues). rsync says (when pressing Ctrl+C multiple times until the script terminates):

^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(504) [receiver=3.1.3]
Wait...
rsync: [receiver] write error: Broken pipe (32)
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(1633) [sender=3.1.3]
^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(504) [receiver=3.1.3
]

rsync: [receiver] write error: Broken pipe (32)
Wait...
^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]
rsync error: received SIGUSR1 (code 19) at main.c(1440) [receiver=3.1.3]
Wait...

According to an answer to the question "How to make ctrl+c /not/ interrupt the while-loop?", at least when running bash, one should be able to ignore the INT signal, which will make child processes also ignore that signal. This is not ideal, but I'd be happy to try.

So, second try in the light of that:

#!/usr/local/bin/bash

trap '' INT

for r in CVSROOT src xenocara ports; do
    su cvsuser -c 'rsync --archive --itemize-changes --delete --omit-dir-times \
        "rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
        "/extra/cvs/$r/"'
done

trap - INT

read -p 'Press enter or interrupt...' junk

# rest of script updates checked-out CVS repositories from local copy

This likewise allows the rsync process to be interrupted by Ctrl+C.

Ok, so maybe it's the su that resets the signal mask? A third try:

#!/usr/local/bin/bash

trap '' INT

for r in CVSROOT src xenocara ports; do
    su -s /usr/local/bin/bash cvsuser -c \
        'trap "" INT; rsync --archive --itemize-changes --delete --omit-dir-times \
        "rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
        "/extra/cvs/$r/"'
done

trap - INT

read -p 'Press enter or interrupt...' junk

# rest of script updates checked-out CVS repositories from local copy

Nope, no joy. The rsync process is still interruptible in the same way.

Is there a way to get the script working the way I (initially) intended?

bash on my machine is release 4.4.23. The /bin/sh is the native pdksh variant on OpenBSD running in POSIX mode.

Best Answer

So, since you don't really want an interrupt, how about disabling it on the terminal level, with stty intr ""? That would make the ^C appear in the input buffer as any ordinary character, and we can read it from there.

This works for me on Linux:

#!/bin/bash

t=$(stty -g)
stty intr ""
echo please hold
sleep 5
stty "$t"

# short timeout, just check if there was any input earlier
read -n1 -t0.1 a
[[ "$a" = $'\003' ]] && echo "you hit ^C"

(I can't test on OpenBSD, but I suppose the terminal setup stuff is standard.)

Related Question