Skip to content

Python subprocess: Externe Befehle aus Python ausführen (Kompletter Leitfaden)

Updated on

Python-Skripte müssen häufig externe Programme aufrufen. Vielleicht musst du einen Shell-Befehl ausführen, um Dateien zu komprimieren, git für Versionskontrolle nutzen, ein Systemtool wie ffmpeg für Videobearbeitung aufrufen oder eine kompilierte Binary als Teil einer Datenpipeline ausführen. Aber zu os.system() zu greifen oder Backtick-artige Hacks zu verwenden führt zu Code, der fragil, unsicher und nicht zu debuggen ist, wenn etwas schiefläuft.

Der Schmerz wird schnell größer. Ausgabe verschwindet im Nirgendwo, weil du sie nicht abfangen kannst. Fehler gehen still durch, weil der Return-Code ignoriert wird. Ein einziger, vom Nutzer gelieferter Dateiname mit Leerzeichen oder Semikolon kann aus deinem harmlosen Skript eine Shell-Injection-Schwachstelle machen. Und wenn ein Subprozess hängt, hängt dein gesamtes Python-Programm mit — kein Timeout, keine Recovery, keine Erklärung.

Pythons subprocess-Modul ist die Standardlösung. Es ersetzt ältere Funktionen wie os.system(), os.popen() und das veraltete commands-Modul durch eine einzige, konsistente API zum Starten von Prozessen, Abfangen ihrer Ausgabe, Behandeln von Fehlern, Setzen von Timeouts und Bauen von Pipelines. Dieser Guide deckt alles ab, was du brauchst, um es effektiv und sicher einzusetzen.

📚

Schnellstart mit subprocess.run()

Die Funktion subprocess.run(), eingeführt in Python 3.5, ist der empfohlene Weg, um externe Befehle auszuführen. Sie führt einen Befehl aus, wartet bis er fertig ist, und gibt ein CompletedProcess-Objekt zurück.

import subprocess
 
# Run a simple command
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
 
print(result.stdout)       # standard output as a string
print(result.stderr)       # standard error as a string
print(result.returncode)   # 0 means success

