1

Backgound

I have read a post about handling of SIGINT signal, but I still don't understand how to properly handle it in code which will be sourced and used by both the interactive and non-interactive shells.

I will give an simplified example of my script and ask questions for specific parts.


Example

I have a script with useful functions which is meant to be sourced and used anywhere.

/tmp/useful_functions.sh

#!/bin/bash

function example()
{
    echo "Main script started"

    # Make process run forever in the background.
    ( while sleep 1; do echo "Background script running"; done ) &

    # Make subshell work for 10 seconds.
    local output="$(sleep 10; echo "Subshell completed")"
    echo "${output}"

    # Kill the backgrounded process once the subshell has finished.
    { kill "${!}" && wait "${!}"; } &> /dev/null

    echo "Main script completed"
}

Test 1

Lets use the example function in another script.

/tmp/test.sh

#!/bin/bash

source /tmp/useful_functions.sh

example

Now lets run /tmp/test.sh and press Control-C while subshell is working.
(subshell = $(sleep 10; echo "Subshell completed"))

Results from Test 1

/tmp/test.sh, example, subshell and background processes were terminated.
Prompt was waiting for new input.

Questions

  1. In which order was SIGINT sent/handled/propagated between processes?
    (Mentioned post claims that SIGINT should be sent to all foreground processes and that the most-inner one should first handle it.)
  2. Why was the backgrounded process terminated?

Test 2

Lets use the example function directly in interactive Bash:

...$ source /tmp/useful_functions.sh
...$ example

and again press Control-C while subshell is working.
(subshell = $(sleep 10; echo "Subshell completed"))

Results from Test 2

example and subshell processes were terminated, but backgrounded process stayed alive.
Prompt was waiting for new input, even though output from backgrounded process was printed.

Questions

  1. In which order was SIGINT sent/handled/propagated between processes?
  2. Why was the backgrounded process NOT terminated now?

Improved example

Even though I don't totaly understand the behavior from Test 1, that behavior is desired one and I wanted to acomplish that with interactive Bash as well.
Idea was to create another subshell which wraps the backgrounded process and the existing subshell and hope that termination of that new procces will lead to termination of the backgrounded process and existing subshell.

/tmp/useful_functions.sh

#!/bin/bash

function example()
{
    (
        echo "Main script started"

        # Make process run forever in the background.
        ( while sleep 1; do echo "Background script running"; done ) &

        # Make subshell work for 10 seconds.
        local output="$(sleep 10; echo "Subshell completed")"
        echo "${output}"

        # Kill the backgrounded process once the subshell has finished.
        { kill "${!}" && wait "${!}"; } &> /dev/null

        echo "Main script completed"
    )
}

Results from repeated Test 1

/tmp/test.sh, example, outer subshell, inner subshell and background processes were terminated.
Prompt was waiting for new input.

Questions

  1. In which order was SIGINT sent/handled/propagated between processes?
  2. Why was the backgrounded process terminated?

Results from repeated Test 2

example, outer subshell, inner subshell and background processes were terminated.
Prompt was waiting for new input.

Questions

  1. In which order was SIGINT sent/handled/propagated between processes?
  2. Why was the backgrounded process terminated?

Problem

At this point I realised that I actually have to do some cleanup after the backgrounded process and that I need to trap SIGINT somewhere to handle it.

Solution

Because I don't know how SIGINT is handled and propagated generally in Bash, I made a few educated guesses and came up with solution which I think works properly, but can't be sure.

#!/bin/bash

function example()
{
    (
        echo "Main script started"

        # Make process run forever in the background.
        (
            trap "echo Cleanup; trap - SIGINT; kill -s SIGINT ${$}" SIGINT
            while sleep 1; do echo "Background script running"; done
        ) &

        # Make subshell work for 10 seconds.
        local output="$(sleep 10; echo "Subshell completed")"
        echo "${output}"

        # Kill the backgrounded process once the subshell has finished.
        { kill "${!}" && wait "${!}"; } &> /dev/null

        echo "Main script completed"
    )
}

Questions

  1. Does my trap properly handle/propagate SIGINT?
  2. Is ${$} the correct process?
    (In mentioned post, it is said that the process should kill itslef, but their example uses ${$} instead of ${BASHPID}.)
Iskustvo
  • 957
  • Looong post. Bottom line, you're trying to figure out how to trap the foreground app + anything that was backgrounded? – slm Aug 05 '18 at 22:11
  • @slm Yeah, sorry :D Well, this question is mostly about general understanding of the concept. There are few things I don't understand. 1) The most important one is the order of termination(handling of SIGINT) for [non-]interactive bash. 2) I don't even know why that backgrounded process is killed on SIGINT? 3) Why use $$ instead of $BASHPID? How would it work in one case, and how in the other? – Iskustvo Aug 05 '18 at 22:24
  • With traps there isn't really any order, it's more like pub/sub, so if at any time your script receives whatever sigs it's trapping, it'll run whatever is defined in it's call. https://unix.stackexchange.com/questions/17314/what-is-signal-0-in-a-trap-command/169915#169915. The trap is from the perspective of the script, so it would have to do killing/handling of the backgrounded jobs itself, the child jobs aren't really aware of the trap. – slm Aug 05 '18 at 22:26
  • Thank you. Child killing is the thing that bothers me. Since my inner subshell does the cleanup and kills itself, I expect that outer subshell will also kill itself with default handling and this will be continued up the call stack, until interactive shell is reached, which will ignore SIGINT. But why and when would it be decided that backgrounded process should also be killed? Further more, why does this differ if backgrounded process is direct child of interactive shell or not? Does it has to do something with job control? – Iskustvo Aug 05 '18 at 22:58
  • Job control and connectivity to the parent's STDIN/STDOUT. If they are nohup or disowned then they'll ignore it. – slm Aug 06 '18 at 02:16
  • Ah, thanks. Can you answer the question officially so I can accept it? – Iskustvo Aug 06 '18 at 19:46

0 Answers0