This question is related to a previous question: perl run background job and force job to finish?
I thought I had a script working that would successfully kill my background job by doing kill 15, $logger_pid
, but it turns out the command creates two jobs. See details below:
#!/usr/bin/perl
use strict;
# Do a background $USER top every minute
my $cmd = "top -b -d 1 -n 300 -u $ENV{USER} >> logfile";
$SIG{CHLD}="IGNORE"; my $logger_pid;
unless ($logger_pid=fork) { exec($cmd); }
# This actually creates two jobs with consecutive PIDs
# $USER 5609 0.0 0.0 63820 1072 pts/9 S 09:42 0:00 sh -c top -b -d 1 -n 300 -u $USER >> logfile
# $USER 5610 0.6 0.0 10860 1216 pts/9 S 09:42 0:00 top -b -d 1 -n 300 -u $USER
# Do something for a while
foreach my $count (1..5) { print "$count\n"; sleep 1; }
# I thought this first kill command was enough
kill 15, $logger_pid;
# This line is needed to kill the child job
kill 15, ($logger_pid+1);
1;
Can someone enlighten me on why I need a second kill 15, ($logger-pid+1)
to actually kill the background job? Is there any way to do this with one single kill
statement?
The system using sh -c ...
to execute an external command with shell metacharacters (in your case, the >>
for output redirection, and for that matter any spaces in the command string) is documented in exec
and system
. To avoid using sh -c
and to create a single process, you can use the LIST
form of exec
:
my @cmd = ('top','-b','-d','1','-n','300','-u',$ENV{USER});
exec(@cmd);
The output redirection makes this a little tricky, but you can accomplish it by closing and reopening STDOUT
after the fork
and before the exec
:
my @cmd = ...
close STDOUT;
open STDOUT, '>>', 'logfile';
exec @cmd;
When doing output redirection, the important thing is to set up file descriptor 1 correctly, not necessarily STDOUT
. This, for example, will fail to do output redirection like you want it to.
my @cmd = ...;
close STDOUT; # close file descriptor 1
open FOO, '>/tmp/asdf'; # probably uses file descriptor 1
open STDOUT, '>>', 'logfile'; # probably uses file descriptor != 1
exec(@cmd); # probably writes output to FOO\/tmp/asdf
Alternate solution using process groups: @Chris suggests in a comment that you send a negative value to the kill
function in order to kill an entire process group. Normally the process you create with fork
has the same process group as its parent, but this will work if you set up the child process to use a new process group:
unless ($logger_pid = fork) {
use POSIX;
POSIX::setpgid($$,$$); # set child process to be in its own process group
exec($cmd);
}
...
kill -15, $logger_pid; # kill the process group with id $logger_pid