Wichtige Parameter:

  • capture_output=True fängt stdout und stderr ab (entspricht stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  • text=True decodiert die Ausgabe als Strings statt als Bytes
  • Der Befehl wird als Liste von Strings übergeben, wobei jedes Argument ein separates Element ist
import subprocess
 
# Run a command with arguments
result = subprocess.run(
    ["python", "--version"],
    capture_output=True,
    text=True
)
print(result.stdout.strip())  # e.g., "Python 3.12.1"

Befehlsliste vs. String verstehen

Das erste Argument von subprocess.run() kann eine Liste oder ein String sein. Dieser Unterschied ist wichtig für Korrektheit und Sicherheit.

Listenform (empfohlen)

Jedes Element in der Liste ist ein separates Argument. Python übergibt sie direkt an das Betriebssystem — ohne Shell-Interpretation.

import subprocess
 
# Each argument is a separate list element
result = subprocess.run(
    ["grep", "-r", "TODO", "/home/user/project"],
    capture_output=True,
    text=True
)
print(result.stdout)

Dateinamen mit Leerzeichen, Anführungszeichen oder Sonderzeichen funktionieren korrekt, weil jedes Argument unverändert übergeben wird:

import subprocess
 
# Filename with spaces -- works correctly as a list element
result = subprocess.run(
    ["cat", "my file with spaces.txt"],
    capture_output=True,
    text=True
)

Stringform (erfordert shell=True)

Das Übergeben eines einzelnen Strings erfordert shell=True, was die System-Shell (/bin/sh auf Unix, cmd.exe auf Windows) aufruft, um den Befehl zu interpretieren.

import subprocess
 
# String form requires shell=True
result = subprocess.run(
    "ls -la | grep '.py'",
    shell=True,
    capture_output=True,
    text=True
)
print(result.stdout)

Das aktiviert Shell-Features wie Pipes (|), Umleitungen (>), Globbing (*.py) und Environment-Variable-Expansion ($HOME). Es bringt aber auch erhebliche Sicherheitsrisiken mit sich, die wir im Abschnitt Sicherheit behandeln.

Ausgabe abfangen

stdout und stderr separat abfangen

import subprocess
 
result = subprocess.run(
    ["python", "-c", "import sys; print('out'); print('err', file=sys.stderr)"],
    capture_output=True,
    text=True
)
 
print(f"stdout: {result.stdout}")   # "out\n"
print(f"stderr: {result.stderr}")   # "err\n"

stderr in stdout zusammenführen

Manchmal willst du alle Ausgaben in einem Stream. Verwende stderr=subprocess.STDOUT:

import subprocess
 
result = subprocess.run(
    ["python", "-c", "import sys; print('out'); print('err', file=sys.stderr)"],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    text=True
)
 
print(result.stdout)  # Contains both "out\n" and "err\n"

Ausgabe verwerfen

Leite Ausgabe nach subprocess.DEVNULL, um sie zu unterdrücken:

import subprocess
 
# Run silently -- discard all output
result = subprocess.run(
    ["apt-get", "update"],
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL
)

Binäre Ausgabe

Lass text=True weg, um rohe Bytes zu erhalten. Nützlich für binäre Daten wie Bilder oder komprimierte Dateien:

import subprocess
 
# Capture binary output (e.g., from curl)
result = subprocess.run(
    ["curl", "-s", "https://example.com/image.png"],
    capture_output=True
)
 
image_bytes = result.stdout  # bytes object
print(f"Downloaded {len(image_bytes)} bytes")

Fehlerbehandlung

Return-Codes manuell prüfen

Standardmäßig wirft subprocess.run() keine Exception, wenn ein Befehl fehlschlägt. Du musst returncode selbst prüfen:

import subprocess
 
result = subprocess.run(
    ["ls", "/nonexistent/path"],
    capture_output=True,
    text=True
)
 
if result.returncode != 0:
    print(f"Command failed with code {result.returncode}")
    print(f"Error: {result.stderr}")

Automatische Exception bei Fehlern mit check=True

Der Parameter check=True wirft subprocess.CalledProcessError, wenn der Return-Code ungleich Null ist:

import subprocess
 
try:
    result = subprocess.run(
        ["ls", "/nonexistent/path"],
        capture_output=True,
        text=True,
        check=True
    )
except subprocess.CalledProcessError as e:
    print(f"Command failed with return code {e.returncode}")
    print(f"stderr: {e.stderr}")
    print(f"stdout: {e.stdout}")

Das ist das empfohlene Pattern für Befehle, die immer erfolgreich sein sollten. Es zwingt dich, Fehlschläge explizit zu behandeln, statt sie still zu ignorieren.

„Command not found“ behandeln

Wenn das Executable nicht existiert, wirft Python FileNotFoundError:

import subprocess
 
try:
    result = subprocess.run(
        ["nonexistent_command"],
        capture_output=True,
        text=True
    )
except FileNotFoundError:
    print("Command not found -- is it installed and in PATH?")

Timeouts

Lang laufende oder hängende Prozesse können dein Skript dauerhaft blockieren. Der Parameter timeout (in Sekunden) beendet den Prozess und wirft subprocess.TimeoutExpired, wenn er nicht rechtzeitig fertig wird:

import subprocess
 
try:
    result = subprocess.run(
        ["sleep", "30"],
        timeout=5,
        capture_output=True,
        text=True
    )
except subprocess.TimeoutExpired:
    print("Process timed out after 5 seconds")

Das ist essenziell für Netzwerkbefehle, externe APIs oder jeden Prozess, der hängen kann:

import subprocess
 
def run_with_timeout(cmd, timeout_seconds=30):
    """Run a command with timeout and error handling."""
    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=timeout_seconds,
            check=True
        )
        return result.stdout
    except subprocess.TimeoutExpired:
        print(f"Command timed out after {timeout_seconds}s: {' '.join(cmd)}")
        return None
    except subprocess.CalledProcessError as e:
        print(f"Command failed (code {e.returncode}): {e.stderr}")
        return None
    except FileNotFoundError:
        print(f"Command not found: {cmd[0]}")
        return None
 
