pexpect – Kommandozeilenprogramme mit Python steuern

Ich hasse Bash-Scripte. Aber richtig.
Das macht mir noch mehr Schmerzen als PHP. Wenn Code Poesie sein kann, dann sind Bash-Scripte und PHP Klingonisch mit Zerhacker.

Ich schreibe auch kleine Scripte in Python. Ich sehe keinen Sinn darin mich für kleine Aufgaben in die Gefahr zu begeben in Sprachen zu schreiben die mir nicht liegen. Und ob da nun der Bash-Interpreter oder der Python-Interpreter um Ausführen geladen wird… pfff.

Nun kam ich vor kurzer Zeit in die Bedrängnis, dass ich Konsolenprogramme aus einem Python-Script heraus aufrufen wollte. Das ist normalerweise gar kein Problem, bringt doch Pyhton in der Standardbibliothek das Modul subprocess mit. Damit kann man prima andere Anwendungen ausführen und sich auch stdin und stdout holen. Wenn man aber mit der Anwendung kommunizieren will ist das nicht wirklich ein Geschenk. Zum Einen muss man ständig den Output der Anwendung überwachen, viel schlimmer aber: manchmal unterscheiden Programme Daten die via Pipe kommen von Eingaben mit der Tastatur. Ein solches Progamm kam mir unter, das wollte eine Passworteingabe, die ließ sich aber einfach nicht über stdin übergeben.

Durch diese Schwierigkeiten bin ich auf pexpect gestoßen. Dieses Modul erlaubt es auf eine einfache Weise einen Prozess zu starten, anschließend dessen Output zu überwachen und darauf zu reagieren.

Hier ein kleines Beispiel:

import pexpect

child = pexpect.spawn("ls -la /")
while True:
    i = child.expect(['\r\n', pexpect.EOF], timeout = None)
    if i == 0:
        print child.before
    elif i == 1:
        print "--Ende--"
        break

Was da passiert ist ziemlich simpel: das Programm ruft den Befehl „ls -la /“ auf, was auf eine unixoiden System den Inhalt des root-Verzeichnisses (/) mit allen Dateien in Listenform anzeigt. Das dient hier nur als Beispiel.
Anschließend erwartet das Programm entweder einen Zeilenumbruch (\r\n)  oder das Ende des Programms (pexpect.EOF). Übrigens wertet pexpect die übergebenen Zeichenketten als Grundlage für regular Expressions. Entsprechend komplexe Formatierungen sind hier möglich. Passt die Vorlage wird die bisherige Ausgabe über child.before zugänglich gemacht. Da wir hier nach dem Zeilenumbruch suchen finden wir also immer genau eine Zeile vor, die wir einfach ausgeben lassen.
Wird aber das Ende des Progamms „gefunden“ ist die Rückgabewert 1 statt 0, denn das Muster für das Ende des Programms befindet sich an zweiter Stelle in unserer Liste an Möglichkeiten. In diesem Falle geben wir nur „–Ende–“ aus und verlassen die Endlosschleife die darauf wartet, dass immer neue Zeilen eintreffen.

Oh, das war jetzt noch nicht so umwerfend, ich weiß. Aber das Tolle ist, dass man auch ganz einfach Eingaben an den gestarteten Prozess senden kann. Wenn wir, unsinnigerweise, davon ausgehen, dass uns der ls-Befehl nach einem Passwort fragt indem er im Terminal anzeigt: Enter Password:, könnten wir den Code wie folgt anpassen:

import pexpect

child = pexpect.spawn("ls -la /")
while True:
    i = child.expect(['Enter Password:', pexpect.EOF], timeout = None)
        if i == 0:
            child.sendline ('dein Password')
        elif i == 1:
            print "--Ende--"
            break

So tippt das Script „dein Password“ ein wenn danach gefragt wird.

Großartig. Und einfach zu handhaben. So lässt sich über ein einfaches Script auch ein komplexes CLI-Programm steuern. Sehr empfehlenswert!

— der Würschtlmann

15 Gedanken zu “pexpect – Kommandozeilenprogramme mit Python steuern

  1. Kann man das mit den Eingaben dann auch so lösen das diese aus einer Keychain geholt werden?

    Ich würd ungern ein systemkritisches Passwort in Klartext in meinem Script haben – auch wenn es nur als Systemaufruf gehandelt wird.

    Wie denkst du darüber?

    • Ich schätze das kommt ganz auf die Situation an. Viele Programme bieten ja gerade die Möglichkeit sowas über einen Schlüsseltausch zu machen.
      In meinem Fall war das aber überflüssig und hätte nur zusätzlichen Verwaltungsaufwand herauf beschworen.

      Ansonsten gebe ich dir natürlich absolut recht.

      Aber man kann ja mit pexpect nicht nur Passwörter eingeben sondern auch lustige andere Sachen machen. Die Homepage bietet da einige interessante Beispiele.

    • Fragen darf man immer ;)

      Du mein here-Dokumente als „Passworteingabe“? Aus irgend einem Grund funktioniert das nicht bei allen Programmen. Oder ich habe etwas falsch gemacht.

      Allerdings wäre das ja dann auch wieder bash, und das drumherum funktioniert so gut mit Python ;)

      • Es gibt dazu einen Abschnitt im Perl Cookbook (Kapitel 16.8), inklusive der Risiken.

        In Kürze, das Modul „IPC::Open2“ wird benutzt, um einen Prozess zum gleichzeitigen Lesen und Schreiben zu öffnen. Das spendiert Dir einen Filehandle fürs Lesen und einen für das Schreiben. Dann kannst Du mit Read und Write die Filehandles benutzen.

        Nachteil: Read blockiert, wenn Du Read einsetzt, muss auch irgendwas kommen.

      • Ah ok. Das kann Python mit dem im Standard enthaltenen Modul „subprocess“, das dürfte sich weitesgehend identisch zu Perl verhalten. Zumindest kann ich mir da keine großen Unterschiede vorstellen.
        Nur gibt es Programme die Eingaben auf stdin nicht akzeptieren, zum Beispiel rsync bei der Eingabe des Passwortes. Mir ist bekannt, dass man rsync auch über eine Option eine Passwortdatei geben kann, aber von dem Programm weiß ich halt, dass es Eingabe auf stdin nicht akzeptiert.

      • Frag mich nicht wie pexpect das macht, aber damit funktioniert es.
        Und von der reinen Benutzung her finde ich das jetzt auch nicht abstoßend.

        (damit hast du völlig recht, und im Normalfall würde ich rsync/ssh und verteilte Schlüssel nehmen)

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s