Kommandos mit dem Subprocess-Modul aufrufen - In der Pfeife

Lesezeit
4 Minuten
Bis jetzt gelesen

Kommandos mit dem Subprocess-Modul aufrufen - In der Pfeife

02.09.2012 - 12:19
Veröffentlicht in:

Für die meisten Anwendungen stellt Python eigene Bibliotheken zur Verfügung. Wer dennoch einmal externe Unix-Programme aufrufen oder gar verbinden will, sollte das Subprocess-Modul verwenden, das dieser Workshop näher vorstellt.

Jede Skriptsprache stellt Mechanismen bereit, mit denen Anwender Unix-Systemprogramme aufrufen können. Bei vielen gibt es einen Aufruf namens system(), mit dem sich das bewerkstelligen lässt. Oder es gibt die von der Shell bekannten Backticks wie `ls`. Auch unter Python bietet das Modul os diese Funktion, aber mit Python 2.4 wurde das Subprocess-Modul eingeführt, dass für diesen Zweck einige Vorteile bietet.

Das Subprocess-Modul löst einige andere Aufrufe ab, die das OS-Modul bietet, etwa popen() (das seit Python 2.6 als "deprecated" also veraltet gilt) und verschiedene Varianten von spawn(). Weiterhin bietet das OS-Modul eine Reihe von Exec-Aufrufen wie execl(), execlp() und so weiter, die ihren Pendants der C-Bibliothek entsprechen, aber das laufende Skript komplett durch den neu gestarteten Prozess ersetzen. Dieser Artikel beschäftigt sich im Weiteren mit den Methoden, die das Subprocess-Modul bereitstellt, um in eigenen Skripten möglichst strukturiert Kommandozeilenprogramme aufzurufen und deren Ausgabe weiterzuverarbeiten.

Im einfachsten Fall lässt sich ein Befehl mit der Methode call() aufrufen, wobei Befehl und Argument als Array übergeben werden:

>>> import subprocess
>>> subprocess.call(["ls", "-l"])
0

Das führt im interaktiven Python-Modus zwar zum gewünschten Ergebnis, denn der Inhalt des Verzeichnisses wird auch angezeigt, aber das ist eigentlich nur ein Nebeneffekt, weil sich Interpreter und der Aufruf die Standardausgabe teilen. Tatsächlich gibt der Aufruf von Call nur den Rückgabewert des aufgerufenen Befehls zurück, im Erfolgsfall, wie oben, also eine Null. Der Call-Aufruf wartet, bis das aufgerufene Kommando beendet ist, blockiert also solange das Skript beziehungsweise den aktuellen Thread. Beinahe das Gleiche wie call() macht check_call(), es prüft jedoch zusätzlich den Rückgabewert und löst eine Exception aus, wenn ein Fehler aufgetreten ist.

Neu in Python-Version 2.7 ist die Methode subprocess.check_output(), die die Ausgabe des aufgerufenen Befehls als Bytestring zurückliefert. Wer das bei älteren Python-Versionen erreichen will, muss sich anders behelfen, dazu gleich mehr.

Möglicherweise empfindet man die Notwendigkeit, den aufgerufenen Befehl und seine Argumente als Array übergeben zu müssen als umständlich. Allerdings hält Python hierzu einen nützlichen Helfer bereit, der sich in diesem Fall als typisches Programmieridiom einsetzen lässt: Das Modul shlex bietet die Methode split(), die aus einem Aufruf-String genau das von den Subprocess-Methoden erwartete Format macht:

cmd = "convert -geometry 80x60 a.png b.png"
args = shlex.split(cmd)
subprocess.call(args)

Wenn man nicht wie in diesem Beispiel, Dateien auf der Festplatte verändert, wird man in Skripte typischerweise häufig die Ausgabe eines Befehls weiterverarbeiten wollen. Das erwähnte subprocess.check_open() ist hierzu perfekt geeignet, doch was kann man machen, wenn man noch auf eine Python-Version älter als 2.7 angewiesen ist? Die Lösung dafür ist leider nicht sehr elegant, denn sie verwendet eine der Popen-Methoden, die eigentlich für die Kommunikation mehrerer Prozesse gedacht ist. Den Stdout-Dateideskriptor setzt man dafür zuerst auf den Pipe-Modus und startet dann mit dem Aufruf von communicate die Kommunikation mit dem Subprozess:

output = Popen(args, stdout=PIPE).communicate()[0]

communicate() gibt ein Tupel von Daten für Stdout und Stderror zurück, deshalb verwendet der obige Aufruf das [0]-Subskript, um an die Standardausgabe zu gelangen.

Analog funktioniert es, mit Popen mehrere Befehle zu verketten, ähnlich wie bei Shell-Pipelines. Der erste Prozess bekommt als Ausgabe das Token subprocess.PIPE zugewiesen, das der folgende Prozess dann als Standardeingabe verwendet. Am Ende lässt sich das Ergebnis wieder mit communicate()[0] auffangen. Ein Beispiel zeigt Listing 1. Aus dem mit Newlines durchsetzten Ergebnisstring macht dann etwa der Aufruf lines = output.split("\n") ein Array mit einzelnen Zeilen.

Listing 1

Pipes

