Expect
ist da! (Linux-Magazin, Oktober 1998)In der Februar-Kolumne stellte ich Net::Telnet
vor, jubelnd,
daß endlich Ersatz für das praktische Tcl-expect
zumindest im Telnet-Bereich
eingetroffen sei. Wie wenig wußte ich um die Dinge, die da kommen
würden, mir den Alltag zu erleichtern und das Leben zu versüßen!
Austin Schutz stellte jüngst sein Expect
-Modul vor - und nun
kann jedes Perl-Skript alle Applikationen ansprechen, die auf die immer
gleichen Ausgaben die immer gleichen Antworten erwarten: Ob man sich
auf einem Cisco-Router einloggt, oder ein Paßwort-Programm aufruft, das
sich gemeinerweise gegen Eingaben aus der Standardeingabe sträubt -
die Send-Expect-Logik des Expect-Moduls erschlägt einfach alles.
Das Skript aus Listing telnet.pl
loggt sich per telnet
auf einem
fremden Rechner ein und bietet anschließend interaktiven Zugriff auf
die geöffnete Shell an. Da mir Andreas König einmal
bescheinigt hat,
daß telnet
nur etwas für ``Ewiggestrige'' wäre (der Rest der Welt nimmt
angeblich ssh
): Das Beispiel dient nur dazu, die prinzipielle Funktionsweise aufzuzeigen, schließlich beschränkt sich die Anwendung von Expect
nicht nur auf telnet
, vielmehr läßt sich jede beliebige Applikation so heinzelmännchengleich ansteuern.
Der Konstruktor spawn
aus dem Expect
-Paket nimmt den Pfad auf
ein externes Programm sowie eine Reihe von (optionalen) Parametern
dafür entgegen, startet es und liefert eine Referenz auf ein
Expect
-Objekt zurück. In Listing telnet.pl
startet das Programm
/bin/telnet
mit dem anzusteuernden Rechner remotehost
als Parameter.
Das Telnet-Programm wird erfahrungsgemäß etwas folgendes liefern:
Trying 205.44.189.17... Connected to remotehost. Escape character is '^]'.
Linux 2.0.30 (ruebe.remote.de) (ttyp3)
ruebe login:
und gleich anschließend auf die Eingabe des Login-Namens zu warten.
Der Aufruf der expect
-Methode in Zeile 12 verfolgt die Ausgabe
aufmerksam und kehrt zurück, falls das eingestellte Suchmuster
daherkommt.
Taucht der eingestellte Ausdruck auch nach Ablauf der im Parameter
$timeout
eingestellten Zeit nicht auf, bricht expect
ab und
liefert undef
zurück. telnet.pl
bricht in diesem Fall mit
der die
-Funktion aus Zeile 13 ab und meldet den aufgetretenen Fehler.
Der Aufruf der send_slow
-Methode in Zeile 14 antwortet dem
Login-Programm, indem es ihm eine Sequenz von Zeichen schickt, die
jenes genauso interpretiert, als würde ein Anwender den Namen eintippen
und die Return-Taste drücken. Wichtig ist dabei,
send_slow
das \n
-Zeichen mitzugeben, sonst tut sich nichts.
Der erste Parameter von send_slow
ist die Anzahl der Sekunden, die
send_slow
zwischen jedem gesendeten Zeichen warten soll - so
kann man auch mit dem langsamsten Modem kommunizieren, ohne daß dieses
den Faden verliert, für die Kommunikation mit dem Login-Programm ist
jedoch keine Zeitverzögerung notwendig.
Wie man statt auf feste Strings auf Muster wartet, die regulären Ausdrücken
genügen, zeigt in Zeile 20 die Wartefunktion
auf die Paßworteingabe, die mit [Pp]assword
entweder
auf Password
oder password
wartet. Geht dem Wartemuster der Parameter
-re
voran, interpretiert expect
den nachfolgenden String als regulären
Ausdruck.
Enthält der Regex einen Match aufs Zeilenende, ist darauf zu achten,
daß das angesprochene Terminal \r\n
als Zeilenende-Sequenz sendet
und so muß statt dem Perl-üblichen $
der Ausdruck \r?$
herhalten, der ein optionales \r
vor dem eigentlich Umbruch erlaubt.
Ein Zeilenanfang notiert wie gehabt mit ^
. Ausdrücke, die mehrere
Zeilen abdecken, sind erlaubt.
Listing telnet.pl
wartet in Zeile 25 lediglich auf das $
-Zeichen, den
Prompt der Bourne- bzw. Bash-Shell, für C-Shells wäre dies etwa
durch %
zu ersetzen.
Der Knüller freilich ist die interact
-Methode aus Zeile 28, die
die Standardeingabe des Skripts mit dem Eingabekanal der geöffneten
Telnet-Session kurzschließt und somit freie Interaktion erlaubt.
Verläßt der Benutzer die Shell wieder, bekommt interact()
dies
mit, kehrt zurück, und telnet.pl
beendet sich.
Freilich sollte man keine Paßwörter in öffentlich zugänglichen
Skripts herumliegen lassen - chmod 700 telnet.pl
stellt wenigstens
sicher, daß niemand außer dem Eigentümer und dem
Super-User den Inhalt lesen kann. Für mehr
Sicherheit bot der Februar-Artikel eine Sandkasten-Shell an.
Die automatischen Kontaktaufnahme beschränkt sich wie gesagt nicht
auf telnet
-Programm - auch ftp
bietet sich an (auch wenn
Net::FTP
schöner ist) und auch alle möglichen Prompt-gesteuerten
Applikationen.
htpasswd
austricksenEine weitere Anwendung zeigt Listing htp.pl
: Um Programme im Batch-Betrieb
anzusteuern, die eine Paßworteingabe verlangen, reicht ein Here-Dokument
nicht aus:
htpasswd htpasswd.dat user <<EOT pass pass EOT
ruft zwar das der Apache-Distribution beiliegende User-Paßwort-Programm auf, dieses nimmt aber die zwei per Standardeingabe hereingereichten Paßwörter gemeinerweise nicht entgegen, sondern wartet beharrlich mit
Adding user user New password:
auf eine Eingabe, die tatsächlich vom Terminal (tty
) kommt.
Mit dem Expect
-Modul hingegen, das mit Pseudo-tty
s herumhantiert, ist das alles kein Problem: Listing htp.pl
öffnet in Zeile 16 einen Kanal zum htpasswd
-Programm, und wartet in
Zeile 20 auf einen von zwei Strings: "Changing"
oder "New password:"
.
Erhält die expect
-Methode nämlich mehr als einen String als zu
erwartetes Muster mitgeliefert, kehrt expect
schon dann zurück, falls
nur einer der Strings in der Ausgabe des zu überwachenden Programms erscheint.
Der Rückgabewert der Methode (in skalarem Kontext) zeigt dann an, welches
der angegebenen Muster gefunden wurde: 1
indiziert, daß das erste
Suchmuster zutraf, 2
das zweite usw.
htpasswd
zeigt für den Fall, daß der angegebene User in der Paßwortdatei
schon existiert,
"Changing password for user X"
an, bevor der Paßwort-Prompt kommt.
Während htpasswd
in einem solchen Fall einfach eine Paßwortänderung
eines bestehenden Benutzers erwartet, hat sich
htp.pl
in den Kopf gesetzt, das Skript abbrechen, falls der Benutzer
schon existiert. Hierzu fängt es die Meldung ab und terminiert mit einer die
-Meldung. Ist der Rückgabewert der expect
-Methode undef
,
wurde bis zum Timeout keines
der Muster gefunden, ist er 1
, war's die Changing
-Meldung, ist er
2
, wartet htpasswd
bereits auf das Paßwort eines neuen Benutzers.
Als Alternative ginge auch
# Schon vorhanden? $pattern = $robot->expect($timeout, "New password:");
die "New password: not found" unless defined $pattern; die "User already there" if $robot->exp_before() =~ /Changing password/;
Die Methoden exp_before
, exp_match
und exp_after
auf ein
Expect
-Objekt zeigen nach
einem Treffer jeweils an, was vor dem Match kam, welche Zeichenkette
als Muster erkannt wurde, und was danach noch folgte. Findet Expect
also die Zeichenkette "New Password:"
, sieht es nach, ob vor dem Match
im Expect
-Akkumulator Changing password
steht - falls ja, gab es
den Benutzer schon und das Skript bricht ab.
Zeile 38 wartet noch, bis sich das htpasswd
-Programm verabschiedet,
fehlt diese Zeile, würgt das sofort terminierende htp.pl
das laufende htpasswd
-Programm rücksichtslos ab und das Ergebnis ist eventuell ein nicht oder nur unvollständig geschriebener Record in der Paßwortdatei.
Funktioniert etwas nicht, und es wäre hilfreich, wenn Expect
erzählen
würde, welche Muster es gefunden hat und was darauf als Antwort folgte,
läßt sich ein Expect
-Objekt einfach in den Debug-Modus schalten:
$exp->debug(1); # Debug an $exp->debug(2); # Gesprächiger Debug $exp->debug(0); # Debug wieder ausschalten
Die vollständige Dokumentation zu Expect
kommt -- wie gehabt -- nach der
Installation des Moduls mit perldoc Expect
zum Vorschein.
Fröhliches Automatisieren!
01 #!/usr/bin/perl -w 02 ################################################## 03 # Michael Schilli, 1998 (mschilli@perlmeister.com) 04 ################################################## 05 06 use Expect; 07 08 my $timeout = 10; # Grundsätzlich 10 sec 09 # auf Antwort warten 10 my $host = "remotehost"; 11 my $host = "localhost"; 12 my $passwd = "super-geheim"; 13 14 # Telnet starten 15 $telnet = Expect->spawn("/bin/telnet", $host); 16 17 # Login 18 $r = $telnet->expect($timeout, 'login'); 19 die "No 'login' prompt" unless defined $r; 20 $telnet->send_slow(0, "$user\n"); 21 22 # Paßwort 23 $r = $telnet->expect($timeout, -re => '[Pp]assword'); 24 die "No 'password' prompt" unless defined $r; 25 $telnet->send_slow(0, "$passwd\n"); 26 27 # Unix-Prompt 28 $r = $telnet->expect($timeout, '\$'); 29 die "No unix prompt (\$)" unless defined $r; 30 31 $telnet->interact(); # In interaktiven 32 # Modus schalten
01 #!/usr/bin/perl -w 02 ################################################## 03 # Michael Schilli, 1998 (mschilli@perlmeister.com) 04 ################################################## 05 06 use Expect; 07 08 $username = "user"; # Neuer user 09 $passwd = "pass"; # ... und sein passwd 10 11 $passwdfile = "htpasswd.dat"; 12 $htpasswd_prog = "./htpasswd"; 13 14 my $timeout = 10; # Grundsätzlich 10 sec 15 # auf Antwort warten 16 17 # htpasswd-Programm 18 # starten 19 $robot = Expect->spawn($htpasswd_prog, 20 $passwdfile, $username); 21 22 # Schon vorhanden? 23 $pattern = $robot->expect($timeout, 24 "Changing", "New password:"); 25 26 if(!defined $pattern) { 27 die "New password: not found"; 28 } elsif($pattern == 1) { 29 die "User already there"; 30 } 31 # Erstes Paßwort 32 $robot->send_slow(0, "$passwd\n"); 33 34 # Paßwort-Bestätigung 35 $robot->expect($timeout, 36 "Re-type new password:") || 37 die "Pattern Re-type new password: not found"; 38 $robot->send_slow(0, "$passwd\n"); 39 40 # Bis zum Programmende warten 41 $robot->expect(undef);
Das Expect
-Modul benötigt noch zwei TTY-Module, sodaß insgesamt
drei Distributionen her müssen (in dieser Reihenfolge):
AUSCHUTZ/IO-Stty-.02.tar.gz GBARR/IO-Tty-0.02.tar.gz AUSCHUTZ/Expect.pm-1.07.tar.gz
Das IO::Tty
-Modul führt bei der Installation durch ein längliches
Konfigurationsskript, das an eine Perl-Installation erinnert - blindes
Hämmern auf die Return-Taste übernimmt die
Default-Werte, die unter Linux ``passen''. Der Rest geht nahtlos.
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |