52

I'm trying to execute commands inside a script using read, and when the user uses Ctrl+C, I want to stop the execution of the command, but not exit the script. Something like this:

#!/bin/bash

input=$1
while [ "$input" != finish ]
do
    read -t 10 input
    trap 'continue' 2
    bash -c "$input"
done
unset input

When the user uses Ctrl+C, I want it to continue reading the input and executing other commands. The problem is that when I use a command like:

while (true) do echo "Hello!"; done;

It doesn't work after I type Ctrl+C one time, but it works once I type it several times.

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
mar_sanbas
  • 833
  • 2
  • 12
  • 20

3 Answers3

47

Use the following code :

#!/bin/bash
# type "finish" to exit

stty -echoctl # hide ^C

# function called by trap
other_commands() {
    tput setaf 1
    printf "\rSIGINT caught      "
    tput sgr0
    sleep 1
    printf "\rType a command >>> "
}

trap 'other_commands' SIGINT

input="$@"

while true; do
    printf "\rType a command >>> "
    read input
    [[ $input == finish ]] && break
    bash -c "$input"
done
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • It still doesn't work for one ctrl+C, i have to type it 3 or 4 times to stop printing Hello – mar_sanbas Oct 07 '12 at 19:30
  • 2
    I don't get you, `ctrl+C` is trapped like you asked. Instead of quitting, it prints some strings on `STDOUT`. – Gilles Quénot Oct 07 '12 at 19:32
  • I had the same problem as mar_sanbas and this works beautifully, thanks for the answer. On a Mac, running Bash, one ctrl+C is trapped immediately. – Blisterpeanuts Mar 19 '21 at 03:21
  • Important info, some noobs might miss (I did): [`SIGINT` is what you get, if you press `Ctrl+C`](https://unix.stackexchange.com/a/332374/498585). – Bazer Con Nov 03 '21 at 12:13
  • note: in some shells (e.g. dash) `trap other_commands SIGINT` will result in an error message (`SIGINT: bad trap`). Use `trap other_commands INT` instead. – Erik Lievaart May 15 '23 at 15:08
17

You need to run the command in a different process group, and the easiest way of doing that is to use job control:

#!/bin/bash 

# Enable job control
set -m

while :
do
    read -t 10 -p "input> " input
    [[ $input == finish ]] && break

    # set SIGINT to default action
    trap - SIGINT

    # Run the command in background
    bash -c "$input" &

    # Set our signal mask to ignore SIGINT
    trap "" SIGINT

    # Move the command back-into foreground
    fg %-

done 
cdarke
  • 42,728
  • 8
  • 80
  • 84
  • Important info, some noobs might miss (I did): [`SIGINT` is what you get, if you press `Ctrl+C`](https://unix.stackexchange.com/a/332374/498585). – Bazer Con Nov 03 '21 at 12:13
13

For bash :

#!/bin/bash

trap ctrl_c INT

function ctrl_c() {
        echo "Ctrl + C happened"
}

For sh:

#!/bin/sh

trap ctrl_c INT

ctrl_c () {
    echo "Ctrl + C happened"
}
Doctor
  • 7,115
  • 4
  • 37
  • 55