Best would be to use the timeout
command if you have it which is meant for that:
timeout 86400 cmd
The current (8.23) GNU implementation at least works by using alarm()
or equivalent while waiting for the child process. It does not seem to be guarding against the SIGALRM
being delivered in between waitpid()
returning and timeout
exiting (effectively cancelling that alarm). During that small window, timeout
may even write messages on stderr (for instance if the child dumped a core) which would further enlarge that race window (indefinitely if stderr is a full pipe for instance).
I personally can live with that limitation (which probably will be fixed in a future version). timeout
will also take extra care to report the correct exit status, handle other corner cases (like SIGALRM blocked/ignored on startup, handle other signals...) better than you'd probably manage to do by hand.
As an approximation, you could write it in perl
like:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
wait;
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
} else {exec @ARGV}' cmd
There's a timelimit
command at http://devel.ringlet.net/sysutils/timelimit/ (predates GNU timeout
by a few months).
timelimit -t 86400 cmd
That one uses an alarm()
-like mechanism but installs a handler on SIGCHLD
(ignoring stopped children) to detect the child dying. It also cancels the alarm before running waitpid()
(that doesn't cancel the delivery of SIGALRM
if it was pending, but the way it's written, I can't see it being a problem) and kills before calling waitpid()
(so can't kill a reused pid).
netpipes also has a timelimit
command. That one predates all the other ones by decades, takes yet another approach, but doesn't work properly for stopped commands and returns a 1
exit status upon timeout.
As a more direct answer to your question, you could do something like:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
That is, check that the process is still a child of ours. Again, there's a small race window (in between ps
retrieving the status of that process and kill
killing it) during which the process could die and its pid be reused by another process.
With some shells (zsh
, bash
, mksh
), you can pass job specs instead of pids.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
That only works if you spawn only one background job (otherwise getting the right jobspec is not always possible reliably).
If that's an issue, just start a new shell instance:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
That works because the shell removes the job from the job table upon the child dying. Here, there should not be any race window since by the time the shell calls kill()
, either the SIGCHLD signal has not been handled and the pid can't be reused (since it has not been waited for), or it has been handled and the job has been removed from the process table (and kill
would report an error). bash
's kill
at least blocks SIGCHLD before it accesses its job table to expand the %
and unblocks it after the kill()
.
Another option to avoid having that sleep
process hanging around even after cmd
has died, with bash
or ksh93
is to use a pipe with read -t
instead of sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
That one still has race conditions, and you lose the command's exit status. It also assumes cmd
doesn't close its fd 4.
You could try implementing a race-free solution in perl
like:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{CHLD} = sub {
$ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new;
sigprocmask(SIG_BLOCK, $ss, $oss);
waitpid($p,WNOHANG);
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
unless $? == -1;
sigprocmask(SIG_UNBLOCK, $oss);
};
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
pause while 1;
} else {exec @ARGV}' cmd args...
(though it would need to be improved to handle other types of corner cases).
Another race-free method could be using process groups:
set -m
((sleep 86400; kill 0) & exec cmd)
However note that using process groups can have side-effects if there's I/O to a terminal device involved. It has the additional benefit though to kill all the other extra processes spawned by cmd
.
The gedit
process is already terminated.
Remember how Windows applications mainly worked back in the Win16 days before Win32 came along and did away with it: where there were hInstance
and hPrevInstance
, attempting to run a second instance of many applications simply handed things over to the first instance, and this made things difficult for command scripting tools (like Take Command) because one would invoke an application a second time, it would visibly be there on the screen as an added window, but as far as the command interpreter was concerned the child process that it had just run immediately exited?
Well GNOME has brought the Win16 behaviour back for Linux.
With GIO applications like gedit
, the application behaves as follows:
- If there's no registered "server" named
org.gnome.gedit
already on the per-user/per-login Desktop Bus, gedit
decides that it's the first instance. It becomes the org.gnome.gedit
server and continues to run.
- If there is a registered "server" named
org.gnome.gedit
already on the per-user/per-login Desktop Bus, gedit
decides that it's a second or subsequent instance. It constructs Desktop Bus messages to the first instance, passing along its command line options and arguments, and then simply exits.
So what you see depends from whether you have the gedit
server already running. If you haven't, you'll be in sebvieira's shoes and wondering why you aren't seeing the behaviour described. If you have, you'll be in your shoes and seeing the gedit
process terminating almost immediately, especially since you haven't given it any command-line options or arguments to send over to the "first instance". Hence the reason that there's no longer a process with that ID.
Much fun ensues when, as alluded to above, the per-login Desktop Bus is switched to the "new" style of a per-user Desktop Bus, and suddenly there's not a 1:1 relationship between a Desktop Bus and an X display any more. Single user-bus-wide instance applications suddenly have to be capable of talking to multiple X displays concurrently.
Further hilarity ensues when people attempt to run gedit
as the superuser via sudo
, as it either cannot connect to a per-user Desktop Bus or connects to the wrong (the superuser's) Desktop Bus.
There's a proposal to give gedit
a command-line option that makes the process that is invoked just be the actual editor application, so that gedit
would be useful as the editor pointed-to by the EDITOR
environment variable (which it isn't for many common usages of EDITOR
, from crontab
to git
, when it just exits immediately). This proposal has not become reality yet.
In the meantime, people have various ways of having a simple second instance of a "lightweight text editor", such as invoking a whole new Desktop Bus instance, private to the invocation of gedit
, with dbus-run-session
. Of course, this tends to spin up other GNOME Desktop Bus servers on this private bus as they are in turn invoked by gedit
, making it not "lightweight" at all.
The icing on the cake is when you've followed this recommendation or one like it and interposed a shell function named gedit
that immediately removes the gedit
process from the shell's list of jobs. Not only does the process terminate rapidly so that you don't see it later with kill
or ps
, but the shell doesn't even monitor it as a shell-controlled job.
Further reading
Best Answer
Probably there is a parent process which kills child processes and forks new children. You can use pstree to find the parent process:
Or alternatively you can use the ppid option of ps:
Then you can kill the parent process