http://qs321.pair.com?node_id=1176994

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

Hello monks, trying to run a system command using a file handle and insert its output to a txt box. Ive tried fileevent and fork. Both failed miserably.

my $mw = MainWindow->new; my $b1 = $mw->Button(-text => 'Go', -command => sub{&go})->pack(); my $b2 = $mw->Button(-text => 'Exit', -command => sub{exit} )->pack(); my $text = $mw->Scrolled('Text',-scrollbars=>'e')->pack; MainLoop; sub go { my $pid = fork(); if($pid == 0) { open(H, "nmap -A localhost |"); while(<H>) { $text->insert('end',$_); } } }

Thanks, -rjb

Thank you for the replies monks! It's working now...

#!/usr/bin/perl use strict; use warnings; use Tk; use Tk::IO; use Tk::ROText; my $entry_var=''; my $pid; my $mw = MainWindow->new(-title => 'Tk::IO'); $mw->geometry('570x500'); my $top_frame = $mw->Frame( -relief => 'groove', )->pack(-side=> 'top', -fill => 'x'); my $top_frame_left = $top_frame->Frame( -relief => 'groove', )->pack(-side => 'left',-fill => 'x'); my $top_frame_right = $top_frame->Frame( -relief => 'groove', )->pack(-side => 'right', -fill=>'x'); my $bot_frame = $mw->Frame( -relief => 'groove', -borderwidth => 5 )->pack(-side=> 'bottom', -expand => 1,-fill => 'both'); my $clear_button = $top_frame_left->Button( -text => 'Clear', -command => sub { &clear_text } )->pack(-side => 'left'); my $start_button = $top_frame_left->Button( -text => 'Start', -command => sub { &go } )->pack(-side => 'left'); my $stop_button = $top_frame_left->Button( -text => 'Stop', -state => 'disabled', -command => sub { &stop } )->pack(-side => 'left'); my $label = $top_frame_left->Label( -text => 'Enter Host' )->pack(-side => 'left'); my $entry = $top_frame_left->Entry( -textvariable => \$entry_var )->pack(-side => 'left'); my $exit_button = $top_frame_right->Button( -text => 'Exit', -command => sub { exit } )->pack(-side => 'right'); my $text_box = $bot_frame->Scrolled( 'ROText', -wrap => 'word', -font => 'rk24', -background => 'black', -foreground => 'green', -scrollbars => 'se' )->pack(-expand => 1, -fill => 'both'); MainLoop; sub go { if($entry_var eq ''){ $text_box->insert('end',"No host specified\n"); return; } $start_button->configure(-state => 'disabled'); $stop_button->configure(-state => 'active'); my $usr_command = Tk::IO->new( -linecommand => sub { $text_box->insert('end', shift()) }, -childcommand => sub { $text_box->yview('end'); $start_button->configure(-state => 'active'); $stop_button->configure(-state => 'disabled')}); $pid = $usr_command ->exec("nmap $entry_var 2>&1") or die $!; } sub stop { kill INT => $pid if kill 0 => $pid; $start_button->configure(-state=>'active'); $stop_button->configure(-state=>'disabled'); $text_box->insert('end', "Cancelled...\n"); $text_box->yview('end'); } sub clear_text { $text_box->delete('0.0','end'); }