# Usage
output = run_with_timeout(["ping", "-c", "4", "example.com"], timeout_seconds=10)
if output:
    print(output)

Input an einen Prozess übergeben

Nutze den Parameter input, um Daten an stdin des Prozesses zu senden:

import subprocess
 
# Send text to stdin
result = subprocess.run(
    ["grep", "error"],
    input="line 1\nerror on line 2\nline 3\nerror on line 4\n",
    capture_output=True,
    text=True
)
 
print(result.stdout)
# "error on line 2\nerror on line 4\n"

Das ersetzt das verbreitete Pattern, Daten per Shell-Pipe durch Commands zu schicken:

import subprocess
import json
 
# Send JSON to a processing command
data = {"name": "Alice", "score": 95}
json_string = json.dumps(data)
 
result = subprocess.run(
    ["python", "-c", "import sys, json; d = json.load(sys.stdin); print(d['name'])"],
    input=json_string,
    capture_output=True,
    text=True
)
 
print(result.stdout.strip())  # "Alice"

Umgebungsvariablen

Standardmäßig erben Subprozesse die aktuelle Umgebung. Du kannst sie ändern:

import subprocess
import os
 
# Add or override environment variables
custom_env = os.environ.copy()
custom_env["API_KEY"] = "secret123"
custom_env["DEBUG"] = "true"
 
result = subprocess.run(
    ["python", "-c", "import os; print(os.environ.get('API_KEY'))"],
    env=custom_env,
    capture_output=True,
    text=True
)
 
print(result.stdout.strip())  # "secret123"

Verwende immer os.environ.copy() als Basis. Übergibst du ein Dict ohne die bestehende Umgebung, entfernst du alle geerbten Variablen — das kann Commands brechen, die von PATH, HOME oder anderen Systemvariablen abhängen.

Arbeitsverzeichnis

Der Parameter cwd setzt das Working Directory für den Subprozess:

import subprocess
 
# Run git status in a specific repository
result = subprocess.run(
    ["git", "status", "--short"],
    cwd="/home/user/my-project",
    capture_output=True,
    text=True
)
 
print(result.stdout)

subprocess.run() vs Popen: Wann du was nutzt

subprocess.run() ist ein Convenience-Wrapper um subprocess.Popen. Für die meisten Use-Cases reicht run(). Verwende Popen, wenn du Folgendes brauchst:

  • Ausgabe in Echtzeit streamen (Zeile für Zeile, während sie entsteht)
  • Mit einem laufenden Prozess interagieren (Input senden, Output in einer Schleife lesen)
  • Mehrstufige Pipelines bauen, die mehrere Prozesse verbinden
  • Nicht-blockierende Ausführung mit manueller Kontrolle über den Prozess-Lifecycle

Vergleichstabelle

Featuresubprocess.run()subprocess.Popenos.system()
EmpfohlenJa (Python 3.5+)Ja (fortgeschritten)Nein (Legacy-Pattern)
Ausgabe abfangenJa (capture_output=True)Ja (via PIPE)Nein
RückgabewertCompletedProcess objectPopen process objectExit code (int)
Timeout-SupportJa (timeout param)Manuell (via wait/communicate)Nein
Fehlerprüfungcheck=True wirft ExceptionManuellExit-Code muss interpretiert werden
Input nach stdininput parametercommunicate() oder stdin.write()Nein
Echtzeit-AusgabeNein (wartet bis fertig)Ja (streamt zeilenweise)Ausgabe geht ins Terminal
PipelinesBegrenzt (ein Command)Ja (mehrere Popen verketten)Ja (via Shell-String)
SicherheitSicher mit Listen-ArgsSicher mit Listen-ArgsShell-Injection-Risiko
Shell-FeaturesNur mit shell=TrueNur mit shell=TrueNutzt immer die Shell

Advanced: subprocess.Popen

Popen gibt dir volle Kontrolle über den Prozess-Lifecycle. Der Konstruktor startet den Prozess sofort und gibt ein Popen-Objekt zurück, mit dem du interagieren kannst.

Basisverwendung von Popen

import subprocess
 
proc = subprocess.Popen(
    ["ls", "-la"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)
 
stdout, stderr = proc.communicate()  # Wait for completion and get output
print(f"Return code: {proc.returncode}")
print(stdout)

Ausgabe in Echtzeit streamen

Im Gegensatz zu run() kannst du mit Popen die Ausgabe Zeile für Zeile lesen, während sie entsteht:

import subprocess
 
proc = subprocess.Popen(
    ["ping", "-c", "5", "example.com"],
    stdout=subprocess.PIPE,
    text=True
)
 
# Read output line by line as it arrives
for line in proc.stdout:
    print(f"[LIVE] {line.strip()}")
 
proc.wait()  # Wait for process to finish
print(f"Exit code: {proc.returncode}")

Das ist essenziell für lange laufende Commands, bei denen du Fortschritt anzeigen oder Ausgabe in Echtzeit loggen willst:

import subprocess
import sys
 
def run_with_live_output(cmd):
    """Run a command and stream its output in real time."""
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
        bufsize=1  # Line-buffered
    )
 
    output_lines = []
    for line in proc.stdout:
        line = line.rstrip()
        print(line)
        output_lines.append(line)
 
    proc.wait()
    return proc.returncode, "\n".join(output_lines)
 
# Usage
code, output = run_with_live_output(["pip", "install", "requests"])
print(f"\nFinished with exit code: {code}")

Pipelines bauen

Verbinde mehrere Commands, indem du stdout eines Prozesses in stdin des nächsten pipest:

import subprocess
 
# Equivalent to: cat /var/log/syslog | grep "error" | wc -l
p1 = subprocess.Popen(
    ["cat", "/var/log/syslog"],
    stdout=subprocess.PIPE
)
 
p2 = subprocess.Popen(
    ["grep", "error"],
    stdin=p1.stdout,
    stdout=subprocess.PIPE
)
 
# Allow p1 to receive SIGPIPE if p2 exits early
p1.stdout.close()
 
p3 = subprocess.Popen(
    ["wc", "-l"],
    stdin=p2.stdout,
    stdout=subprocess.PIPE,
    text=True
)
 
p2.stdout.close()
 
output, _ = p3.communicate()
print(f"Error count: {output.strip()}")

Der Aufruf p1.stdout.close() nach dem Verbinden mit p2 ist wichtig. Er erlaubt p1, ein SIGPIPE-Signal zu erhalten, wenn p2 früh endet, und verhindert so Deadlocks.

Interaktive Prozesskommunikation

import subprocess
 
# Start a Python REPL as a subprocess
proc = subprocess.Popen(
    ["python", "-i"],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)
 
# Send commands and get results
stdout, stderr = proc.communicate(input="print(2 + 2)\nprint('hello')\n")
print(f"stdout: {stdout}")
print(f"stderr: {stderr}")

shell=True: Power und Gefahr

Mit shell=True wird der Befehl über die System-Shell ausgeführt. Das ermöglicht Shell-Features, bringt aber Sicherheitsrisiken mit.

Wann shell=True nützlich ist

import subprocess
 
# Shell features: pipes, redirects, globbing, env vars
result = subprocess.run(
    "ls *.py | wc -l",
    shell=True,
    capture_output=True,
    text=True
)
print(f"Python files: {result.stdout.strip()}")
 
# Environment variable expansion
result = subprocess.run(
    "echo $HOME",
    shell=True,
    capture_output=True,
    text=True
)
print(result.stdout.strip())

Das Shell-Injection-Problem

Gib niemals unbereinigte User-Inputs an shell=True weiter:

import subprocess
 
# DANGEROUS -- shell injection vulnerability
user_input = "file.txt; rm -rf /"  # malicious input
subprocess.run(f"cat {user_input}", shell=True)  # Executes "rm -rf /"!
 
# SAFE -- use list form without shell=True
subprocess.run(["cat", user_input])  # Treats entire string as filename

Wenn du zwingend shell=True mit dynamischen Werten nutzen musst, verwende shlex.quote():

import subprocess
import shlex
 
user_input = "file with spaces.txt; rm -rf /"
safe_input = shlex.quote(user_input)
 
