Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options

Reading non-blockingly / "awk has to be better for something."

by Anonymous Monk
on Jul 28, 2012 at 07:42 UTC ( #984156=perlquestion: print w/replies, xml ) Need Help??

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

Hail, monks.

I'm writing a GUI frontend to a command-line program, and am in need of a progress bar. The command-line program outputs things like this:

Initialising...\n Progress: 0%\r Progress: 1%\r Progress: 2%\r Progress: 3%\r

I have an I/O helper that can tell me when there is something waiting in the I/O buffer for me. It then calls my callback:

sub io_cb { my $fh = shift; local $/ = "\r"; my $line = <$fh>; # blocks until there is a \r # update progress bar with $line }

However, the kicker is that the command-line program's initialisation takes a while. Therefore, the sub will block the few seconds until the first percentage line is output, which leads to the UI freezing. ($line will then contain Initialising...\nProgress: 0%\r -- one line more than I hoped for)

It would be great if I could set $/ to qr/[\r\n]/, but the documentation tells me only awk supports that. Is there a way I could just read whatever is immediately available on $fh?

Replies are listed 'Best First'.
Re: Reading non-blockingly / "awk has to be better for something."
by james2vegas (Chaplain) on Jul 28, 2012 at 08:49 UTC
    Yes there is, set your $fh non-blocking (IO::Handle's blocking method) and use IO::Handle's getline method on $fh, bearing in mind that getline will return undef (ending a while loop) when there isn't a line to read (<$fh> would also work non-blockingly, so getline is not strictly necessary)

    As for the other issue, just split your returned line on "\n".
Re: Reading non-blockingly / "awk has to be better for something."
by Anonymous Monk on Jul 28, 2012 at 08:59 UTC

    Throwing in more detail, the I/O poller module is Gtk2::Helper, whose documentation warns: "you should not use Perl's builtin read and write functions here because these operate always with buffered I/O. Use low level sysread() and syswrite() instead."

    I'm not really sure what to construe of that. sysread() needs a size -- should I just read one byte at a time as Grimy suggested above? Am I doing this all wrong by using Perl's buffered I/O? (The code behaves as expected even if I use my current style.)

      When using Gtk2 I normally use the AnyEvent wrapper over that, especially for its AnyEvent::Handle's methods which let you define reading by line as you wish. But keeping with plain Gtk2 you can just, with your $fh set non-blocking, call sysread with the maximum expected length of your data, like this:

      use IO::Handle; my $fh = *STDIN; $fh->blocking(0); while (1) { # this block would go in your callback, not in a loop lik +e this: $fh->sysread( my $data, 255 ); print "$data"; # split $data into lines on CR|LF put the first lin +e on the end of the last line of the previous block (use an array per +haps) }

        I'm having a bit of trouble going the plain Gtk2 route. I'm not sure what's wrong since I mucked around with the code and changed its behaviour a few times already, but it either 1) works until EOF, after which it goes to an infinite loop, or 2) sysreads the whole output on one go.

        my $cmd = [perl => -e => '$|++; for my $i (0..4) { $sum+= $i; print +"Line $i: sum = $sum\r\n"; sleep 1;}']; my $cb = sub {print "CB: >>" . shift() . "<<\n";} open($fh, '-|', @$cmd) or die "failed to launch external command: $! +"; $fh->blocking(0); $tag = Gtk2::Helper->add_watch($fh->fileno, in => sub { if ($fh->eof) { print "pipe EOF\n"; Gtk2::Helper->remove_watch($tag); close $fh; return; } while (1) { my $buf; $fh->sysread($buf, 4095); print "READ: >>$buf<<\n"; last unless $buf; for (split(/[\r\n]/, $buf)) { $cb->($_); } } # keep watch active return TRUE; });
Re: Reading non-blockingly / "awk has to be better for something."
by Grimy (Pilgrim) on Jul 28, 2012 at 08:37 UTC
    my $line = ''; my $chr; $line .= $chr until (($chr = getc($fh)) =~ /[\r\n]/);
    Not tested, but it might work, or at least give you an idea.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://984156]
Approved by moritz
Front-paged by Corion
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (5)
As of 2020-12-03 17:40 GMT
Find Nodes?
    Voting Booth?
    How often do you use taint mode?

    Results (57 votes). Check out past polls.