Skripting mit Expect und Tcl

Ferngesteuert

Für interaktives Skripting eignet sich die Tcl-Bibliothek Expect besonders gut. Damit lassen sich zum Beispiel virtuelle Maschinen in der Cloud, Atomuhren oder SSH-Sessions fernsteuern, wie dieser Artikel zeigt.
Wer sein System permanent überwacht, hat den Grundstein dafür gelegt Engpässe zu vermeiden und Fehler frühzeitig zu erkennen. Neben dem Platzhirsch Nagios ... (mehr)

Auch wenn sich mit der Bash oder anderen Unix-Shells viele Skripting-Aufgaben erledigen lassen, stößt man schnell an Grenzen, wenn ein Skript mit einem Kommandozeilenprogramm interagieren soll. Zum Beispiel sind viele Password-Prompts, etwa ein SSH-Login, so konfiguriert, dass sie nur Eingaben von einem Terminal verarbeiten. Auch die Ausgabe von Programmen zu verarbeiten, erfordert einiges an Anstrengung, wenn es sich um eine interaktive Session handelt.

Das Expect-Tool ist dagegen extra dafür gemacht, interaktive Prozesse zu automatisieren [1] , [2] . Die auf Expect basierenden Programme sind sogenannte Chat-Skripts, die aus einer Reihe von Prompt-Antwort-Paaren bestehen. Expect basiert auf der Skriptsprache Tcl und wurde von Don Libes geschrieben. Verfügbar ist es für die meisten Linux- und Unix-Varianten. Selbst für Windows gibt es einen Port [3] .

Als einfaches Beispiel für die Anwendung von Expect soll ein Skript dienen, das eine SSH-Verbindung zu einem Host aufbaut, den man auf der Kommandozeile als Argument übergibt. Die erste Zeile des Skripts in Listing 1 ist das Tcl-Konstrukt, das der Variablen »host« mit »set« einen Wert zuweist. Das Gebilde in eckigen Klammern extrahiert mit dem Tcl-Kommando »lindex« aus dem Array »$argv« den ersten Wert (die Zählung startet bei Null). Die folgende Zeile weist der Variablen »timeout« den Wert 5 zu. Es handelt sich dabei um eine von Expect automatisch verwendete Variable, die festlegt, wie lange das Skript auf eine Eingabe wartet.

Listing 1

Automatisches SSH-Login

 

Der Rest des Listings zeigt spezielle Expect-Schlüsselwörter, die in den meisten solcher Skripte vorkommen. Die drei Wichtigsten davon sind:

  • »spawn« : Startet ein Kommando und etabliert Ein- und Ausgabeverbindungen damit.
  • »expect« : Der namensgebende Befehl des Expect-Tools. Wartet darauf, dass in der Ausgabe des Befehls die angegebene Zeichenkette auftritt oder bis der als Timeout vorgegebene Zeitspanne verstrichen ist. Alles, was vor der gesuchten Zeichenkette auftritt, wird ignoriert, es genügt also meist, nach den letzten typischerweise auftretenten Zeichen einer Programmausgabe zu suchen. Der Default-Wert für den Timeout ist zehn Sekunden, der Wert »-1« schaltet den Timeout ab.
  • »send« : Schickt eine Zeichenkette an den gestarteten Prozess.

In Listing 1 versucht Zeile 5 per SSH eine Verbindung zum Rechner »$host« aufzubauen. Das folgende »expect« wartet auf die Zeichenkette »(yes/no)?« , die das Ende der Ausgabe von SSH beim Austausch der Schlüssel anzeigt. Zeile 7 antwortet daraufhin mit »yes« und dem Return-Zeichen. Im nächsten Schritt wartet das Skript auf die Ausgabe »password:« , dessen Erscheinen es mit dem Password plus Return-Zeichen quittiert (Zeile 10). Schließlich wartet es auf den Prompt der Shell auf dem entfernten Rechner, hier ein Dollar-Zeichen (Zeile 11). Ist es gefunden, übergibt das Skript die Kontrolle per »interact« an den Benutzer. Der Ablauf des Skripts ist in Abbildung 1 zu sehen.

Abbildung 1: Automatisiertes Login per SSH und Expect. Kleiner Schönheitsfehler dieser Lösung: Das Passwort ist im Klartext im Skript gespeichert.

Interessant ist hier der Unterschied der beiden »expect« -Kommandos. Im ersten Fall ( Listing 1 , Zeile 7), ist ihm ein Block mit dem Send-Befehl untergeordnet. Er wird nur dann ausgeführt, wenn das Expect-Statement auch den gesuchten String findet. Taucht er während des Timeout-Zeitraums nicht auf, wird auch das Send nicht ausgeführt. Im Gegensatz dazu, führt das Skript das zweite Send in Zeile 10 auf jeden Fall aus, auch wenn nie ein Passwort-Prompt erscheint. Eine Expect-Anweisung ist folgendermaßen aufgebaut:

expect {
 item1 {
 statements
 }
 item2 {
 statements
 }
 ...
}

Die geschweiften Klammern nach dem Schlüsselwort »expect« sind nur dann nötig, wenn man auf mehr als eine Zeichenkette warten will. Beispiele dazu zeigt dieser Artikel weiter unten. Die Suche nach den Strings findet üblicherweise in der Reihenfolge statt, in der sie aufgeführt sind. Beim ersten passenden wird dann der dazugehörige Block ausgeführt.

Das nächste Beispiel soll eine virtuelle Maschine in Amazons Cloud konfigurieren und dabei passwortlose Logins für den Default-User »ec2-user« wie auch für den Root-Acount ermöglichen. Das Expect-Skript schickt dazu den Public Key des aktuellen Users an den Amazon-Host und fügt ihn dort der Datei »~/.ssh/authorized_keys« beider Accounts hinzu. Zusätzlich ändert es die SSH-Konfiguration, sowohl des Root-Accounts wie auch der globalen Konfiguration des Daemons, den es außerdem noch neu startet. Hat alles geklappt, verlangen weder »ssh« noch »scp« in Zukunft nach Passwort oder -phrase.

Login mit Zertifikat

Das Expect-Skript heißt »ec2_ssh_init« , der erste Teil davon ist in Listing 2 zu sehen. Als Erstes speichert es das erste Argument des Aufrufs in der Variablen »h« . Es startet einen »scp« -Prozess und wartet auf dessen Ausgabe. Mit dem Schalter »-i« verwendet der Befehl zur Authentifizierung gegenüber der Amazon-Instanz die dazugehörige Schlüsseldatei »AEF.pem« . Als Nächstes wartet das Skript auf den SSH-Schlüsselaustausch und bestätigt ihn mit »yes« . Am Ende schließt es die Verbindung und wartet noch einmal zehn Sekunden.

Listing 2

ec2_ssh_init (Teil 1)

 

Der in Listing 3 abgedruckte Rest des Skripts führt auf der Amazon-Instanz eine Reihe von Befehlen aus und öffnet dafür zu Beginn eine SSH-Sitzung. Alle Expect-Anweisungen warten auf Shell-Prompt-Zeichen, entweder des Accounts »ec2-user« oder von Root. Zuerst fügt das Skript den Inhalt des kopierten Public-Keys der Datei »~/.ssh/authorized_keys« hinzu und löscht anschließend den Key. Dann öffnet es per »sudo« eine Root-Shell und kopiert die Authorized-Keys des EC2-Users. Das Skript wechselt dann in das Konfigurationsverzeichnis des SSH-Servers, macht eine Kopie der Konfigurationsdatei und editiert sie mit »sed« . Schließlich startet das Skript den SSH-Server neu. Der Rest des Skripts beendet die Root-Shell, loggt sich aus der SSH-Session aus und beendet das Spawn-Kommando.

Listing 3

ec2_ssh_init (Teil 2)

 

Dialog mit der Hardware

Expect kann genauso dafür verwendet werden, mit Hardware zu kommunizieren, die einen interaktiven Modus besitzt. Listing 4 verbindet sich über die serielle Schnittstelle mit einer Atomuhr, um die aktuelle Zeit auszulesen. Als Erstes weist es der Variablen »clock« den Namen der seriellen Schnittstelle zu, im Beispiel »/dev/ttyS0« . Mit der Spawn-Anweisung beginnt dann die Kommunikation mit dem Device. Dabei öffnet die Tcl-Anweisung »open« die Gerätedatei. Das folgende »stty« legt wie das gleichnamige Unix-Kommando die Übertragungsparameter fest, etwa die Übertragungsgeschwindigkeit, Parity-Einstellungen und so weiter.

Listing 4

Kommunikation mit der Uhr

 

Der eigentliche Datenaustausch findet in den folgenden Send-/Expect-Anweisungen statt. Zuerst schickt das Skript ein »o« an die Uhr, die darauf mit einem Zeichen antwortet. Der Schalter »-re« der Expect-Anweisung zeigt an, dass es sich um eine Regular Expression handelt. Expect wartet hier also auf ein beliebiges Zeichen. Darauf antwortet es seinerseits mit einem Return-Zeichen, das die Uhr anweist, die Zeit auszugeben. Das tut sie mit einem einzelnen Zeichen, das mit der Zeit direkt nichts zu tun hat, gefolgt von der eigentlichen Zeitangabe. Entsprechend enthalten die Expect-Anweisungen in den Zeilen 15 und 16 in Listing 4 die passenden Regular Expressions.

Die zweite Regular Expression enthält 16 Punkte, die jeweils für ein Zeichen stehen. Der folgende Stern gibt an, das weitere Zeichen optional folgen können. Die Klammern um den Ausdruck sind keine Zeichen, auf die Expect wartet, sondern geben an, dass die gefundene Zeichenkette später im Skript verwendet wird. Im Beispiel endet das Skript hier, aber es gibt automatisch den letzten verwendeten Ausdruck, in diesem Fall also die gefundene Zeit, aus.

comments powered by Disqus
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Konfigurationsmanagement

Ich konfiguriere meine Server

  • von Hand
  • mit eigenen Skripts
  • mit Puppet
  • mit Ansible
  • mit Saltstack
  • mit Chef
  • mit CFengine
  • mit dem Nix-System
  • mit Containern
  • mit anderer Konfigurationsmanagement-Software

Ausgabe /2023