subprocess in Python: Externe Programme sicher aufrufen

Wie du mit dem subprocess-Modul externe Programme startest, ihre Ausgabe einfängst und typische Sicherheitsfallen vermeidest.

Teilen

Irgendwann reicht reines Python nicht mehr aus: Du willst ffmpeg ein Video umwandeln lassen, git nach dem aktuellen Commit fragen oder ein Kommandozeilen-Tool aufrufen, für das es keine Bibliothek gibt. Genau dafür gibt es das subprocess-Modul aus der Standardbibliothek. Es ist mächtig – und genau deshalb lauern hier einige Stolperfallen, besonders beim Thema Sicherheit. Wer nach dem Lesen dieses Artikels tiefer einsteigen möchte, findet auf unserem englischsprachigen Schwesterblog pykit.org einen ausführlichen Deep Dive: Shell Out Safely: A Practical Deep Dive into Python's subprocess. Dieser Beitrag hier bringt dir die wichtigsten Grundlagen auf Deutsch bei.

Der Einstieg: subprocess.run()

Seit Python 3.5 ist subprocess.run() die empfohlene Funktion für die meisten Aufgaben. Ein einfaches Beispiel:

import subprocess

result = subprocess.run(["echo", "Hallo Welt"])
print(result.returncode)  # 0 bedeutet Erfolg

Wichtig ist die Schreibweise: Das Kommando und seine Argumente werden als Liste übergeben, nicht als ein zusammenhängender String. Das ist kein Schönheitsdetail, sondern der Kern sicherer Aufrufe – dazu gleich mehr.

Die Ausgabe einfangen

Meistens willst du nicht nur, dass ein Programm läuft, sondern auch, was es ausgibt. Dafür sorgt capture_output=True zusammen mit text=True, damit du echte Strings statt Bytes bekommst:

result = subprocess.run(
    ["git", "rev-parse", "--short", "HEAD"],
    capture_output=True,
    text=True,
)
print("Commit:", result.stdout.strip())
print("Fehler:", result.stderr)

Über result.stdout und result.stderr kommst du an die Standardausgabe und die Fehlerausgabe. So kannst du die Ergebnisse direkt in deinem Python-Programm weiterverarbeiten.

Fehler nicht übersehen: check=True

Standardmäßig ignoriert run(), ob das aufgerufene Programm erfolgreich war. Ein fehlgeschlagenes Kommando läuft still ins Leere. Mit check=True löst Python stattdessen eine CalledProcessError aus, wenn der Rückgabewert ungleich null ist:

try:
    subprocess.run(["ls", "existiert-nicht"], check=True)
except subprocess.CalledProcessError as e:
    print(f"Kommando fehlgeschlagen mit Code {e.returncode}")

In Skripten, die zuverlässig laufen sollen, ist check=True fast immer die richtige Wahl. So bemerkst du Probleme sofort, statt mit falschen Ergebnissen weiterzurechnen.

Nicht ewig warten: timeout

Externe Programme können hängen bleiben. Ein timeout (in Sekunden) sorgt dafür, dass dein Skript nicht unendlich blockiert:

try:
    subprocess.run(["sleep", "10"], timeout=3)
except subprocess.TimeoutExpired:
    print("Zu langsam – abgebrochen.")

Die wichtigste Sicherheitsregel: shell=True vermeiden

Du wirst online oft Beispiele mit shell=True sehen, bei denen das ganze Kommando als ein String übergeben wird. Das ist bequem, aber gefährlich, sobald auch nur ein Teil des Kommandos aus einer Benutzereingabe stammt:

# GEFÄHRLICH, wenn dateiname von außen kommt:
dateiname = "foto.jpg; rm -rf ~"
subprocess.run(f"convert {dateiname} out.png", shell=True)

Über den eingeschmuggelten Strichpunkt könnte ein Angreifer beliebige Befehle ausführen – das nennt man Command Injection. Die Lösung ist einfach: Übergib die Argumente als Liste und verzichte auf shell=True. Dann behandelt Python jeden Eintrag als ein einzelnes Argument, ganz gleich, welche Sonderzeichen darin stehen:

# SICHER:
subprocess.run(["convert", dateiname, "out.png"], check=True)

Der englische Deep Dive auf pykit.org, Shell Out Safely, geht auf genau diese Angriffsfläche und weitere Feinheiten wie das Streamen großer Ausgaben und das Zusammenspiel mehrerer Prozesse noch tiefer ein.

Fazit

Für den Alltag reichen dir meist vier Bausteine: subprocess.run() mit einer Argumentliste, capture_output=True und text=True für die Ausgabe, check=True für sauberes Fehler-Handling und ein timeout gegen hängende Prozesse. Und die goldene Regel: kein shell=True, sobald fremde Daten im Spiel sind. Damit rufst du externe Programme aus Python heraus zuverlässig und sicher auf.