# shlex.quote wraps in single quotes, neutralizing shell metacharacters
result = subprocess.run(
    f"cat {safe_input}",
    shell=True,
    capture_output=True,
    text=True
)

Der sicherste Ansatz ist aber, shell=True komplett zu vermeiden und Shell-Features in Python nachzubauen:

import subprocess
import glob
 
# Instead of: subprocess.run("ls *.py | wc -l", shell=True)
py_files = glob.glob("*.py")
print(f"Python files: {len(py_files)}")
 
# Instead of: subprocess.run("cat file1.txt file2.txt > combined.txt", shell=True)
with open("combined.txt", "w") as outfile:
    result = subprocess.run(
        ["cat", "file1.txt", "file2.txt"],
        stdout=outfile
    )

Sicherheits-Best-Practices

PracticeDoDon't
Command-Format["cmd", "arg1", "arg2"]f"cmd {user_input}" mit shell=True
User-Inputshlex.quote() wenn Shell nötig istString-Konkatenation in Commands
Shell-Modusshell=False (Default)shell=True mit untrusted Input
Executable-PfadVollständige Pfade wie /usr/bin/gitFür sicherheitskritischen Code auf PATH vertrauen
Input-ValidierungVor dem Übergeben validieren und sanitizenRohes User-Input an Commands geben
import subprocess
import shlex
from pathlib import Path
 
