Signal keys such as Ctrl+C send a signal to all processes in the foreground process group.
In the typical case, a process group is a pipeline. For example, in head <somefile | sort
, the process running head
and the process running sort
are in the same process group, as is the shell, so they all receive the signal. When you run a job in the background (somecommand &
), that job is in its own process group, so pressing Ctrl+C doesn't affect it.
The timeout
program places itself in its own process group. From the source code:
/* Ensure we're in our own group so all subprocesses can be killed.
Note we don't just put the child in a separate group as
then we would need to worry about foreground and background groups
and propagating signals between them. */
setpgid (0, 0);
When a timeout occurs, timeout
goes through the simple expedient of killing the process group of which it is a member. Since it has put itself in a separate process group, its parent process will not be in the group. Using a process group here ensures that if the child application forks into several processes, all its processes will receive the signal.
When you run timeout
directly on the command line and press Ctrl+C, the resulting SIGINT is received both by timeout
and by the child process, but not by interactive shell which is timeout
's parent process. When timeout
is called from a script, only the shell running the script receives the signal: timeout
doesn't get it since it's in a different process group.
You can set a signal handler in a shell script with the trap
builtin. Unfortunately, it's not that simple. Consider this:
#!/bin/sh
trap 'echo Interrupted at $(date)' INT
date
timeout 5 sleep 10
date
If you press Ctrl+C after 2 seconds, this still waits the full 5 seconds, then print the “Interrupted” message. That's because the shell refrains from running the trap code while a foreground job is active.
To remedy this, run the job in the background. In the signal handler, call kill
to relay the signal to the timeout
process group.
#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid
Best Answer
The reason why cannot interrupt that with Ctrl-c etc... is that the shell isn't running any command at that point. It's busy expanding
{1..999999}
to compute what the command line arguments will eventually be once it gets to the point of running the command.While external commands respond to termination signals like
SIGINT
(which is emitted by default when you press Ctrl-c), shells themselves ignore them. If they didn't, then, when you pressed Ctrl-c, then in addition to killing whatever command happened to be running, you'd also kill the shell itself! (This is not quite true because of tty job control and foreground and background process groups, but close enough for the purpose of that explanation.)If you need to interrupt it, there is unfortunately nothing you can do but kill the shell itself. Killing the shell itself will cause your session to terminate. In that sense it's largely equivalent to closing the terminal window or terminating the SSH connection.