2

I have a script which works great in bash 4.3, but gives me unexpected behavior with bash 3.2. Here's a simplified version:

set -o errexit -o pipefail

task() {
    local name=${1}
    local duration=${2}
    trap 'echo "[${SECONDS} secs] ${name}: SIGINT"; exit 255' INT
    echo "[${SECONDS} secs] ${name}: Running"
    sleep "${duration}"
    echo "[${SECONDS} secs] ${name}: Done"
}

trap 'echo "[${SECONDS} secs] SIGINT"; exit 255' INT
task 'Task 1' 5 &
task 'Task 2' 5 &
wait
echo "[${SECONDS} secs] Done"

Here's the output when run with bash 4.3 (4.3.42(1)-release) after CTRL-C'ing two seconds into it:

[0 secs] Task 1: Running
[0 secs] Task 2: Running
^C[2 secs] SIGINT
[2 secs] Task 2: SIGINT
[2 secs] Task 1: SIGINT
prompt>

Same thing but with bash 3.2 (3.2.57(1)-release):

[0 secs] Task 1: Running
[0 secs] Task 2: Running
^C[2 secs] SIGINT
prompt> [5 secs] Task 2: Done
[5 secs] Task 1: Done

Are there known issues preventing the above script from working correctly under bash 3.2? Do workarounds exist?

Here are a few things I've tried:

  • No signal handler in the parent:

    # bash 4.3
    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C[2 secs] Task 2: SIGINT
    [2 secs] Task 1: SIGINT
    prompt>
    
    # bash 3.2
    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C
    prompt> [5 secs] Task 2: Done
    [5 secs] Task 1: Done
    
  • No signal handlers at all:

    # bash 4.3
    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C
    prompt>
    
    # bash 3.2
    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C
    prompt> [5 secs] Task 2: Done
    [5 secs] Task 1: Done
    
  • Signal handler in parent that kills the process group with SIGINT (kill -INT -- -$$):

    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C[2 secs] SIGINT
    [2 secs] Task 2: SIGINT
    [2 secs] Task 1: SIGINT
    prompt>
    
    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C[2 secs] SIGINT
    prompt> [5 secs] Task 2: Done
    [5 secs] Task 1: Done
    
  • Signal handler in parent that kill the process group with SIGTERM (tasks trap SIGTERM):

    # bash 4.3
    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C[2 secs] SIGINT
    [2]    92813 terminated  bash minimal_example.sh
    prompt>
    
    # bash 3.2
    [0 secs] Task 1: Running
    [0 secs] Task 2: Running
    ^C[2 secs] SIGINT
    Terminated: 15
    Terminated: 15
    [2 secs] Task 2: SIGTERM
    [2 secs] Task 1: SIGTERM
    [1]    92836 terminated  /bin/bash minimal_example.sh
    prompt>
    

The last is the closest to working properly in 3.2, but that same code behaves differently in 4.3.

2

It may be that you are a victim of a well known bash problem that frequently hits make users.

I did not yet check bash 4, but bash 3 incorrectly does jobcontrol inside scripts. This usually causes makefiles that contain a loop over several subdirectories not to be easily killable by ^C because the sub processes run in separate process groups even though these commands are not interactive commands.

smake includes a workaround for /bin/sh being bash and explicitely forwards SIGINT to the procress group of the currently running command. But this is software written in C.

The same cannot be implemented with usual shells in scripts as there is no standard UNIX command to retrieve the process group of a child.

  • This may be a different (related?) issue - when I kill the process group with SIGTERM, all of the children are killed as well. This makes me think they're all running in the same process group. – Matt Tardiff Jan 26 '16 at 14:16
  • To be completely sure, I just verified that all of the spawned processes are indeed in the same process group as the parent. – Matt Tardiff Jan 26 '16 at 18:38
  • Are they in the same process group as the shell that runs the script and as the tty? – schily Jan 26 '16 at 22:06
  • They're in the same process group as the shell that runs the script, but not in the same group with the interactive shell. – Matt Tardiff Jan 27 '16 at 5:20
  • It may be that bash 3 ignores SIGINT in shell scripts and continues with the execution. This would be non-conformant behavior. You can verify this by installing a trap for SIGINT that just calls exit. – schily Jan 27 '16 at 9:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.