def safe_file_operation(filename):
    """Safely run a command with user-supplied filename."""
    # Validate input
    path = Path(filename)
    if not path.exists():
        raise FileNotFoundError(f"File not found: {filename}")
 
    # Check for path traversal
    resolved = path.resolve()
    allowed_dir = Path("/home/user/uploads").resolve()
    if not str(resolved).startswith(str(allowed_dir)):
        raise PermissionError("Access denied: file outside allowed directory")
 
    # Use list form -- no shell injection possible
    result = subprocess.run(
        ["wc", "-l", str(resolved)],
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout.strip()

Praxisbeispiele

git-Befehle ausführen

import subprocess
 
def git_status(repo_path):
    """Get git status for a repository."""
    result = subprocess.run(
        ["git", "status", "--porcelain"],
        cwd=repo_path,
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout.strip()
 
def git_log(repo_path, n=5):
    """Get last n commit messages."""
    result = subprocess.run(
        ["git", "log", f"--oneline", f"-{n}"],
        cwd=repo_path,
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout.strip()
 
status = git_status("/home/user/my-project")
if status:
    print("Uncommitted changes:")
    print(status)
else:
    print("Working directory clean")

Dateien komprimieren und entpacken

import subprocess
 
def compress_directory(source_dir, output_file):
    """Create a tar.gz archive of a directory."""
    subprocess.run(
        ["tar", "-czf", output_file, "-C", source_dir, "."],
        check=True
    )
    print(f"Created archive: {output_file}")
 
def extract_archive(archive_file, dest_dir):
    """Extract a tar.gz archive."""
    subprocess.run(
        ["tar", "-xzf", archive_file, "-C", dest_dir],
        check=True
    )
    print(f"Extracted to: {dest_dir}")
 
compress_directory("/home/user/data", "/tmp/data_backup.tar.gz")

Systeminformationen prüfen

import subprocess
 
def get_disk_usage():
    """Get disk usage summary."""
    result = subprocess.run(
        ["df", "-h", "/"],
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout
 
def get_memory_info():
    """Get memory usage on Linux."""
    result = subprocess.run(
        ["free", "-h"],
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout
 
def get_process_list(filter_name=None):
    """List running processes, optionally filtered."""
    cmd = ["ps", "aux"]
    result = subprocess.run(cmd, capture_output=True, text=True, check=True)
 
    if filter_name:
        lines = result.stdout.strip().split("\n")
        header = lines[0]
        matching = [line for line in lines[1:] if filter_name in line]
        return header + "\n" + "\n".join(matching)
 
    return result.stdout
 
print(get_disk_usage())

Datenfiles mit externen Tools verarbeiten

import subprocess
import csv
import io
 
def sort_csv_by_column(input_file, column_index=1):
    """Sort a CSV file using the system sort command (fast for large files)."""
    result = subprocess.run(
        ["sort", "-t,", f"-k{column_index}", input_file],
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout
 
def count_lines(filepath):
    """Count lines in a file using wc (faster than Python for huge files)."""
    result = subprocess.run(
        ["wc", "-l", filepath],
        capture_output=True,
        text=True,
        check=True
    )
    return int(result.stdout.strip().split()[0])
 
def search_in_files(directory, pattern, file_type="*.py"):
    """Search for a pattern in files using grep."""
    result = subprocess.run(
        ["grep", "-rn", "--include", file_type, pattern, directory],
        capture_output=True,
        text=True
    )
    # grep returns exit code 1 if no matches found (not an error)
    if result.returncode == 0:
        return result.stdout
    elif result.returncode == 1:
        return ""  # No matches
    else:
        raise subprocess.CalledProcessError(result.returncode, result.args)
 
matches = search_in_files("/home/user/project", "TODO")
if matches:
    print(matches)
else:
    print("No TODOs found")

Automatisiertes Deployment-Skript

import subprocess
import sys
 
def deploy(repo_path, branch="main"):
    """Simple deployment script using subprocess."""
    steps = [
        (["git", "fetch", "origin"], "Fetching latest changes"),
        (["git", "checkout", branch], f"Switching to {branch}"),
        (["git", "pull", "origin", branch], "Pulling latest code"),
        (["pip", "install", "-r", "requirements.txt"], "Installing dependencies"),
        (["python", "manage.py", "migrate"], "Running migrations"),
        (["python", "manage.py", "collectstatic", "--noinput"], "Collecting static files"),
    ]
 
    for cmd, description in steps:
        print(f"\n--- {description} ---")
        try:
            result = subprocess.run(
                cmd,
                cwd=repo_path,
                capture_output=True,
                text=True,
                check=True,
                timeout=120
            )
            if result.stdout:
                print(result.stdout)
        except subprocess.CalledProcessError as e:
            print(f"FAILED: {e.stderr}")
            sys.exit(1)
        except subprocess.TimeoutExpired:
            print(f"TIMEOUT: {description} took too long")
            sys.exit(1)
 
    print("\nDeployment complete")

Cross-Platform-Aspekte

Commands verhalten sich auf Windows anders als auf Unix. Schreibe portablen Code:

import subprocess
import platform
 
def run_command(cmd_unix, cmd_windows=None):
    """Run a command with platform awareness."""
    if platform.system() == "Windows":
        cmd = cmd_windows or cmd_unix
        # Windows often needs shell=True for built-in commands
        return subprocess.run(cmd, shell=True, capture_output=True, text=True)
    else:
        return subprocess.run(cmd, capture_output=True, text=True)
 
# List directory contents
result = run_command(
    cmd_unix=["ls", "-la"],
    cmd_windows="dir"
)
print(result.stdout)

Wichtige Plattformunterschiede:

FeatureUnix/macOSWindows
Shell/bin/shcmd.exe
Path-Separator/\\
Built-in Commands (dir, copy)Nicht verfügbarErfordern shell=True
Executable-ExtensionNicht nötigManchmal .exe erforderlich
Signal-HandlingVolle POSIX-SignaleEingeschränkt
shlex.quote()FunktioniertVerwende subprocess.list2cmdline()

subprocess in Jupyter Notebooks ausführen

Shell-Commands aus Jupyter-Notebooks heraus auszuführen ist ein typischer Workflow für Data Scientists. Während Jupyter mit !command-Syntax schnelle Shell-Calls erlaubt, gibt dir subprocess saubere Fehlerbehandlung und Output-Capture direkt in deinem Python-Code.

Beim Debuggen von subprocess-Calls in Notebooks — besonders wenn Commands still fehlschlagen oder unerwartete Ausgabe erzeugen — kann RunCell (opens in a new tab) helfen. RunCell ist ein AI Agent für Jupyter, der den Kontext deines Notebooks versteht. Er kann diagnostizieren, warum ein subprocess-Command fehlschlägt, die richtigen Argumente vorschlagen und plattformspezifische Besonderheiten berücksichtigen. Statt zwischen Terminal und Notebook hin- und herzuwechseln, um einen Shell-Command zu debuggen, verfolgt RunCell das Problem direkt in deiner Zelle.

import subprocess
 
# In a Jupyter notebook: capture and display command output
result = subprocess.run(
    ["pip", "list", "--format=columns"],
    capture_output=True,
    text=True
)
 
# Display as formatted output in the notebook
print(result.stdout)

Häufige Fehler und wie du sie behebst

Fehler 1: Ausgabe nicht abfangen

import subprocess
 
# Output goes to terminal, not captured
result = subprocess.run(["ls", "-la"])
print(result.stdout)  # None!
 
# Fix: add capture_output=True
result = subprocess.run(["ls", "-la"], capture_output=True, text=True)
print(result.stdout)  # Actual output

Fehler 2: String ohne shell=True verwenden

import subprocess
 
# Fails: string passed without shell=True
# subprocess.run("ls -la")  # FileNotFoundError: "ls -la" is not a program
 
# Fix option 1: use a list
subprocess.run(["ls", "-la"])
 
# Fix option 2: use shell=True (less safe)
subprocess.run("ls -la", shell=True)

Fehler 3: Fehler ignorieren

import subprocess
 
# Bad: silently continues on failure
result = subprocess.run(["rm", "/important/file"], capture_output=True, text=True)
# ... continues even if rm failed
 
# Good: check=True raises exception on failure
try:
    result = subprocess.run(
        ["rm", "/important/file"],
        capture_output=True,
        text=True,
        check=True
    )
except subprocess.CalledProcessError as e:
    print(f"Failed to delete: {e.stderr}")

Fehler 4: Deadlock mit Popen

import subprocess
 
# DEADLOCK: stdout buffer fills up, process blocks, .wait() waits forever
proc = subprocess.Popen(["command_with_lots_of_output"], stdout=subprocess.PIPE)
proc.wait()  # Deadlock!
 
# Fix: use communicate() which handles buffering
proc = subprocess.Popen(["command_with_lots_of_output"], stdout=subprocess.PIPE, text=True)
stdout, stderr = proc.communicate()  # Safe

Fehler 5: Encoding nicht behandeln

import subprocess
 
# Bytes output can cause issues
result = subprocess.run(["cat", "data.txt"], capture_output=True)
# result.stdout is bytes, not str
 
# Fix: use text=True or encoding parameter
result = subprocess.run(["cat", "data.txt"], capture_output=True, text=True)
 
# For specific encodings:
result = subprocess.run(
    ["cat", "data.txt"],
    capture_output=True,
    encoding="utf-8",
    errors="replace"  # Handle invalid bytes
)

subprocess.run() Vollständige Parameter-Referenz

import subprocess
 
result = subprocess.run(
    args,                    # Command as list or string
    stdin=None,              # Input source (PIPE, DEVNULL, file object, or None)
    stdout=None,             # Output destination
    stderr=None,             # Error destination
    capture_output=False,    # Shorthand for stdout=PIPE, stderr=PIPE
    text=False,              # Decode output as strings (alias: universal_newlines)
    shell=False,             # Run through system shell
    cwd=None,                # Working directory
    timeout=None,            # Seconds before TimeoutExpired
    check=False,             # Raise CalledProcessError on non-zero exit
    env=None,                # Environment variables dict
    encoding=None,           # Output encoding (alternative to text=True)
    errors=None,             # Encoding error handling ('strict', 'replace', 'ignore')
    input=None,              # String/bytes to send to stdin
)

FAQ

Was ist das subprocess-Modul in Python?

Das subprocess-Modul ist Pythons Standardbibliothek, um externe Befehle und Programme aus Python-Skripten heraus auszuführen. Es hat ältere Ansätze wie os.system(), os.popen() und das commands-Modul ersetzt. Es stellt Funktionen bereit, um neue Prozesse zu starten, deren Input/Output/Error-Pipes zu verbinden, Return-Codes auszulesen und Timeouts zu behandeln. Das wichtigste Interface ist subprocess.run() für einfache Command-Ausführung und subprocess.Popen für fortgeschrittene Use-Cases mit Echtzeit-I/O oder Prozess-Pipelines.

Was ist der Unterschied zwischen subprocess.run() und subprocess.Popen?

subprocess.run() ist eine High-Level-Comfort-Funktion, die einen Befehl ausführt, wartet bis er fertig ist, und ein CompletedProcess-Objekt samt Output zurückgibt. Für die meisten Aufgaben ist das die richtige Wahl. subprocess.Popen ist eine Low-Level-Klasse, die dir direkte Kontrolle über den Prozess gibt: Du kannst Output Zeile für Zeile streamen, interaktiv Input senden, Multi-Prozess-Pipelines bauen und den Prozess-Lifecycle manuell steuern. Verwende Popen, wenn du Echtzeit-Ausgabe brauchst oder mehrere Prozesse miteinander verbinden willst.

Ist shell=True in subprocess gefährlich?

Ja, shell=True zusammen mit untrusted Input erzeugt eine Shell-Injection-Schwachstelle. Wenn shell=True gesetzt ist, wird der Command-String an die System-Shell zur Interpretation übergeben — das heißt, Shell-Metazeichen wie ;, |, && und $() werden ausgeführt. Ein Angreifer könnte beliebige Commands injizieren. Der sichere Default ist shell=False mit Commands als Liste. Wenn du shell=True verwenden musst, bereinige Input mit shlex.quote() und gib niemals rohes User-Input weiter.

Wie fange ich die Ausgabe eines subprocess-Commands ab?

Verwende capture_output=True und text=True mit subprocess.run(). Die Ausgabe steht in result.stdout (als String) und Fehler in result.stderr. Beispiel: result = subprocess.run(["ls", "-la"], capture_output=True, text=True) und dann result.stdout. Ohne text=True wird die Ausgabe als Bytes zurückgegeben.

Wie behandle ich subprocess-Timeouts in Python?

Gib den Parameter timeout (in Sekunden) an subprocess.run() weiter. Wenn der Prozess das Timeout überschreitet, beendet Python ihn und wirft subprocess.TimeoutExpired. Beispiel: subprocess.run(["slow_command"], timeout=30). Bei Popen nutze proc.communicate(timeout=30) oder proc.wait(timeout=30). Verpacke timeout-sensitiven Code immer in einen try/except-Block.

Warum funktioniert os.system() noch, wenn subprocess empfohlen wird?

os.system() ist nicht formal deprecated, gilt aber als Legacy-Interface. Es führt Befehle über die Shell aus (wie shell=True), kann keine Ausgabe abfangen, bietet keinen Timeout-Mechanismus und gibt nur den Exit-Status zurück. subprocess.run() kann alles, was os.system() kann — plus Output-Capture, Fehlerbehandlung, Timeouts und sichere Argumentübergabe. Neuer Code sollte durchgehend subprocess verwenden.

Fazit

Das subprocess-Modul ist Pythons maßgebliches Werkzeug, um externe Befehle auszuführen. Nutze subprocess.run() für unkomplizierte Command-Ausführung — es vereint Output-Capture, Fehlerprüfung, Timeouts und Input-Übergabe in einem einzigen Funktionsaufruf. Greife nur zu subprocess.Popen, wenn du Echtzeit-Output-Streaming, interaktive Prozesskommunikation oder mehrstufige Pipelines brauchst.

Die wichtigste Gewohnheit: Vermeide shell=True mit user-supplied Input. Übergib Commands als Listen, um Shell-Injection-Risiken vollständig auszuschließen. Verwende check=True, um Fehlschläge früh zu erkennen. Setze timeout, um hängende Prozesse zu verhindern. Und nutze text=True, um mit Strings statt rohen Bytes zu arbeiten.

Von git-Automation bis zur Orchestrierung von Datenpipelines gibt dir subprocess die Kontrolle und Sicherheit, die os.system() nie bieten konnte. Beherrschst du diese Patterns, kannst du jedes externe Tool souverän in deine Python-Workflows integrieren.

📚