bashshellsignalssigintsubshell

Stop subshell with background process from receiving SIGINT despite trapping signals in the foreground


Let us say I have a function that is run in the foreground. This function traps SIGINT and ignores EOF (for preventing Control + C and Control + D). This function creates a subshell that runs a command in the background.

I would think that SIGINT would be caught by the main function. However, using Control + C while running the main function does still result in the subshell receiving SIGINT and being killed before what is expected.

I have also attempted to add the traps for SIGINT and ignoring EOF in the subshell itself, but that did not seem to work either.

Here is a relatively-minimal example that encapsulates the issue, using the mpv command:

function play_video_until_yes {
    trap '' 2
    set -o ignoreeof
    
    videourl="$1"
    read -r mpv_pid < <(mpv --loop "$videourl" --no-video &>/dev/null & echo $!)
    
    while true; do
        read -rp "Input y for yes: " answer
        if [[ "$answer" = "y" ]]; then
            break
        fi
        printf "\nIncorrect. Try again.\n"
    done
    
    kill "$mpv_pid" >/dev/null
    
    trap 2
    set +o ignoreeof
}

One can run this function with a command-line argument of any YouTube video (e.g. play_video_until_yes "https://www.youtube.com/watch?v=usNsCeOV4GM") and press Control + C while the main process is asking for user input. This causes the subshell to quit, presumably due to the SIGINT.


Solution

  • I did a fair amount of research on this and was able to find one answer. It works, but it is less than ideal.

    I had to install a separate package called util-linux via Homebrew on my Mac to get the setsid command. Then, I was able to use that command to run the mpv command in a separate process group to prevent SIGINT from being forwarded to it.

    So, here is what my solution looked like after that:

    function play_video_until_yes {
        trap '' 2
        set -o ignoreeof
    
        videourl="$1"
        read -r mpv_pid < <( /opt/homebrew/opt/util-linux/bin/setsid mpv --loop "$videourl" --no-video &>/dev/null & echo $!)
    
        while true; do
            read -rp "Input y for yes: " answer
            if [[ "$answer" = "y" ]]; then
                break
            fi
            printf "\nIncorrect. Try again.\n"
        done
    
        kill "$mpv_pid" >/dev/null
    
        trap 2
        set +o ignoreeof
    }
    

    I would still prefer a solution that uses commands already present on macOS, but this solution stands its ground for now.