Pipes#

The same open that attaches a handle to a file can attach it to a child process. Two modes select the direction:

  • "-|" — you read the child’s standard output.

  • "|-" — you write to the child’s standard input.

In both cases the child runs concurrently with your Perl program, and its exit status becomes available in $? when the handle is closed.

Reading from a command#

open my $fh, "-|", "sort", "-u", glob("unsorted/*.txt")
    or die "pipe: $!";

while (my $line = <$fh>) {
    # ... process one sorted unique line at a time ...
}

close $fh or die pipe_close_diag($?, $!);

What happens:

  • Perl forks. In the child, stdout is connected to the write end of a pipe, and the command is execed.

  • In the parent, $fh reads from the other end. As far as your code is concerned, it is a normal read handle — readline, read, <$fh>, and so on all work.

  • When you finish reading, close waits for the child to exit and records its status in $?.

Writing to a command#

open my $fh, "|-", "gzip", "-9", "-c"
    or die "pipe: $!";

for my $line (@records) {
    print $fh $line, "\n";
}

close $fh or die pipe_close_diag($?, $!);

Symmetry: the child’s stdin is connected to the read end of the pipe; your parent writes to the other end; close waits for the child to exit.

One behaviour that often surprises people: close on a write-pipe flushes everything buffered, shuts the pipe’s write end, and only then waits for the child. If the child is slow, close blocks until the child is done. That is almost always what you want.

List form versus string form#

Both open calls above used the list form:

open my $fh, "-|", "sort", "-u", glob("unsorted/*.txt")  or die;

Perl recognises that the third and subsequent arguments form a command and its argv. It does not involve a shell:

  • No word splitting, no glob expansion, no redirection, no variable substitution.

  • A filename containing a space or a ; is just a filename.

  • There is no shell to mis-quote against, so the class of bugs known as “shell injection” is structurally impossible.

The alternative is the string form:

open my $fh, "-|", "sort -u unsorted/*.txt"  or die;

Perl passes the single string to /bin/sh -c. The shell does word splitting, glob expansion, redirection, everything. That is what makes this form useful — you can write "cat -n > numbered.txt" on the command side and the shell sets up the > for you — and what makes it dangerous when any piece of the string came from outside your program.

Rule of thumb: list form by default, string form only when a shell feature is the point. When a filename glob is the reason to use a shell, consider pre-expanding the glob with Perl’s own glob built-in and staying in list form, as the example at the top of this page did.

Decoding $?#

After a pipe is closed, $? is set the same way wait(2) returns: a 16-bit value with the low byte carrying signal information and the high byte carrying the exit code. In Perl terms:

  • $? == 0 — the child exited cleanly with status 0. Success.

  • $? == -1close could not even wait for the child, because something went wrong before fork/exec. $! has detail.

  • $? & 0x7F — signal number if the child died on a signal. $? & 0x80 (the high bit of the low byte) is set if the child dumped core.

  • $? >> 8 — exit status if the child exited normally.

A diagnostic helper pulls these apart:

sub pipe_close_diag {
    my ($status, $err) = @_;
    return "pipe close: $err"               if $status == -1;
    my $signal = $status & 0x7F;
    return "child died on signal $signal"   if $signal;
    my $exit   = $status >> 8;
    return "child exited with status $exit" if $exit;
    return "child exited cleanly";
}

Pair that with every pipe close:

close $fh or die pipe_close_diag($?, $!);

Not checking the close on a pipe is the same mistake as not checking it on a write handle — except louder, because a child process failed silently in the background.

One command, one pipe#

open with -| or |- gives you a pipe in one direction. There is no standard way to get both directions from a single open call, because two-way pipes to a co-process require careful buffering and deadlock avoidance that the open shape cannot express. When you need that, reach for IPC::Open2 or IPC::Open3 — built for the job, with the handshake caveats documented in their own POD.

Next#

The third kind of thing open can attach a handle to is neither a file nor a process — it is a string in memory. See In-memory handles.