Question

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?

Was it helpful?

Solution

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top