📄 ch16_03.htm
字号:
exit; # don't let the child return to main!}</pre></blockquote>This technique can be applied repeatedly to push as many filterson your output stream as you wish. Just keep calling functionsthat fork-open <tt class="literal">STDOUT</tt>, and have the child read from its parent(which it sees as <tt class="literal">STDIN</tt>) and pass the massaged output along tothe next function in the stream.</p><p>Another interesting application of talking to yourself with fork-openis to capture the output from an ill-mannered function that alwayssplats its results to <tt class="literal">STDOUT</tt>. Imagine if Perl only had <tt class="literal">printf</tt>and no <tt class="literal">sprintf</tt>. What you'd need would be something that workedlike backticks, but with Perl functions instead of external commands:<blockquote><pre class="programlisting">badfunc("arg"); # drat, escaped!$string = forksub(\&badfunc, "arg"); # caught it as string@lines = forksub(\&badfunc, "arg"); # as separate linessub forksub { my $kidpid = open my $self, "-|"; defined $kidpid or die "cannot fork: $!"; shift->(@_), exit unless $kidpid; local $/ unless wantarray; return <$self>; # closes on scope exit}</pre></blockquote>We're not claiming this is efficient; a tied filehandle would probablybe a good bit faster. But it's a lot easier to code up if you'rein more of a hurry than your computer is.</p><h3 class="sect2">16.3.3. Bidirectional Communication </h3><a name="INDEX-3020"></a><a name="INDEX-3021"></a><a name="INDEX-3022"></a><a name="INDEX-3023"></a><p>Although using <tt class="literal">open</tt> to connect to another command over a pipeworks reasonably well for unidirectional communication, what aboutbidirectional communication? The obvious approachdoesn't actually work:<blockquote><pre class="programlisting">open(PROG_TO_READ_AND_WRITE, "| some program |") # WRONG!</pre></blockquote>and if you forget to enable warnings, then you'll miss outentirely on the diagnostic message:<blockquote><pre class="programlisting">Can't do bidirectional pipe at myprog line 3.</pre></blockquote></p><p><a name="INDEX-3024"></a><a name="INDEX-3025"></a><a name="INDEX-3026"></a>The <tt class="literal">open</tt> function doesn't allow this because it's rather proneto deadlock unless you're quite careful. But if you're determined,you can use the standard <tt class="literal">IPC::Open2</tt> library module to attach twopipes to a subprocess's <tt class="literal">STDIN</tt> and <tt class="literal">STDOUT</tt>. There's also an<tt class="literal">IPC::Open3</tt> module for tridirectional I/O (allowing you to also catchyour child's <tt class="literal">STDERR</tt>), but this requires either an awkward <tt class="literal">select</tt>loop or the somewhat more convenient <tt class="literal">IO::Select</tt> module. Butthen you'll have to avoid Perl's buffered input operations like<tt class="literal"><></tt> (<tt class="literal">readline</tt>).</p><p>Here's an example using <tt class="literal">open2</tt>:<blockquote><pre class="programlisting">use IPC::Open2;local (*Reader, *Writer);$pid = open2(\*Reader, \*Writer, "bc -l");$sum = 2;for (1 .. 5) { print Writer "$sum * $sum\n"; chomp($sum = <Reader>);}close Writer;close Reader;waitpid($pid, 0);print "sum is $sum\n";</pre></blockquote>You can also autovivify lexical filehandles:<blockquote><pre class="programlisting">my ($fhread, $fhwrite);$pid = open2($fhread, $fhwrite, "cat -u -n");</pre></blockquote><a name="INDEX-3027"></a>The problem with this in general is that standard I/O buffering isreally going to ruin your day. Even though your output filehandleis autoflushed (the library does this for you) so that the processon the other end will get your data in a timely manner, you can'tusually do anything to force it to return the favor. In thisparticular case, we were lucky: <tt class="literal">bc</tt> expects to operate over apipe and knows to flush each output line. But few commands areso designed, so this seldom works out unless you yourself wrote theprogram on the other end of the double-ended pipe. Even simple,apparently interactive programs like <em class="emphasis">ftp</em> fail here because they won't do line buffering on a pipe. They'll only do it on a tty device.</p><p><a name="INDEX-3028"></a><a name="INDEX-3029"></a><a name="INDEX-3030"></a>The <tt class="literal">IO::Pty</tt> and <tt class="literal">Expect</tt> modules from CPAN can help with thisbecause they provide a real tty (actually, a real pseudo-tty, but it actslike a real one). This gets you line buffering in the other processwithout modifying its program.</p><p><a name="INDEX-3031"></a>If you split your program into several processes andwant these to all have a conversation that goes both ways, you can'tuse Perl's high-level pipe interfaces, because these are allunidirectional. You'll need to use two low-level<tt class="literal">pipe</tt> function calls, each handling one direction ofthe conversation:<blockquote><pre class="programlisting">pipe(FROM_PARENT, TO_CHILD) or die "pipe: $!";pipe(FROM_CHILD, TO_PARENT) or die "pipe: $!";select((select(TO_CHILD), $| = 1))[0]); # autoflushselect((select(TO_PARENT), $| = 1))[0]); # autoflushif ($pid = fork) { close FROM_PARENT; close TO_PARENT; print TO_CHILD "Parent Pid $$ is sending this\n"; chomp($line = <FROM_CHILD>); print "Parent Pid $$ just read this: `$line'\n"; close FROM_CHILD; close TO_CHILD; waitpid($pid,0);} else { die "cannot fork: $!" unless defined $pid; close FROM_CHILD; close TO_CHILD; chomp($line = <FROM_PARENT>); print "Child Pid $$ just read this: `$line'\n"; print TO_PARENT "Child Pid $$ is sending this\n"; close FROM_PARENT; close TO_PARENT; exit;}</pre></blockquote><a name="INDEX-3032"></a>On many Unix systems, you don't actually have to make two separate<tt class="literal">pipe</tt> calls to achieve full duplex communication between parent andchild. The <tt class="literal">socketpair</tt> syscall provides bidirectional connectionsbetween related processes on the same machine. So instead of two<tt class="literal">pipe</tt>s, you only need one <tt class="literal">socketpair</tt>.<blockquote><pre class="programlisting">use Socket; socketpair(Child, Parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "socketpair: $!";# or letting perl pick filehandles for youmy ($kidfh, $dadfh);socketpair($kidfh, $dadfh, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "socketpair: $!";</pre></blockquote>After the <tt class="literal">fork</tt>, the parent closes the <tt class="literal">Parent</tt> handle, then reads and writes via the <tt class="literal">Child</tt> handle. Meanwhile, thechild closes the <tt class="literal">Child</tt> handle, then reads and writes viathe <tt class="literal">Parent</tt> handle.</p><p>If you're looking into bidirectional communications because theprocess you'd like to talk to implements a standard Internet service,you should usually just skip the middleman and use a CPAN moduledesigned for that exact purpose. (See the section <a href="ch16_05.htm#ch16-sect-sockets">Section 16.5, "Sockets"</a> later for alist of a some of these.)</p><a name="INDEX-3033"></a><a name="INDEX-3034"></a><h3 class="sect2">16.3.4. Named Pipes</h3><a name="INDEX-3035"></a><a name="INDEX-3036"></a><a name="INDEX-3037"></a><p>A named pipe (often called a FIFO) is a mechanism for setting up aconversation between unrelated processes on the same machine. Thenames in a "named" pipe exist in the filesystem, which is just afunny way to say that you can put a special file in the filesystemnamespace that has another process behind it instead of a disk.<a href="#FOOTNOTE-9">[9]</a></p><blockquote class="footnote"><a name="FOOTNOTE-9"></a><p>[9]You can do the same thing with Unix-domain sockets, but youcan't use <tt class="literal">open</tt> on those.</p></blockquote><p>A FIFO is convenient when you want to connect a process to an unrelatedone. When you open a FIFO, your process will block until there'sa process on the other end. So if a reader opens the FIFOfirst, it blocks until the writer shows up--and vice versa.</p><p><a name="INDEX-3038"></a><a name="INDEX-3039"></a><a name="INDEX-3040"></a>To create a named pipe, use the POSIX <tt class="literal">mkfifo</tt> function--if you'reon a POSIX system, that is. On Microsoft systems, you'll insteadwant to look into the <tt class="literal">Win32::Pipe</tt> module, which, despite itspossible appearance to the contrary, creates named pipes. (Win32users create anonymous pipes using <tt class="literal">pipe</tt> just like the rest ofus.)</p><p>For example, let's say you'd like to have your <em class="emphasis">.signature</em> file producea different answer each time it's read. Just make it a named pipewith a Perl program on the other end that spits out random quips.Now every time any program (like a mailer, newsreader, fingerprogram, and so on) tries to read from that file, that program willconnect to your program and read in a dynamic signature.</p><p><a name="INDEX-3041"></a><a name="INDEX-3042"></a>In the following example, we use the rarely seen <tt class="literal">-p</tt> filetest operatorto determine whether anyone (or anything) has accidentally removedour FIFO.<a href="#FOOTNOTE-10">[10]</a>If theyhave, there's no reason to try to open it, so we treat this as arequest to exit. If we'd used a simple <tt class="literal">open</tt> functionwith a mode of "<tt class="literal">> $fpath</tt>", there would have been a tiny racecondition that would have risked accidentally creating the signature as a plainfile if it disappeared between the <tt class="literal">-p</tt> test and the open. Wecouldn't use a "<tt class="literal">+< $fpath</tt>" mode, either, because opening aFIFO for read-write is a nonblocking open (this is only true ofFIFOs). By using <tt class="literal">sysopen</tt> and omitting the <tt class="literal">O_CREAT</tt> flag, weavoid this problem by never creating a file by accident.<a name="INDEX-3043"></a><blockquote><pre class="programlisting">use Fcntl; # for sysopenchdir; # go home$fpath = '.signature';$ENV{PATH} .= ":/usr/games";unless (-p $fpath) { # not a pipe if (-e _) { # but a something else die "$0: won't overwrite .signature\n"; } else { require POSIX; POSIX::mkfifo($fpath, 0666) or die "can't mknod $fpath: $!"; warn "$0: created $fpath as a named pipe\n"; }}while (1) { # exit if signature file manually removed die "Pipe file disappeared" unless -p $fpath; # next line blocks until there's a reader sysopen(FIFO, $fpath, O_WRONLY) or die "can't write $fpath: $!"; print FIFO "John Smith (smith\@host.org)\n", `fortune -s`; close FIFO; select(undef, undef, undef, 0.2); # sleep 1/5th second}</pre></blockquote>The short sleep after the close is needed to give the reader achance to read what was written. If we just immediately loop backup around and open the FIFO again before our reader has finishedreading the data we just sent, then no end-of-file is seen becausethere's once again a writer. We'll both go round and round untilduring one iteration, the writer falls a little behind and thereader finally sees that elusive end-of-file. (And we were worried aboutrace conditions?)</p><blockquote class="footnote"><a name="FOOTNOTE-10"></a><p>[10]Another use is to see if a filehandle is connectedto a pipe, named or anonymous, as in <tt class="literal">-p STDIN</tt>.</p></blockquote><a name="INDEX-3044"></a><a name="INDEX-3045"></a><a name="INDEX-3046"></a><a name="INDEX-3047"></a><!-- BOTTOM NAV BAR --><hr width="515" align="left"><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="index.htm"><img src="../gifs/txthome.gif" alt="Home" border="0"></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><tr><td align="left" valign="top" width="172">16.2. Files</td><td align="center" valign="top" width="171"><a href="index/index.htm"><img src="../gifs/index.gif" alt="Book Index" border="0"></a></td><td align="right" valign="top" width="172">16.4. System V IPC</td></tr></table></div><hr width="515" align="left"><!-- LIBRARY NAV BAR --><img src="../gifs/smnavbar.gif" usemap="#library-map" border="0" alt="Library Navigation Links"><p><font size="-1"><a href="copyrght.htm">Copyright © 2001</a> O'Reilly & Associates. All rights reserved.</font></p><map name="library-map"> <area shape="rect" coords="2,-1,79,99" href="../index.htm"><area shape="rect" coords="84,1,157,108" href="../perlnut/index.htm"><area shape="rect" coords="162,2,248,125" href="../prog/index.htm"><area shape="rect" coords="253,2,326,130" href="../advprog/index.htm"><area shape="rect" coords="332,1,407,112" href="../cookbook/index.htm"><area shape="rect" coords="414,2,523,103" href="../sysadmin/index.htm"></map><!-- END OF BODY --></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -