SPUG: shell script event loops + wrapping shell utilities

dancerboy dancerboy at strangelight.com
Fri Apr 19 01:42:24 CDT 2002


So, I wanted to add some minor functionality to a couple of shell 
programs (telnet and ftp, specifically) and the simplest way seemed 
to be simply to wrap them in a Perl script, i/e simply make a Perl 
script that opens the program, and pipes input from the terminal 
(keystrokes) through to the program, and passes characters output 
from the program back to the terminal -- occasionally adding some 
extra keystrokes of its own (the added functionality) but mostly just 
acting as a "bidirectional pipe" between the program and the terminal.

Here is the main wrapper part of the ftp version of the script 
(greatly stripped-down for clarity):

____________________________
#!/usr/bin/perl

use strict;
use Fcntl;
use IPC::Open3;

$| = 1;

my $from_term;
my $from_shell;
my $pipe_error;

open(TTY, "+</dev/tty") or die "no tty: $!";
fcntl( TTY, F_SETFL, O_NONBLOCK );

my $pid = open3( \*TO_SHELL, \*FROM_SHELL, \*SHELL_ERR,
         'ftp -v strangelight.com'
);

fcntl( FROM_SHELL, F_SETFL, O_NONBLOCK );
fcntl( SHELL_ERR, F_SETFL, O_NONBLOCK );

my $oldfh = select(FROM_SHELL); $| = 1; select($oldfh);
$oldfh = select(SHELL_ERR); $| = 1; select($oldfh);
$oldfh = select(TTY); $| = 1; select($oldfh);

$SIG{PIPE} = sub { ++$pipe_error; };

while ( not $pipe_error ) {
                 while( defined( $from_term = getc(TTY) ) ) {
                         print TO_SHELL $from_term;
                 }
                 while( defined( $from_shell = getc(FROM_SHELL) ) ) {
                         print $from_shell;
                 }
                 while( defined( $from_shell = getc(SHELL_ERR) ) ) {
                         print $from_shell;
                 }
}

__END__

Now, I have two questions.

My first question is, admittedly, one of those "I could probably 
figure it out on my own but I'm lazy so I'll ask the folks on SPUG 
instead" questions:

While my script works fairly well as-is, it's a real processor hog, 
as you can probably guess.  All of those getc() calls are 
non-blocking, so even when there's no input or output to process, the 
main while() loop still keeps executing over and over again, doing 
absolutely nothing as fast as it possibly can.  What's the 
best/simplest way to tell Perl to "go to sleep, but wake up as soon 
as something interesting happens"?

My second question is one I've banged my head on for a while and 
haven't been able to figure out at all:

Even with setting $|=1 on all the open pipes, I still don't always 
get my I/O flushed promptly -- in particular, the responses I read 
back from FROM_SHELL seem often to be one or two lines behind what I 
should be getting. (My work-around has been to send a no-op command 
like 'pwd', the response to which usually forces the lines that I 
want to see out of the buffer.)  What else can I do to get my pipes 
"piping hot" (as the perldocs say)?

-jason

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     POST TO: spug-list at pm.org       PROBLEMS: owner-spug-list at pm.org
      Subscriptions; Email to majordomo at pm.org:  ACTION  LIST  EMAIL
  Replace ACTION by subscribe or unsubscribe, EMAIL by your Email-address
 For daily traffic, use spug-list for LIST ;  for weekly, spug-list-digest
     Seattle Perl Users Group (SPUG) Home Page: http://seattleperl.org




More information about the spug-list mailing list