Question

I'm a newb' on Perl, and try to do a simple script's launcher in Perl with Curses (Curses::UI)

On Stackoverflow I found a solution to print (in Perl) in real time the output of a Bash script.

But I can't do this with my Curses script, to write this output in a TextEditor field.

For example, the Perl script :

#!/usr/bin/perl -w

use strict;
use Curses::UI;
use Curses::Widgets;
use IO::Select;

my $cui = new Curses::UI( -color_support => 1 );

[...]

my $process_tracking = $container_middle_right->add(
    "text", "TextEditor",
    -readonly       => 1,
    -text           =>  "",
);

sub launch_and_read()
{
    my $s = IO::Select->new();
    open my $fh, '-|', './test.sh';
    $s->add($fh);

    while (my @readers = $s->can_read()) {
        for my $fh (@readers) {
            if (eof $fh) {
                $s->remove($fh);
                next;
            }
            my $l = <$fh>;
            $process_tracking->text( $l );
            my $actual_text = $process_tracking->text() . "\n";
            my $new_text = $actual_text . $l;
            $process_tracking->text( $new_text );
            $process_tracking->cursor_to_end();
        }
    }
}

[...]

$cui->mainloop();

This script contains a button to launch launch_and_read().

And the test.sh :

#!/bin/bash
for i in $( seq 1 5 )
do
    sleep 1
    echo "from $$ : $( date )"
done

The result is my application freeze while the bash script is executed, and the final output is wrote on my TextEditor field at the end.

Is there a solution to show in real time what's happened in the Shell script, without blocking the Perl script ?

Many thanks, and sorry if this question seems to be stupid :x

Was it helpful?

Solution

You can't block. Curses's loop needs to run to process events. So you must poll. select with a timeout of zero can be used to poll.

my $sel;

sub launch_child {
   $sel = IO::Select->new();
   open my $fh, '-|', './test.sh';
   $sel->add($fh);
}

sub read_from_child {
   if (my @readers = $sel->can_read(0)) {
      for my $fh (@readers) {
         my $rv = sysread($fh, my $buf, 64*1024);
         if (!$rv) {
            $sel->remove($fh);
            close($fh);
            next;
         }

         ... add contents of $buf to the ui here ...
      }
   }
}

launch_child();
$cui->set_timer(read_from_child => \&read_from_child, 1);
$cui->mainloop();

Untested.

Note that I switched from readline (<>) to sysread since the former blocks until a newline is received. Using blocking calls like read or readline defies the point of using select. Furthermore, using buffering calls like read or readline can cause select to say nothing is waiting when there actually is. Never use read and readline with select.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top