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,stdoutis connected to the write end of a pipe, and the command isexeced.In the parent,
$fhreads 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,
closewaits 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.$? == -1—closecould not even wait for the child, because something went wrong beforefork/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.