tags:

views:

1023

answers:

5

I'd like to run a subcommand from Perl (or pipe it into a Perl script) and have the script process the command's output immediately, rather than waiting for a timeout, a newline, or a certain number of blocks. For example, let's say I want to surround each chunk of input with square brackets. When I run the script like this:

$ ( echo -n foo ; sleep 5 ; echo -n bar ; sleep 5; echo baz) | my_script.pl

I'd like the output to be this, with each line appearing five seconds after the previous one:

[foo]
[bar]
[baz]

How do I do that?

This works, but is really ugly:

#! /usr/bin/perl -w

use strict;
use Fcntl;

my $flags = '';
fcntl(STDIN, F_GETFL, $flags);
$flags |= O_NONBLOCK;
fcntl(STDIN, F_SETFL, $flags);

my $rin = '';
vec($rin,fileno(STDIN),1) = 1;
my $rout;

while (1) {
  select($rout=$rin, undef, undef, undef);
  last if eof();

  my $buffer = '';

  while (my $c = getc()) {
    $buffer .= $c;
  }

  print "[$buffer]\n";
}

Is there a more elegant way to do it?

A: 

You didn't mention how you are reading input in your Perl script, but you might want to look at the getc function:

$|++; # set autoflush on output
while ($c = getc(STDIN)) {
    print $c;
}
Greg Hewgill
That doesn't work -- I've updated the question to explain why.
raldi
My script doesn't output any square brackets, those must be coming from your script. Without seeing the rest of your script, it's hard to say what you might be doing that causes that.
Greg Hewgill
With your exact script, it prints one character at a time, rather than one chunk at a time. To illustrate, change the print line to print "[$c]";
raldi
A: 
Kyle
That doesn't appear to be necessary -- I've updated the question with some example code that works, without tricking the other program into thinking it's talking to a tty.
raldi
+1  A: 

If there's time inbetween each character, you might be able to detect the pauses.

Perl also does line input - if you don't use getc you should be able to add newlines to the end of foo, bar, etc and perl will give you each line.

If you can't add newlines, and you can't depend on a pause, then what exactly do you expect the system to do to tell perl that it's started a new command? As far as perl is concerned, there's a stdin pipe, it's eating data from it, and there's nothing in the stdin pipe to tell you when you are executing a new command.

You might consider the following instead:

$ echo "( echo -n foo ; sleep 5 ; echo -n bar ; sleep 5; echo baz)" | my_script.pl

or

$ my_script.pl$ "echo -n foo ; sleep 5 ; echo -n bar ; sleep 5; echo baz"

And modify your perl program to parse the input "command line" and execute each task, eating the stdout as needed.

Adam Davis
I've updated the question with an example that works, so it's definitely possible. Now I just want to do it The Right Way.
raldi
+8  A: 

From perlfaq5: How can I read a single character from a file? From the keyboard?. You probably also want to read How can I tell whether there's a character waiting on a filehandle?. Poll the filehandle. If there is a character there, read it and reset a timer. If there is not character there, try again. If you've retried and passed a certain time, process the input.

After you read the characters, it's up to you to decide what to do with them. With all the flexibility of reading single characters comes the extra work of handling them.

brian d foy
So are you saying that my example is the most elegant way? Or is there a better way to do it?
raldi
I'm saying that if you want all the control, you have to do the work yourself. There are no magic ponies when you want to play with stuff at that level :)
brian d foy
+1  A: 

Term::ReadKey can do this for you. In particular setting the ReadKey() mode to do the polling for you.

use Term::ReadKey;

$| = 1;
while( my $key = ReadKey(10) ) {
    print $key;
}
Schwern