Replies are listed 'Best First'.
Re: Perl Tk nonblocking
by kcott (Archbishop) on Dec 01, 2016 at 05:41 UTC

    G'day rjbuckley,

    Welcome to the Monastery.

    I've previously encountered the same issue as you with Tk::fileevent: when you've read everything from the filehandle, the GUI locks up. I don't know what's going on under the hood, so I can't provide an explanation of this behaviour; however, the general solution is to read from the filehandle until you get an undefined result and then delete the event handler:

    $widget->fileevent($fh, readable => sub { if (defined(my $read = scalar <$fh>)) { # Do something with $read here } else { $widget->fileevent($fh, readable => ''); } });

    Here's a somewhat more robust solution that:

    • Provides both a Go and a Stop button.
    • Disables the Go when running.
    • Disables the Stop when not running.
    • Changes the text on both those buttons providing primitive status feedback.

    [I wanted something like this for a future project, so it's a prototype of sorts. I'll be extending it for my own needs. Feel free to use all, some or none of the features.]

    #!/usr/bin/env perl use strict; use warnings; use autodie qw{:all}; use Tk; { my $mw = MainWindow::->new(-title => 'Test `nmap` with Tk::fileeve +nt'); my $ctrl_F = $mw->Frame()->pack(-side => 'bottom'); my $cmd_F = $mw->Frame()->pack(-side => 'top'); my $text_F = $mw->Frame()->pack(-side => 'top', -fill => 'both', - +expand => 1); my $gui_data = { start_button_text => get_start_button_text(), stop_button_text => get_stop_button_text(), button_pack_opts => [-side => 'left', -padx => 10], }; $gui_data->{out_T} = $text_F->Scrolled('Text', -scrollbars => 'osoe', -wrap => 'none', -bg => '#ffffff' )->pack(-fill => 'both', -expand => 1); sub get_button_width (); $gui_data->{start_B} = $cmd_F->Button( -textvariable => \$gui_data->{start_button_text}, -state => 'normal', -width => get_button_width(), -command => [\&start_nmap, \$mw, $gui_data], )->pack(@{$gui_data->{button_pack_opts}}); $gui_data->{stop_B} = $cmd_F->Button( -textvariable => \$gui_data->{stop_button_text}, -state => 'disabled', -width => get_button_width(), -command => [\&stop_nmap, \$mw, $gui_data], )->pack(@{$gui_data->{button_pack_opts}}); $ctrl_F->Button(-text => 'Exit', -command => sub { exit })->pack; } MainLoop; { my $nmap_running; BEGIN { $nmap_running = 0 } { my ($nmap_pipe, $nmap_pid); sub start_nmap { my ($mw, $gui_data) = @_; $nmap_pid = open $nmap_pipe, '-|', 'nmap -A localhost'; $$mw->fileevent($nmap_pipe, readable => sub { if (defined(my $read = scalar <$nmap_pipe>)) { $gui_data->{out_T}->insert(end => $read); $gui_data->{out_T}->yview('end'); } else { stop_nmap($mw, $gui_data); } }); $nmap_running = 1; @$gui_data{qw{start_button_text stop_button_text}} = (get_start_button_text(), get_stop_button_text()); $gui_data->{start_B}->configure(-state => 'disabled'); $gui_data->{stop_B}->configure(-state => 'normal'); } sub stop_nmap { my ($mw, $gui_data) = @_; $$mw->fileevent($nmap_pipe, readable => ''); kill INT => $nmap_pid if kill 0 => $nmap_pid; $nmap_running = 0; @$gui_data{qw{start_button_text stop_button_text}} = (get_start_button_text(), get_stop_button_text()); $gui_data->{start_B}->configure(-state => 'normal'); $gui_data->{stop_B}->configure(-state => 'disabled'); } } { my (@start_button_texts, @stop_button_texts); BEGIN { @start_button_texts = ('Start `nmap`', '`nmap` running ... +'); @stop_button_texts = ('`nmap` not running', 'Stop `nmap`') +; } sub get_start_button_text { $start_button_texts[$nmap_running] + } sub get_stop_button_text { $stop_button_texts[$nmap_running] } sub get_button_width () { length +(sort { length $b <=> length $a } @start_button_texts, @stop_button_texts)[0]; } } }

    — Ken

      Thanks Ken, your solution works.

Re: Perl Tk nonblocking
by tybalt89 (Monsignor) on Dec 01, 2016 at 00:54 UTC

    Try this one. I don't have nmap so I used something else that would produce output.

    #!/usr/bin/perl # http://perlmonks.org/?node_id=1176994 use strict; use warnings; use Tk; use Tk::IO; my $tester; my $mw = MainWindow->new; my $b1 = $mw->Button(-text => 'Go', -command => \&go)->pack(); my $b2 = $mw->Button(-text => 'Exit', -command => sub{exit} )->pack(); my $text = $mw->Scrolled('Text',-scrollbars=>'e')->pack; MainLoop; sub go { $tester = Tk::IO->new( -linecommand => sub {$text->insert('end', shift()) }, ); $tester->exec('ls -l | head'); # command goes in here }

      Thanks tybalt89, your solution worked

Re: Perl Tk nonblocking
by choroba (Cardinal) on Nov 30, 2016 at 22:55 UTC
    fileevent worked for me. Just populate the widget from a fileevent handler:
    #!/usr/bin/perl use warnings; use strict; use Tk; my $mw = 'MainWindow'->new(-title => 'nmap'); my $b1 = $mw->Button(-text => 'Go', -command => \&go)->pack; my $b2 = $mw->Button(-text => 'Exit', -command => \&Tk::exit)->pack; my $text = $mw->Scrolled('Text', -scrollbars => 'e')->pack; MainLoop(); sub go { open my $nmap, '-|', 'nmap -A localhost' or die $!; $text->fileevent($nmap, 'readable', sub { $text->insert(end => scalar <$nmap>); }); }

    BTW, no need for sub {&go} etc. Lexical filehandles and 3-argument form of open exist for pipes, too.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      Thanks Choroba, I tried your code and got the same result as when I attempted fileevent. After inserting the text, the gui freezes. Might be something about my build: Perl 5.022001 Tk 8.4 4.4.0-47-generic GNU/Linux Do you know anything about using pipes to get it done? Thanks, -rjb

        Note that a filehandle may be "readable", but not have a complete line. This may cause a "hang" when reading with angle brackets.

Re: Perl Tk nonblocking (threads queue)
by Anonymous Monk on Nov 30, 2016 at 23:28 UTC
    Try threaded version, should work, works on windows where fileevent wont work
    #!/usr/bin/perl -- use threads stack_size => 4096; use threads::shared; use Thread::Queue; use strict; use warnings; { my $qin = Thread::Queue->new(); my $qout = Thread::Queue->new(); my $guithread = threads->create( \&tkguithread, $qin, $qout ); my $nmapthread = threads->create( \&nmapthread, $qin, $qout ); $guithread->join; } exit; sub tkguithread { my( $qin, $qout ) = @_; require Tk; my $mw = Tk::tkinit(); my $b1 = $mw->Button(-text => 'Go', -command => sub { $qin->enqueue( 'localhost' ); })->pack(); my $b2 = $mw->Button(-text => 'Exit', -command => [ $mw, 'destroy' + ] )->pack(); my $text = $mw->Scrolled('Text',-scrollbars=>'e')->pack; $mw->repeat( 500, #ms [ \&appendFromQToText, $qout, $text ], ); $mw->MainLoop; return; } sub appendFromQToText { my( $qout, $text ) = @_; for(1..5){ if( defined( my $line = $qout->dequeue_nb )){ $text->insert('end', $line ); } } return; } sub Sleeps(){ Time::HiRes::usleep( 33 * 1000 ); ## microseconds } sub nmapthread { my( $qin, $qout ) = @_; threads->detach(); ## can't join me :) while( 1 ) { #~ if( defined( my $url = $qin->dequeue_nb ) ) { if( defined( my $url = $qin->dequeue ) ) { nmap( $url, $qout ); } Sleeps(); } return; } sub nmap { my( $host , $qout ) = @_; $qout->enqueue("got nmap($host)\n"); $host =~ s{[^a-zA-Z0-9.]}{}g; $host = 'localhost' if ! length $host; #~ my $pid = open my($H), qq{nmap -A "$host" |}; my $pid = open my($H), qq{echo faker "$host" |}; $qout->enqueue("pid($pid) \$! @{[int$!]} $!\n"); $qout->enqueue("eof @{[eof($H)]}\n"); while(<$H>) { $qout->enqueue( "nmap($host) $_" ); } close $H; return; }