I am writing a bash script in which I wrote a handler to take care of when the user pressed Control+C, (by using trap interruptHandler SIGINT
) but the SIGINT gets sent to both the bash script and the child process that is currently running, closing the child process. How can I prevent this from happening?
edit: here's the script, don't critique my skills too much..
#!/bin/bash
trap "interruptHandler" SIGINT
inInterrupt=false;
quit=false;
if [ -z ${cachedir+x} ]; then cachedir=~/.cache/zlima12.encoding; fi
cachedir=$(realpath ${cachedir});
if [ ! -e ${cachedir} ]; then mkdir ${cachedir}; fi
if [ ! -e ${cachedir}/out ]; then mkdir ${cachedir}/out; fi
cleanCache ()
{
rm ${cachedir}/*.mkv;
rm ${cachedir}/out/*.mkv;
}
interruptHandler ()
{
if [ ${inInterrupt} != true ]; then
printf "BASHPID: ${BASHPID}";
inInterrupt=true;
ffmpegPID=$(pgrep -P ${BASHPID});
kill -s SIGTSTP ${ffmpegPID};
printf "\nWould you like to quit now(1) or allow the current file to be encoded(2)? ";
read response;
if [ ${response} = "1" ]; then kill ${ffmpegPID}; cleanCache;
elif [ ${response} = "2" ]; then quit=true; kill -s SIGCONT ${ffmpegPID};
else printf "I'm not sure what you said... continuing execution.\n"; kill -s SIGCONT ${ffmpegPID};
fi
inInterrupt=false;
fi
}
for param in "$@"; do
dir=$(realpath ${param});
if [ ! -e ${dir} ]; then
printf "Directory ${dir} doesn't seem to exist... Exiting...\n"
exit 1;
elif [ -e ${dir}/new ]; then
printf "${dir}/new already exists! Proceed? (y/n) ";
read response;
if [ ${response} != y ]; then exit 1; fi
else
mkdir ${dir}/new;
fi
for file in ${dir}/*.mkv; do
filename="$(basename ${file})";
cp $file ${cachedir}/${filename};
ffmpeg -vsync passthrough -i ${cachedir}/${filename} -c:v libx265 -c:a copy -f matroska ${cachedir}/out/${filename};
rm ${cachedir}/${filename};
mv ${cachedir}/out/${filename} ${dir}/new/${filename};
if [ ${quit} = true ]; then exit 0; fi
done
done
(This is a script to encode matroska (mkv) files to H.265 in case you're curious)
The signal is sent to all jobs in the current foreground process. So the easiest way to prevent the signal from going to the child is to get it out of the foreground. Just background the ffmpeg call by doing:
Note that this also gives you the pid of the child more robustly that trying to parse the output of
ps
, so you might want to do:Take a look at this:
Explanations:
The childIt is enough to get the child out of foreground to prevent it from receiving SIGINT.ffmpeg
(sleep
here) must ignore SIGINT itself. To do that, start it withbash -c
, reset the handler, thenexec
.In the parent, a simple
wait
will not do, for reasons explained here. (Try it.) In that case, the parent would continue after executing its SIGINT handler but before the child was done. Instead, we use a loop and wait using the child pid.Upon legitimate exit of the child, one more
kill
will be executed on a nonexisting pid, whose stderr we ignore.Performed a simple test here and it delivers the expected result:
int.sh
contents:Testing:
EDIT (answering the question edit):
You probably want to trap and ignore
SIGINT
for every other command, such as(trap '' SIGINT && command)
in your script, so you can prevent the signal being caught from the current command beforeinterruptHandler
is invoked.A simple example of what's happening:
Output:
Note that it only lasted for 0.6 seconds due to
SIGINT
being caught.But when you ignore SIGINT for
sleep
:The output is:
Note that despite the
SIGINT
was delivered and caught by the script, theintHandler
will only return when the currentsleep
exits, and also note that the currentsleep
didn't caught theSIGINT
from the parent (it lasted for the full 5 seconds) as the subshell where it's running on (the( ... )
) is ignoringSIGINT
.