01 import shlex
02 from subprocess import Popen
03 from subprocess import PIPE
04
05 pscmd = shlex.split("ps auxw")
06 grepcmd = shlex.split("grep root")
07 ps = Popen(pscmd, stdout=PIPE)
08 grep = Popen(grepcmd, stdin=ps.stdout, stdout=PIPE)
09 output = grep.communicate()[0]

Shell=True

Einer der möglichen Aufrufparameter der Subprocess-Methoden gibt an, ob beim Ausführen des Programms eine Shell gestartet werden soll oder nicht. Aus Sicherheitsgründen rät die Dokumentation davon ab, das zu tun, jedenfalls wenn ein Skript Benutzereingaben verarbeitet, die später in einen solchen Aufruf münden. Das kann nämlich zu einer Sicherheitslücke führen, die den von Webanwendungen bekannten SQL Injections ähnelt. Sie firmieren deshalb auch unter dem Namen Shell Injections und funktionieren so: Der böswillige Anwender hängt hinter eine Eingabe mit Semikolon getrennt einen eigenen Befehl an, den das fehlerhafte Skript dann mit ausführt.

Parallel

Bei eigenen Skripten, die länger dauernde Berechnungen oder Tasks durchführen, ist es auch wichtig zu wissen, dass manche Subprocess-Methoden die Befehle im Hintergrund ausführen, ohne das Skript zu blockieren. Auf Multicore-Rechnern führen diese Methoden zum Teil zu einem erheblichen Performance-Gewinn. Als Beispiel konvertiert das Skript in Listing 2 alle Jpg-Dateien im aktuellen Verzeichnis in Thumbnails.

Listing 2

Benchmark-Code

01 import glob
02 import subprocess
03
04 files = glob.glob("*.jpg")
05
06 for file in files:
07    cmd = "convert -geometry 640x480 " + file + " tmp/" + file
08    print cmd
09    subprocess.call(cmd)

In dieser Form verwendet es die Call-Methode, die ein Bild nach dem anderen verarbeitet. Das Ganze dauert auf einem Vierkern-Rechner etwa 30 Sekunden:

$ time python call.py
real 0m32.028s
user 1m32.154s
sys 0m4.568s

Ersetzt man den Call-Aufruf in Zeile 9 durch die Methode Popen, sieht das Ergebnis gleich anders aus.

Die Laufzeit reduziert sich damit auf ein Drittel! Mit so einfachen Methoden lassen sich Skripte tunen, wenn man die richtigen Python-Methoden verwendet. Das setzt natürlich voraus, dass die verarbeiteten Tasks voneinander unabhängig sind. Wer möglichst fehlerfreie Skripte schreiben will, überprüft beim Start eines Skripts, ob alle aufgerufenen Shell-Kommandos auch verfügbar sind. Dabei hilft ein Python-Skript, das ausführbare Dateien im PATH auf einem Unix-System sucht (Listing 3). Es zerlegt die Umgebungsvariable in einzelne Elemente, verkettet sie mit dem fraglichen Befehl und prüft, ob die Datei mit dem entstehenden Pfad existiert und ausführbar ist. Falls ja, gibt die Funktion sie zurück. Findet sie keine Datei, auf die das zutrifft, ist der Rückgabewert False. Mit den im Artikel beschriebenen Methoden sollte es ein leichtes sein, eine Funktion zu schreiben, die das gleiche Ziel erreicht, indem sie den Unix-Befehl which aufruft.  

Listing 3

which

01 def which(program):
02    pathlist = os.environ['PATH'].split(os.pathsep)
03    for dir in pathlist:
04        filename = os.path.join(dir, program)
05        try:
06            st = os.stat(filename)
07        except os.error:
08            continue
09        mode = S_IMODE(st.st_mode)
10        if mode & 0111:
11            return filename
12    return False

 

Aus dem Admin Magazin Ausgabe 5/2012: KVM und Co. Seite 112

Ähnliche Beiträge

Richtlinien und Maßnahmen für digitale Barrierefreiheit

Die Digitalisierung erleichtert das Leben vieler, erschwert es aber auch für einige, etwa Menschen mit Behinderung, ältere Generationen und Nicht-Muttersprachler. Menschengerechte Webseitendesigns und digitale Barrierefreiheit sind daher essenziell. Der Artikel beschreibt, welche gesetzlichen Regelungen Unternehmen ab Mitte 2024 erfüllen müssen, um dies zu erreichen und welche Maßnahmen Firmen jetzt schon ergreifen sollten, um ihre Onlineauftritte ohne Hürden zu gestalten.
Sicherheit in Microsoft Azure (3) Redaktion IT-A… Mo., 15.04.2024 - 07:42
Hybride Szenarien lassen sich je nach eingesetzter Technologie in der Cloud relativ schnell aufbauen. Dies ist etwa für Testszenarien interessant. Planen Sie aber, Teile Ihrer lokalen Infrastruktur dauerhaft auszulagern, sollten Sie die Sicherheit nicht aus den Augen verlieren. In der Cloud warten hier ganz neue Security-Aspekte – und das gleich auf verschiedenen Ebenen. Im letzten Teil des Workshops geht es unter anderem darum, wie Sie mit Microsoft Defender for Cloud für Sicherheit sorgen und warum Sie den Zugriff auf virtuelle Server einschränken sollten.