Search code examples
bashshellcommandbash-trapcommand-substitution

Trap with external call in command substitution breaks the parent Bash shell


I have a text-based user interface script that allows me to browse directories and select a file. The graphics are output to stderr, the chosen file's path is sent to stdout. This allows to get the chosen file this way:

file="$(./script)"

This is very handy, as command substitution only grabs stdout.

But I need my script to handle signals, so that when the script is interrupted, it can reset the display. I set up a trap that handles the INT signal. To simulate what it's doing, consider the following script:

catch() { 
    echo "caught"
    ps # Calling an external command
    exit
}

trap catch INT

while read -sN1; do # Reading from the keyboard
    echo $REPLY >&2
done

The script is then called with var="$(./script)". Now, if you send the INT signal by hitting ^C, the parent shell breaks: Anything you type (including control characters) will be printed out until you hit return, then none of your inputs will be shown.

Removing the external command call in the catch function seems to fix the issue (still, the echo doesn't seem to work), but I don't understand why, and I can't do without it in my final script.

Is there something I'm missing? Why does this breaks the parent shell?


Solution

  • As other users seemed to agree that this is a bug, I filed a bug report. I got the following answer:

    This is a race condition -- the parent shell handles the SIGINT before it should. This will be fixed in the next devel branch push.

    So the best thing to do here is to keep an eye out on Bash's git.

    As a "fix", I had to refactor the script to be sourced (. script.sh), so that it could communicate with the caller without involving temporary files, as process substitution resulted in the exact same behavior than command substitution.