views:

1427

answers:

4

Perl's system() starts a process, but breaks the parent/child relationship?

test.pl:

use POSIX;

system("./test.sh &");

my $pid = `ps -C test.sh -o pid=`;

print "pid: -$pid-\n";

waitpid($pid, 0);

test.sh:

while true
do
    sleep 1
done

When I run test.pl, it finds and prints a correct pid of test.sh. But waitpid() returns -1 and test.pl exits. After test.pl exist, test.sh is still running.

It looks like test.sh is not a child of test.pl, which breaks waitpid(). Why does this happen and how to make system() behave? Is that because Perl clears children automatically? If yes, how can I solve a general task of waiting on a child explicitly?

Update:

answers below suggest using fork/exec. The initial problem is this:

  1. from a Perl script, run a command-line utility that starts a service. The utility exits but the service stays.

  2. after some time, find that service's pid and wait on it.

fork/exec doesn't solve this, although it clears up the question.

+13  A: 

The test.sh process is not your child process. The system() forked a shell (which is your child), that shell forked a child that ran the test.sh program. The shell that was your child exited.

Liudvikas Bukys
Just curious: does waitpid exit because the given pid isn't one of it's children?
Paul Tomblin
Yes. The return value of -1 indicates just that: no such child process
innaM
+5  A: 

To further explain Liudvikas' answer -

system("./test.sh &")
 |
 |--> (P) /bin/sh (to run test.sh)
       |
       |--> (P) test.sh & (still running)

(P) - process

After fork'ing and running the test.sh script the /bin/sh shell, which was the child of the Perl system call, exits and so you get a -1 return value from waitpid().

muteW
+4  A: 

What you probably want to do is something like this:

my $pid = fork || exec './test.sh';
print "pid: -$pid-\n";
waitpid($pid, 0);

Though since the shell script is in an infinite loop, it will wait forever.

Leon Timmermans
+5  A: 

In general, you should manually fork and exec if you don't want Perl to help you out. It's hard to determine exactly what you are doing, but I think you want this:

my $pid = fork;
unless($pid){
    # child;
    exec(qw/sh test.sh/);
}

# parent
...
waitpid $pid, 0;

Personally, I prefer to let AnyEvent babysit:

my $done = AnyEvent->condvar;

my $pid = fork;

unless( $pid ) { ... }

my $w = AnyEvent->child (
   pid => $pid,
   cb  => sub {
      my ($pid, $status) = @_;
      warn "pid $pid exited with status $status";
      $done->send;
   },
);

$done->recv; # control resumes here when child exits

Or, more generally: http://github.com/jrockway/anyevent-subprocess/tree/master

jrockway
I've never seen AnyEvent before. Is it a standard part of most distros, or do I need to download it from CPAN?
Paul Tomblin
CPAN. (POE is another option.)
jrockway