[comp.lang.perl] PERL expect

bobd@magnus.ircc.ohio-state.edu (Bob DeBula) (04/04/91)

Back last November, Randal L. Schwartz distributed an Alpha
version of "expect" for PERL (expect.pl) in this newsgroup.
Has anyone used it since? Is there a later version? I seem
to be having problems with it and an example of a working PERL
script which uses it (especially a password changing script, like
the sample "passwd.exp" that comes with "expect") would
probably help immensely. Thanks in advance for any pointers
you can give.

merlyn@iwarp.intel.com (Randal L. Schwartz) (04/04/91)

In article <1991Apr3.195446.23461@magnus.acs.ohio-state.edu>, bobd@magnus (Bob DeBula) writes:
| Back last November, Randal L. Schwartz distributed an Alpha
| version of "expect" for PERL (expect.pl) in this newsgroup.
| Has anyone used it since? Is there a later version? I seem
| to be having problems with it and an example of a working PERL
| script which uses it (especially a password changing script, like
| the sample "passwd.exp" that comes with "expect") would
| probably help immensely. Thanks in advance for any pointers
| you can give.

Arggh.  I've replied about 10 times in private, which means that I'd
probably get a dozen more queries from this posting, so this is worth
a public reply....

"expect.pl" was an *alpha* release.  What I learned from doing expect
is that it was a slightly wrong approach.  I am now taking what I
learned from expect.pl, and creating a new package that is being
called "chat.pl" for the moment.  It'll basically have the same
conceptual function as expect, but also work with sockets (to chat
with SMTP or NNTP ports, for example).  It'll still have the basic
function of expect ("wait until you see one of these regex, and then
execute the corresponding code"), but implemented *much* more
efficiently than gawdawfulslow expect.pl could have ever done it.

Wait.  Wait.  I am working on it.  I have a need for it coming up real
soon, and that's usually the time I come up with fun goodies: when I
can justify them for a task-at-hand.  Be patient.

print "Just another Perl hacker,"
-- 
/=Randal L. Schwartz, Stonehenge Consulting Services (503)777-0095 ==========\
| on contract to Intel's iWarp project, Beaverton, Oregon, USA, Sol III      |
| merlyn@iwarp.intel.com ...!any-MX-mailer-like-uunet!iwarp.intel.com!merlyn |
\=Cute Quote: "Intel: putting the 'backward' in 'backward compatible'..."====/

des0mpw@colman.newcastle.ac.uk (M.P. Ward) (04/05/91)

If you just can't wait for Randell's supercharged chat script, here's
what I use to communicate interactively with another process using ptys:

An example script which logs into another machine, using another username,
and sends me some files via FTP:

#! /usr/bin/perl -s
require "waitfor.pl";

# get a password:
$password = &get_password();

# start up the program we are talking to:
&start_command("rlogin colman -l des0mpw");

$logging = 0;	# don't echo chars sent
$timeout = 10;	# time out if nothing happens for 10 seconds
&wait_for("colman>");	# wait for the prompt to appear
if ($#ARGV == -1) { # no arguments, so send all:
  &send_ln ("tar cf ftpme.all ftp");
  &wait_for("colman>");
} else {
  &send_ln ("cd ftp");
  &wait_for("colman>");
}
&send_ln ("ftp dur.ws_mw");
&wait_for("Password:");
&send_ln ("$password");
&wait_for("ftp> ");
&send_ln ("prompt");
&wait_for("ftp> "); 
&send_ln ("hash");
&wait_for("ftp> "); 
&send_ln ("bin");
&wait_for("ftp>"); 
if ($#ARGV == -1) {
  &send_ln ("put ftpme.all");
  &wait_for("ftp>");
} else {
  foreach $file (@ARGV) {
    &send_ln ("put $file");
    &wait_for("ftp> ");
  }
}
&send_ln ("quit");	# quit from FTP

# log out from colman:
&wait_for("colman>");
if ($#ARGV == -1) {
  &send_ln ("rm ftpme.all");
  &wait_for("colman>");
}
&send_ln ("exit");
close (IN);
close (F);
exit;

################## END OF SCRIPT ##################################


And here's waitfor.pl:
Note there are more functions than were used in the example,
in particular, you can wait for a pattern or a list of strings or patterns
(the function returns the pattern/list found).

You can recover from a timeout using eval, eg:

eval '&wait_for("may not be found")';
if ($@ eq "Timed out.\n") {
  ... recover from timeout, eg re-send command
} else {
  # pass on the death:
  die $@;
}

##################### START OF waitfor.pl #####################
#
#       Get some chars from the program and build string
#       received so far.

sub get_char {
  local($rmask, $nfound, $timeleft, $thisbuf);
  $endtime = time + $timeout;
  $rmask = "";
  vec($rmask,fileno(IN),1) = 1;
  ($nfound, $timeleft) = select($rmask, undef, undef, $endtime - time);
  die "Timed out.\n" if ((0 + $endtime - time) <= 0);
  if ($nfound) {
    $nread = sysread(IN, $thisbuf, 1024);
    if (defined($nread)) {
      print $thisbuf;
      $str .= $thisbuf;
      return "" if $nread == 0; # eof
    }
  } else {
    return undef; # timeout ?
  }
}

# Get a password from the terminal (Don't echo it!):
sub get_password {
  local($password);
  open(TERM,"/dev/tty") || return("anonymous"); # no terminal
  system "stty -echo";
  select(TERM);$|=1;select(STDOUT);
  print "Password?\n";
  $password = <TERM>;
  chop($password);
  system "stty echo pass8";	# you may just need "stty echo" here
  return($password);
}

# wait_for(string ...) - Eat chars until a string is matched.
# $str is the buffer, remove up to the match when found,
# return the string which matched
sub wait_for {
  local(@strings) = @_;
  local(@patterns);
  local($i, $pattern);
  for ($i = 0; $i <= $#strings; $i++) {
    ($patterns[$i] = $strings[$i]) =~ s/(\W)/\\$1/g;
  }
  print "Waiting for: @strings\n" if ($debug);
  print "Patterns = @patterns\n" if ($debug); 
  while (1) {
    for ($i = 0; $i <= $#patterns; $i++) {
      $pattern = $patterns[$i];
      if ($str =~ /$pattern/) {
        $str = $'; # strip all up to and including the match from $str
        return ($strings[$i]);
      } # fi
    } # next match
    &get_char;
  }
}

# wait_pat(pattern ...) - Eat chars until pattern is matched.
# $str is the buffer, remove up to the match when found:
sub wait_pat {
  local(@patterns) = @_;
  local($i, $pattern);
  print "Waiting for: @strings\n" if ($debug);
  while (1) {
    for ($i = 0; $i <= $#patterns; $i++) {
      $pattern = $patterns[$i];
      if ($str =~ /$pattern/) {
        $str = $'; # strip all up to and including the match from $str
        return ($pattern);
      } # fi
    } # next match
    &get_char;
  }
}

# Switch to interactive mode - take input from stdin:
# select on both STDIN and IN (output of telnet).
# No timeout.
sub interact {
  local($escape) = @_;
  local($rmask, $nfound, $thisbuf);
  print "\n### begin interactive mode ###\n";
  $str = "";
  system "stty raw -echo";
  for(;;) {
    $rmask = "";
    vec($rmask,fileno(IN),1) = 1;
    vec($rmask,fileno(STDIN),1) = 1;
    $nfound = select($rmask, undef, undef, undef);
    if ($nfound) {
      if (vec($rmask,fileno(IN),1) == 1) {
        $nread = sysread(IN, $thisbuf, 1024);
        if (defined($nread)) {
          print $thisbuf;
        }
      } elsif (vec($rmask,fileno(STDIN),1) == 1) {
        $nread = sysread(STDIN, $thisbuf, 1024);
        if (defined($nread)) {
          # check for escape character
          last if ($thisbuf =~ /$escape/);
          &send ($thisbuf);
        } # fi $nread
      } # fi IN
    } # fi select
  } # od
  system "stty -raw echo pass8";
  print "\n### end interactive mode ###\n";
}

 

#       Disconnect

sub quit {
        close(IN);;close(F);
}


#      Get a pseudo terminal connection.. (Borrowed code from expect.pl)

sub getpty {
        local($PTY,$TTY) = @_;
        # don't adjust $PTY,$TTY with main', but use caller when available
        local($pty,$tty);
        for $bank (112..127) {
                next unless -e sprintf("/dev/pty%c0", $bank);
                for $unit (48..57) {
                        $pty = sprintf("/dev/pty%c%c", $bank, $unit);
                        open(PTY,"+>$pty") || next;
                        close(PTY); 
                        ($tty = $pty) =~ s/pty/tty/;
                        return ($pty,$tty);
                }
        }
        $err = 75; &quit;
}

# start the command:
sub start_command {
  local ($command) = @_;
  local($pty,$tty) = &getpty($PTY,$TTY);
  open(IN,"+>$pty") || die "Can't get pty";
  open(F,"|$command >$tty");
  select(F);$|=1;select(STDOUT);$|=1;
}

# send a string to command:

sub send {
  local ($send) = @_;
  print F "$send";
}

# send a line command telnet:

sub send_ln {
  local ($send) = @_;
  print "$send\n" if ($logging);
  print F "$send\r";
}

######################## END OF waitfor.pl ######################

Enjoy!

		Martin.

JANET: Martin.Ward@uk.ac.durham    Internet (eg US): Martin.Ward@durham.ac.uk
or if that fails:  Martin.Ward%uk.ac.durham@nfsnet-relay.ac.uk  
or even: Martin.Ward%DURHAM.AC.UK@CUNYVM.CUNY.EDU
BITNET: Martin.Ward%durham.ac.uk@UKACRL UUCP:...!ukc!durham!Martin.Ward