There are several ways you can cut off the effect of Ctrl+C:
- Change the terminal setting so that it doesn't generate a signal.
- Block the signal so that it is saved for later delivery, when the signal becomes unblocked.
- Ignore the signal, or set a handler for it.
- Run subprocesses in a background process group.
Since you want to detect that Ctrl+C has been pressed, ignoring the signal is out. You could change the terminal settings, but then you would need to write custom key processing code. Shells don't provide access to signal blocking.
You can however isolate subprocesses from receiving the signal automatically by running them in a separate process group. Interactive shells run background commands in a separate process group by default, but non-interactive shells run them in the same process group, and all processes in the foreground process group receive a signal from terminal events. To tell the shell to run background jobs in a separate process group, run set -m
. Running setsid ping …
is another way of forcing ping
to run in a separate process group.
set -m
interrupted=
trap 'echo Interrupted, but ping may still be running' INT
set -m
ping … &
while wait; [ $? -ge 128 ]; do echo "Waiting for background jobs"; done
echo ping has finished
If you want Ctrl+Z to suspend a background process group, you'll need to propagate the signal from the shell.
Controlling signals finely is a bit of a stretch for a shell script, and shells other than ATT ksh tend to be a little buggy when you reach the corner cases, so consider a language that gives you more control such as Perl, Python or Ruby.
bash
shouldn't print the job status when non-interactive.
If that's indeed for an interactive bash
, you can do:
{ pid=$(sleep 20 >&3 3>&- & echo "$!"); } 3>&1
We want sleep
's stdout to go to where it was before, not the pipe that feeds the $pid
variable. So we save the outer stdout in the file descriptor 3 (3>&1
) and restore it for sleep
inside the command substitution. So pid=$(...)
returns as soon as echo
terminates because there's nothing left with an open file descriptor to the pipe that feeds $pid
.
However note that because it's started from a subshell (here in a command substitution), that sleep
will not run in a separate process group. So it's not the same as running sleep 20 &
with regards to I/O to the terminal for instance.
Maybe better would be to use a shell that supports spawning disowned background jobs like zsh
where you can do:
sleep 20 &! pid=$!
With bash
, you can approximate it with:
{ sleep 20 2>&3 3>&- & } 3>&2 2> /dev/null; pid=$!; disown "$pid"
bash
outputs the [1] 21578
to stderr. So again, we save stderr before redirecting to /dev/null, and restore it for the sleep
command. That way, the [1] 21578
goes to /dev/null
but sleep
's stderr goes as usual.
If you're going to redirect everything to /dev/null anyway, you can simply do:
{ apt-get update & } > /dev/null 2>&1; pid=$!; disown "$pid"
To redirect only stdout:
{ apt-get-update 2>&3 3>&- & } 3>&2 > /dev/null 2>&1; pid=$!; disown "$pid"
Best Answer
I don't think that's available in ksh. There's a POSIX solution which involves running an external process:
On Linux,
readlink /proc/self
would also work, but I fail to see any advantage (it might be marginally faster; it could be useful on a BusyBox variant that hasreadlink
but not$PPID
, but I don't think there is one).Note that in order to get the value in the shell, you need to be careful not to run that command in a short-lived sub-sub-shell. For example,
p=$(sh -c 'echo $PPID')
might show the output of the subshell that invokessh
within the command substitution (or it might not, some shells optimize that case). Instead, run