0

I have the following process execution structure:

script0.sh
    script1.sh
        script2.sh
        script3.sh

Meaning:

  • script0.sh executes script1.sh.
  • script1.sh executes script2.sh and script3.sh.

Ideally, I want to have a single trap handler in script1.sh, which would perform a cleanup and exit.
For some reason, it doesn't work.

I also tried having a trap handlers in all my scripts, but only the trap handler in script0.sh is being called.

I tried using the trap handler in script0.sh to kill the child process (i.e. script1.sh) but the process hangs until it completes.

I used the command 'trap -p' and noticed that only script0.sh has trap handlers, although that I registered trap handlers for all the scripts.

Note:

  • script2.sh is actually 'rsync', the remaining scripts are my own.
  • script1.sh, script2.sh and script3.sh were sent to background using '&' operator.
  • I perform 'wait' for every process that is sent to background.

I created the script files which reproduce this issue:

script0.sh:

#!/usr/bin/env bash

function cleanup_script0()
{
    echo "SIGINT in script0.sh"
    kill -s SIGINT $script1_pid
    wait $script1_pid
}

trap cleanup_script0 SIGINT

./script1.sh &
script1_pid=$!
wait $script1_pid

script1.sh:

#!/usr/bin/env bash

function cleanup_script1()
{
    echo "SIGINT in script1.sh"
    kill -s SIGINT $script2_pid
    wait $script2_pid
    kill -s SIGINT $script3_pid
    wait $script3_pid
}

trap cleanup_script1 SIGINT

rsync_file_to_copy="$( mktemp )"
dd if=/dev/urandom of="$rsync_file_to_copy" bs=1M count=1
rsync --timeout=5 --bwlimit=200 "$rsync_file_to_copy" "$( mktemp -d )/" &
script2_pid=$!
wait $script2_pid
echo "Finished script2.sh"

./script3.sh
script3_pid=$!
wait $script3_pid
echo "Finished script3.sh"

script3.sh:

#!/usr/bin/env bash

function cleanup_script3()
{
    echo "SIGINT in script3.sh"
}

trap cleanup_script3 SIGINT

delay_counter=0
while [ 8 -gt $delay_counter ]; do
    echo "script3.sh: $( date "+%H_%M_%S" )"
    let 'delay_counter++'
    sleep 1
done

The output for execution of the above scripts is as follows:

The output for when CTRL+C during rsync:

$ ./script0.sh 
1+0 records in
1+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.00604244 s, 174 MB/s
^CSIGINT in script0.sh
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(703) [sender=3.2.3]
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(503) [generator=3.2.3]
Finished script2.sh
script3.sh: 23_57_24
script3.sh: 23_57_25
script3.sh: 23_57_26
script3.sh: 23_57_27
script3.sh: 23_57_28
script3.sh: 23_57_29
script3.sh: 23_57_30
script3.sh: 23_57_31
Finished script3.sh

The output for when CTRL+C during script3.sh:

$ ./script0.sh 
1+0 records in
1+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.00585018 s, 179 MB/s
Finished script2.sh
script3.sh: 23_57_46
script3.sh: 23_57_47
^CSIGINT in script0.sh
script3.sh: 23_57_48
script3.sh: 23_57_49
script3.sh: 23_57_50
script3.sh: 23_57_51
script3.sh: 23_57_52
script3.sh: 23_57_53
Finished script3.sh
4
  • is script1 doing anything else? because otherwise the rsync ... & wait; could be replaced with the simpler rsync ...;
    – thrig
    Commented Sep 24, 2022 at 22:59
  • @thrig currently script1.sh doesn't do something else. But I prefer to leave it like this. Anyway the same issue exist
    – Dor
    Commented Sep 24, 2022 at 23:08
  • Important conclusion: Regarding script0.sh: If I write ./script1.sh instead of ./script1.sh & then script1.sh is able to detect SIGINT such that its handler is executed. Therefore, I conclude that the SIGINT handling was passed to it because it is part of the "foreground" execution.
    – Dor
    Commented Sep 25, 2022 at 13:03
  • In my script1.sh file I also had an additional issue which was caused by redirection with exec, like so: exec > >(tee "${script_log_filepath}") 2>&1. This caused the SIGINT to be ignored for script1.sh, so instead of the handler, the script would just exit. That can be solved with pipes (mkfifo) instead of process substitution.
    – Dor
    Commented Sep 25, 2022 at 15:20

1 Answer 1

2

A control+c will result in a SIGINT sent to every member of the foreground process group (or no signal at all; see the termios man page). Thus, we can simplify the signal handling by relying on the usual behavior of a signal to the process group, even as more and more changes come to light as to what the scripts are actually doing.

#!/bin/sh
# script0 - apparently this script does more things that were absent
# in the question, so we run the first script instead of executing
# the next script (this changes nothing from the prior result)
./script1
echo and we do some other things here ...

and script1, which now apparently has some sort of output redirection going on, somewhere it it, so let's guess that happened up at the top (this changes nothing from the prior result):

#!/usr/bin/env bash
# script1 - replace rsync with a hopefully similar `sleep` call,
# plus now the output redirect, minus anything they haven't yet
# told us about

exec > >(tee and-a-ruthless-devotion-to-the-pope) 2>&1

echo RUN script2
# `command & wait;` is just a complicated and verbose way to write
# `command;` (unless there is yet another thing going on they
# haven't yet told us about??)
sleep 9

echo RUN script3
./script3

and script3

#!/usr/bin/env bash
delay_counter=0
while [[ 8 -gt $delay_counter ]]; do
   echo -n "script3 time "
   date +%H:%M:%S
   (( delay_counter++ ))
   sleep 1
done

which after chmod +x script* still again results in

$ ./script0
RUN script2
^C
$ ./script0
RUN script2
RUN script3
script3 time 16:28:31
script3 time 16:28:32
script3 time 16:28:33
^C
$ 

Needless signal handling only complicates matters, and requires even more complications to try to workaround the resulting complications. Why not simplify and rely on the default behavior?

3
  • I appreciate your time and effort though this solution limits my capabilities because that exec doesn't execute the remaining script. I noticed through file /proc/$$/status that the signal SIGINT is ignored (field SigIgn in the file). So I tried with SIGTERM and it worked well. I think, maybe it is a feature, because that only one process is supposed to handle user interruption (SIGINT), such that the other processes should respond to SIGTERM accordingly
    – Dor
    Commented Sep 25, 2022 at 12:38
  • "only one process is supposed to handle SIGINT" is a false claim. The signal is sent to each and every process in the foreground process group, as is documented, and can be observed with suitable tracing of processes.
    – thrig
    Commented Sep 25, 2022 at 13:35
  • probably, though this is my interpretation of what I observed. These processes are in background, so it makes sense with what you wrote, no?
    – Dor
    Commented Sep 25, 2022 at 15:22

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .