Executing Nonblocking System Commands

One of the most common requests seen on the comp.lang.perl.tk newsgroup is how to execute a system command and display its output in a Text widget. The typical response is some variation of tktail, which uses fileevent to signal that output data is available without blocking the application.

Here’s the program:

open(H, "tail -f -n 25 $ARGV[0]|") or die "Nope: $!";

my $t = $mw->Text(-width => 80, -height => 25, -wrap => 'none');
$t->pack(-expand => 1);
$mw->fileevent(\*H, 'readable', [\&fill_text_widget, $t]);
MainLoop;

sub fill_text_widget {

    my($widget) = @_;

    $_ = <H>;
    $widget->insert('end', $_);
    $widget->yview('end');

}

The standard way to keep Perl/Tk programs from blocking is to use multiple processes. Here we use Perl’s open function to create a separate process that sends its output to a pipe. fileevent then defines a callback that gets invoked whenever the file handle H has data available to read. The callback appends one line to the Text widget and uses yview to ensure that we always see the end of the file.

There’s a problem here. The statement $_ = <H> expects to read an entire line, one that’s newline terminated. If only a partial line were available, the read would block, and so would tktail. To be rigorous, we should use sysread for our I/O, which handles partial lines:

sub fill_text_widget {

    my($widget) = @_;

    my($stat, $data);
                $stat = sysread H, $data, 4096;
                die "sysread error:  $!" unless defined $stat;
             $widget->insert('end', ...

Get Mastering Perl/Tk now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.