📄 ch16_03.htm
字号:
<html><head><title>Pipes (Programming Perl)</title><!-- STYLESHEET --><link rel="stylesheet" type="text/css" href="../style/style1.css"><!-- METADATA --><!--Dublin Core Metadata--><meta name="DC.Creator" content=""><meta name="DC.Date" content=""><meta name="DC.Format" content="text/xml" scheme="MIME"><meta name="DC.Generator" content="XSLT stylesheet, xt by James Clark"><meta name="DC.Identifier" content=""><meta name="DC.Language" content="en-US"><meta name="DC.Publisher" content="O'Reilly & Associates, Inc."><meta name="DC.Source" content="" scheme="ISBN"><meta name="DC.Subject.Keyword" content=""><meta name="DC.Title" content="Pipes"><meta name="DC.Type" content="Text.Monograph"></head><body><!-- START OF BODY --><!-- TOP BANNER --><img src="gifs/smbanner.gif" usemap="#banner-map" border="0" alt="Book Home"><map name="banner-map"><AREA SHAPE="RECT" COORDS="0,0,466,71" HREF="index.htm" ALT="Programming Perl"><AREA SHAPE="RECT" COORDS="467,0,514,18" HREF="jobjects/fsearch.htm" ALT="Search this book"></map><!-- TOP NAV BAR --><div class="navbar"><table width="515" border="0"><tr><td align="left" valign="top" width="172"><a href="ch16_02.htm"><img src="../gifs/txtpreva.gif" alt="Previous" border="0"></a></td><td align="center" valign="top" width="171"><a href="ch16_01.htm">Chapter 16: Interprocess Communication</a></td><td align="right" valign="top" width="172"><a href="ch16_04.htm"><img src="../gifs/txtnexta.gif" alt="Next" border="0"></a></td></tr></table></div><hr width="515" align="left"><!-- SECTION BODY --><h2 class="sect1">16.3. Pipes</h2><p><a name="INDEX-2986"></a><a name="INDEX-2987"></a><a name="INDEX-2988"></a><a name="INDEX-2989"></a><a name="INDEX-2990"></a><a name="INDEX-2991"></a>A <em class="emphasis">pipe</em> is a unidirectional I/O channel that can transfer a stream ofbytes from one process to another. Pipes come in both named and namelessvarieties. You may be more familiar with nameless pipes, so we'll talkabout those first.</p><h3 class="sect2">16.3.1. Anonymous Pipes</h3><p><a name="INDEX-2992"></a><a name="INDEX-2993"></a><a name="INDEX-2994"></a>Perl's <tt class="literal">open</tt> function opens a pipe instead of a file when you appendor prepend a pipe symbol to the second argument to <tt class="literal">open</tt>. This turnsthe rest of the arguments into a command, which will be interpreted as aprocess (or set of processes) that you want to pipe a stream of dataeither into or out of. Here's how to start up a child process that youintend to write to:<blockquote><pre class="programlisting">open SPOOLER, "| cat -v | lpr -h 2>/dev/null" or die "can't fork: $!";local $SIG{PIPE} = sub { die "spooler pipe broke" };print SPOOLER "stuff\n";close SPOOLER or die "bad spool: $! $?";</pre></blockquote><a name="INDEX-2995"></a><a name="INDEX-2996"></a>This example actually starts up two processes, the first of which(running <em class="emphasis">cat</em>) we print to directly. The second process (running<em class="emphasis">lpr</em>) then receives the output of the first process. In shellprogramming, this is often called a <em class="emphasis">pipeline</em>. A pipeline can have asmany processes in a row as you like, as long as the ones in the middleknow how to behave like <em class="emphasis">filters</em>; that is, they read standard inputand write standard output.</p><p><a name="INDEX-2997"></a>Perl uses your default system shell (<tt class="command">/bin/sh</tt> on Unix) whenever apipe command contains special characters that the shell cares about.If you're only starting one command, and you don't need--or don'twant--to use the shell, you can use the multi-argument form of apiped open instead:<blockquote><pre class="programlisting">open SPOOLER, "|-", "lpr", "-h" # requires 5.6.1 or die "can't run lpr: $!";</pre></blockquote><a name="INDEX-2998"></a>If you reopen your program's standard output as a pipe to anotherprogram, anything you subsequently <tt class="literal">print</tt> to <tt class="literal">STDOUT</tt> will be standardinput for the new program. So to page your program's output,<a href="#FOOTNOTE-8">[8]</a> you'd use:<blockquote><pre class="programlisting">if (-t STDOUT) { # only if stdout is a terminal my $pager = $ENV{PAGER} || 'more'; open(STDOUT, "| $pager") or die "can't fork a pager: $!";}END { close(STDOUT) or die "can't close STDOUT: $!" }</pre></blockquote>When you're writing to a filehandle connected to a pipe, alwaysexplicitly <tt class="literal">close</tt> that handle when you're done with it. That wayyour main program doesn't exit before its offspring.</p><blockquote class="footnote"><a name="FOOTNOTE-8"></a><p>[8]That is, let themview it one screenful at a time, not set off random bird calls.</p></blockquote><p><a name="INDEX-2999"></a>Here's how to start up a child process that you intend to read from:<blockquote><pre class="programlisting">open STATUS, "netstat -an 2>/dev/null |" or die "can't fork: $!";while (<STATUS>) { next if /^(tcp|udp)/; print;} close STATUS or die "bad netstat: $! $?";</pre></blockquote><a name="INDEX-3000"></a><a name="INDEX-3001"></a>You can open a multistage pipeline for input just as you can foroutput. And as before, you can avoid the shell by using an alternateform of <tt class="literal">open</tt>:<blockquote><pre class="programlisting">open STATUS, "-|", "netstat", "-an" # requires 5.6.1 or die "can't run netstat: $!";</pre></blockquote>But then you don't get I/O redirection, wildcard expansion, ormultistage pipes, since Perl relies on your shell to do those.</p><p><a name="INDEX-3002"></a>You might have noticed that you can use backticks to accomplish the sameeffect as opening a pipe for reading:<blockquote><pre class="programlisting">print grep { !/^(tcp|udp)/ } `netstat -an 2>&1`;die "bad netstat" if $?;</pre></blockquote>While backticks are extremely handy, they have to read the whole thinginto memory at once, so it's often more efficient to open your ownpiped filehandle and process the file one line or record at a time.This gives you finer control over the whole operation, letting youkill off the child process early if you like. You can also be moreefficient by processing the input as it's coming in, since computerscan interleave various operations when two or more processes are running at thesame time. (Even on a single-CPU machine, input and output operations canhappen while the CPU is doing something else.)<a name="INDEX-3003"></a><a name="INDEX-3004"></a><a name="INDEX-3005"></a></p><p>Because you're running two or more processes concurrently, disastercan strike the child process any time between the<tt class="literal">open</tt> and the <tt class="literal">close</tt>. This meansthat the parent must check the return values of both<tt class="literal">open</tt> and <tt class="literal">close</tt>. Checking the<tt class="literal">open</tt> isn't good enough, since that will only tellyou whether the fork was successful, and possibly whether thesubsequent command was successfully launched. (It can tell you thisonly in recent versions of Perl, and only if the command is executeddirectly by the forked child, not via the shell.) Any disaster thathappens after that is reported from the child to the parent as anonzero exit status. When the <tt class="literal">close</tt> function seesthat, it knows to return a false value, indicating that the actualstatus value should be read from the <tt class="literal">$?</tt>(<tt class="literal">$CHILD_ERROR</tt>) variable. So checking the returnvalue of <tt class="literal">close</tt> is just as important as checking<tt class="literal">open</tt>. If you're writing to a pipe, you should alsobe prepared to handle the <tt class="literal">PIPE</tt> signal, which issent to you if the process on the other end dies before you're donesending to it.</p><a name="INDEX-3006"></a><h3 class="sect2">16.3.2. Talking to Yourself</h3><p><a name="INDEX-3007"></a><a name="INDEX-3008"></a><a name="INDEX-3009"></a>Another approach to IPC is to make your program talk to itself, ina manner of speaking. Actually, your process talks over pipes toa forked copy of itself. It works much like the piped open wetalked about in the last section, except that the child processcontinues executing your script instead of some other command.</p><p><a name="INDEX-3010"></a><a name="INDEX-3011"></a>To represent this to the <tt class="literal">open</tt> function, you use apseudocommand consisting of a minus. So the second argument to<tt class="literal">open</tt> looks like either "<tt class="literal">-|</tt>" or"<tt class="literal">|-</tt>", depending on whether you want to pipe fromyourself or to yourself. As with an ordinary <tt class="literal">fork</tt>command, the <tt class="literal">open</tt> function returns the child'sprocess ID in the parent process but <tt class="literal">0</tt> in the childprocess. Another asymmetry is that the filehandle named by the<tt class="literal">open</tt> is used only in the parent process. Thechild's end of the pipe is hooked to either <tt class="literal">STDIN</tt>or <tt class="literal">STDOUT</tt> as appropriate. That is, if you open apipe <em class="emphasis">to</em> minus with <tt class="literal">|-</tt>, you canwrite to the filehandle you opened, and your kid will find this in<tt class="literal">STDIN</tt>:<blockquote><pre class="programlisting">if (open(TO, "|-")) { print TO $fromparent;}else { $tochild = <STDIN>; exit;}</pre></blockquote><a name="INDEX-3012"></a><a name="INDEX-3013"></a>If you open a pipe <em class="emphasis">from</em> minus with <tt class="literal">-|</tt>, you can read from thefilehandle you opened, which will return whatever your kid writes to <tt class="literal">STDOUT</tt>:<blockquote><pre class="programlisting">if (open(FROM, "-|")) { $toparent = <FROM>;}else { print STDOUT $fromchild; exit;}</pre></blockquote></p><p><a name="INDEX-3014"></a><a name="INDEX-3015"></a><a name="INDEX-3016"></a><a name="INDEX-3017"></a>One common application of this construct is to bypass the shellwhen you want to open a pipe from a command. You might want to dothis because you don't want the shell to interpret any possiblemetacharacters in the filenames you're trying to pass to the command.If you're running release 5.6.1 or greater of Perl, you can usethe multi-argument form of <tt class="literal">open</tt> to get the same result.</p><p>Another use of a forking open is to safely open a file or command evenwhile you're running under an assumed UID or GID. The child you<tt class="literal">fork</tt> drops any special access rights, then safelyopens the file or command and acts as an intermediary, passing databetween its more powerful parent and the file or command it opened.Examples can be found in the section <a href="ch23_01.htm#ch23-sect-access">Section 16.1.3, "Accessing Commands and Files UnderReduced Privileges"</a> in <a href="ch23_01.htm">Chapter 23, "Security"</a>.</p><p><a name="INDEX-3018"></a><a name="INDEX-3019"></a>One creative use of a forking open is to filter your own output.Some algorithms are much easier to implement in two separate passesthan they are in just one pass. Here's a simple examplein which we emulate the Unix <em class="emphasis">tee</em>(1) program by sending our normaloutput down a pipe. The agent on the other end of the pipe (oneof our own subroutines) distributes our output to all the files specified:<blockquote><pre class="programlisting">tee("/tmp/foo", "/tmp/bar", "/tmp/glarch");while (<>) { print "$ARGV at line $. => $_";}close(STDOUT) or die "can't close STDOUT: $!"; sub tee { my @output = @_; my @handles = (); for my $path (@output) { my $fh; # open will fill this in unless (open ($fh, ">", $path)) { warn "cannot write to $path: $!"; next; } push @handles, $fh; } # reopen STDOUT in parent and return return if my $pid = open(STDOUT, "|-"); die "cannot fork: $!" unless defined $pid; # process STDIN in child while (<STDIN>) { for my $fh (@handles) { print $fh $_ or die "tee output failed: $!"; } } for my $fh (@handles) { close($fh) or die "tee closing failed: $!"; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -