ZetCode

Perl processes

last modified October 20, 2026

Perl programs often need to run other programs: a shell command, a converter, or a worker process. Perl offers system to run a command and wait for it, backticks and qx to capture its output, open with a pipe to stream data to and from it, and fork to create a copy of the running program. In this article we use each of these with complete examples.

Running a command with system

The system function runs a command and waits for it to finish. It returns the exit status in the same form as $?, where the real exit code is stored in the upper eight bits. Passing the command and each argument as separate list items avoids the shell and is the safe form when the arguments come from user input.

system_call.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $rc = system('echo', 'hello from child');
say "system returned: $rc";
say "exit status:     ", $? >> 8;

my $fail = system('sh', '-c', 'exit 3');
say "failing exit:    ", $? >> 8;

The first call succeeds, so system returns 0. The second command exits with status 3, and $? >> 8 extracts that code. A non-zero return value always means the child failed, so check it before trusting the output.

$ perl system_call.pl
hello from child
system returned: 0
exit status:     0
failing exit:    3

Capturing output with backticks and qx

Backticks, or the equivalent qx operator, run a command and return its standard output. In scalar context the whole output comes back as one string, and in list context it comes back as one element per line.

capture.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my $out = `echo captured output`;
chomp $out;
say "backticks got: $out";

my @lines = qx(printf 'a\\nb\\nc\\n');
chomp @lines;
say "qx lines:      @lines";

Backticks pass the string through the shell, so the command is interpreted by /bin/sh. The trailing newline is kept in the result, which is why the example calls chomp. The output of a command that writes to standard error is not captured; redirect it with 2>&1 inside the command when it is needed.

$ perl capture.pl
backticks got: captured output
qx lines:      a b c

Reading and writing pipes with open

Opening a command as a filehandle streams its output or input without collecting everything in memory first. The mode -| reads from the command, and |- writes to it. The list form of open passes the arguments directly, without a shell.

pipes.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

open my $ph, '-|', 'printf', "one\ntwo\nthree\n" or die "open failed: $!";
my @rows = <$ph>;
close $ph;
chomp @rows;
say "read: @rows";
say "count: ", scalar @rows;

open my $wh, '|-', 'sort -r' or die "open failed: $!";
print {$wh} "apple\nbanana\ncherry\n";
close $wh;

The read pipe collects three lines into an array, and the write pipe sends its input to sort -r, which prints the lines in reverse order when the handle is closed. Closing a pipe waits for the command to finish and sets $?, so a failing command is detected only at close.

$ perl pipes.pl
read: one two three
count: 3
cherry
banana
apple

Creating child processes with fork and waitpid

fork duplicates the running program. It returns the child's process ID in the parent, 0 in the child, and undef on failure. The child can do its work and exit, while the parent collects the result with waitpid. Forking does not share variables after the split: each process has its own copy of memory.

fork_workers.pl
#!/usr/bin/perl

use strict;
use warnings;
use v5.34.0;

my @pids;

for my $n (1 .. 3) {
    my $pid = fork();
    die "fork failed: $!" unless defined $pid;

    if ($pid == 0) {
        exit($n * 10);
    }

    push @pids, $pid;
}

for my $pid (@pids) {
    waitpid($pid, 0);
    say "child exit: ", $? >> 8;
}

Each child exits with a different code, and the parent reads them back in the order it started them. Without waitpid the children would become zombie processes until the parent itself exits. The order in which children finish is not guaranteed, so the parent should not assume it.

$ perl fork_workers.pl
child exit: 10
child exit: 20
child exit: 30

Best Practices

In this article we have covered system, backticks and qx, pipes opened with open, and fork with waitpid in Perl.

Source

Perl built-in functions ยท perlipc

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.