Processes, Signals and IPC

36.1 Running External Commands

use v5.36;
system('ls', '-l', '/tmp') == 0 or die "ls failed: $?";   # list form: no shell (safe)
my $exit = $? >> 8;
my $output = `uname -a`;                                  # backticks: capture stdout (uses the shell)
my @lines = qx(git log --oneline -5);
exec('vim', 'file.txt');                                  # replace the current process (never returns)

# Pipes
open my $ps, '-|', 'ps', 'aux' or die $!;                 # read from a command (list form, no shell)
while (<$ps>) { print if /perl/ }
close $ps;
open my $mail, '|-', '/usr/sbin/sendmail', '-t' or die $!; # write to a command

# Capture stdout, stderr, and the exit code safely (CPAN)
use Capture::Tiny qw(capture);
my ($stdout, $stderr, $exit_code) = capture { system('make', 'test') };
use IPC::Run3; run3(['grep', 'x'], \$in, \$out, \$err);
use IPC::Cmd qw(run can_run);                             # core
use IPC::Open3;                                           # core, low level

36.2 fork, wait and Signals

my $pid = fork() // die "fork failed: $!";
if ($pid == 0) {                       # child
    say "Child $$ working";
    exit 0;
}
waitpid($pid, 0);                      # parent waits
say "Child exited with ", $? >> 8;

$SIG{INT}  = sub { say "Caught SIGINT, cleaning up"; exit 1 };
$SIG{TERM} = 'IGNORE';
eval {                                 # timeout pattern
    local $SIG{ALRM} = sub { die "timeout\n" };
    alarm 5;
    long_operation();
    alarm 0;
    1;
} or do {
    die $@ unless $@ eq "timeout\n";   # rethrow unrelated errors
    warn "Timed out\n";
};
$SIG{CHLD} = 'IGNORE';                 # auto-reap children (on most Unix systems)
kill 'TERM', $pid;
local $SIG{__WARN__} = sub { ... };

36.3 IPC Mechanisms

Pipes (pipe(my $r, my $w)), socketpairs, named pipes (mkfifo), System V IPC (IPC::SysV, shared memory, semaphores), files with flock, Unix domain sockets, message queues (Redis, RabbitMQ via Net::AMQP::RabbitMQ, ZeroMQ via ZMQ::FFI).

36.4 Daemons

use POSIX qw(setsid);
sub daemonize {
    chdir '/';
    open STDIN, '<', '/dev/null'; open STDOUT, '>', '/dev/null';
    defined(my $pid = fork) or die "Can't fork: $!";
    exit if $pid;
    setsid() or die "Can't start a new session: $!";
    open STDERR, '>&', \*STDOUT;
}
# Modern approach: don't daemonize yourself; run in the foreground under systemd/supervisord/Docker.
# CPAN: Proc::Daemon, Daemon::Control, Proc::PID::File