Search code examples
bashprocesssigint

SIGINT and process substitution in bash


I've found that if I have something like:

#!/usr/bin/env bash

function abort-politely {
  echo 'Aborting politely'
  {
    sleep 5
    touch .stop
  }
}
trap 'abort-politely' SIGINT

{
  while [ ! -r .stop ] ; do echo hello world ; sleep 1 ; done
  rm -f .stop
} &
wait $!

echo Exiting

it behaves as I would expect, which is to say that the background task continues for 5s after the interrupt:

hello world
hello world
hello world
hello world
<Ctrl+C pressed>
Aborting politely
hello world
hello world
hello world
hello world
hello world
Exiting

However, if instead I introduce process substitution as part of the background process...

#!/usr/bin/env bash

function abort-politely {
  echo 'Aborting politely'
  {
    sleep 5
    touch .stop
  }
}
trap 'abort-politely' SIGINT

{
  # The "while" loop below is all that has changed
  while [ ! -r .stop ] && read line; do echo hello $line ; done < <(
    while : ; do echo world ; sleep 1 ; done
  )
  rm -f .stop
} &
wait $!

echo Exiting

...the background process seems to exit immediately when Ctrl+C is pressed:

hello world
hello world
hello world
hello world
<Ctrl+C pressed>
Aborting politely
<5 seconds delay>
Exiting

I had expected the same output as in the first case.

How come this doesn't work as I expected? Is there a way to make it behave as I had hoped? I suppose what I need is for the process substitution to continue until it is no longer being read from. (I wondered if adding trap '' SIGINT inside the process substitution block might be the solution, but then it just exits immediately.)


Solution

  • The SIGINT signal propagates through the subprocesses causing the process substitution shell to abort itself.

    You can avoid it by adding trap : SIGINT:

      while [ ! -r .stop ] && read line; do echo hello $line ; done < <(
        trap : SIGINT
        while : ; do echo world ; sleep 1 ; done
      )