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.
#!/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.
#!/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.
#!/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.
#!/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
- Use the list form:
system('cmd', $arg)andopenwith several arguments avoid the shell and its quoting problems. - Check every status: test the return value of
systemandclose, and read$?. - Always reap children: call
waitpidfor every forked process to avoid zombies. - Stream large output: read a pipe line by line rather than capturing a huge command output into one string.
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
List all Perl tutorials.