Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight

Reading from a pipe line by line

by Marcello (Hermit)
on Jan 17, 2006 at 20:10 UTC ( #523823=perlquestion: print w/replies, xml ) Need Help??

Marcello has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,

I'm trying to write a program which forks off a child process. The child needs to send message to the parent, where a message is just a string followed by a newline. The problem I am experiencing is that the parent only reads one message although multiple messages were sent. Here is a short test script:

use strict; use warnings; use IO::Select; use IO::Socket; my $child = my $parent = my $pid = undef; # Create a socket pair for IPC (both ways) socketpair($child, $parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC); $child->autoflush(1); $parent->autoflush(1); if (!defined($pid = fork())) { die $!; } elsif ($pid == 0) { close($child); print $parent "LINE1\nLINE2\n"; sleep(5); close($parent); exit 0; } else { close($parent); # Make sure the client has already started and sent the two lines sleep(2); my $ios = IO::Select->new($child); while (my @handles = $ios->can_read(0)) { foreach my $handle (@handles) { my $message = <$handle>; print "Read message: ".$message; } } close($child); }
My questions:
  • Why does IO::Select not return the handle when there is still data to read after the first line?
  • How can I read all messages, without ever blocking the parent?

    Thanks for your help
  • Replies are listed 'Best First'.
    Re: Reading from a pipe line by line
    by ikegami (Pope) on Jan 17, 2006 at 20:30 UTC

      <FH> reads until the end of the line. You want to read until the end of available data. You need to use sysread.

      Simplisticly, sysread($handle, my $message, 1024); will work.

      Realisticly, you need something adapted from that which I posted earlier. Give me a couple of minutes, and I will post the necessary code.

      Update: I've just found out that -s will return the number of bytes available (on FreeBSD). So all you need is the following:

      sysread($handle, my $message, -s $handle);

      You will need to split the lines apart, but that shouldn't be a problem.

      Update: If you wanted to be super safe — i.e. if you don't want to reply on your OS's implementation details — you could use the following. As a bonus, it still supports multiple child handles.

      my %buf; while (my @handles = $ios->can_read(0)) { foreach my $handle (@handles) { $buf{$handle} ||= { buf => '', offset => 0 }; # Aliases to improve readability through conciseness. our $buf; local *buf = \($buf{$handle}{buf }); our $offset; local *offset = \($buf{$handle}{offset}); #my $len = sysread($handle, $buf, -s $handle, $offset);#Portable? my $len = sysread($handle, $buf, 1024, $offset); die("Unable to read from pipe: $!\n") if not defined $len; if (not $len) { $ios->remove($handle); next; } $offset += $len; for (;;) { my $pos = index($buf, "\x0A"); last if not ++$pos; my $message = substr($buf, 0, $pos); $buf = substr($buf, $pos); $offset -= $pos; $message =~ s/\x0A$/\n/; # For Macs. print "Read message: ".$message; } } } foreach (keys %buf) { die("Unable to read from pipe: Premature end of file\n") if $buf{$_}{offset}; }

        -s $handle does not seem to work on Linux, it returns 0. So I modified the code to:
        my $ios = IO::Select->new($child); if ($ios->can_read(0)) { if (sysread($child, my $data, 1024)) { my @lines = split(/\n/, $data); print "Read message(s): ".join("|", @lines); } else { # Child died print "Child died"; } }

    Log In?

    What's my password?
    Create A New User
    Node Status?
    node history
    Node Type: perlquestion [id://523823]
    Approved by Limbic~Region
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others about the Monastery: (4)
    As of 2020-06-01 00:31 GMT
    Find Nodes?
      Voting Booth?
      If programming languages were movie genres, Perl would be:

      Results (177 votes). Check out past polls.