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 successWichtige Parameter:
capture_output=Truefängt stdout und stderr ab (entsprichtstdout=subprocess.PIPE, stderr=subprocess.PIPE)text=Truedecodiert 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
| Feature | subprocess.run() | subprocess.Popen | os.system() |
|---|---|---|---|
| Empfohlen | Ja (Python 3.5+) | Ja (fortgeschritten) | Nein (Legacy-Pattern) |
| Ausgabe abfangen | Ja (capture_output=True) | Ja (via PIPE) | Nein |
| Rückgabewert | CompletedProcess object | Popen process object | Exit code (int) |
| Timeout-Support | Ja (timeout param) | Manuell (via wait/communicate) | Nein |
| Fehlerprüfung | check=True wirft Exception | Manuell | Exit-Code muss interpretiert werden |
| Input nach stdin | input parameter | communicate() oder stdin.write() | Nein |
| Echtzeit-Ausgabe | Nein (wartet bis fertig) | Ja (streamt zeilenweise) | Ausgabe geht ins Terminal |
| Pipelines | Begrenzt (ein Command) | Ja (mehrere Popen verketten) | Ja (via Shell-String) |
| Sicherheit | Sicher mit Listen-Args | Sicher mit Listen-Args | Shell-Injection-Risiko |
| Shell-Features | Nur mit shell=True | Nur mit shell=True | Nutzt 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 filenameWenn 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
| Practice | Do | Don't |
|---|---|---|
| Command-Format | ["cmd", "arg1", "arg2"] | f"cmd {user_input}" mit shell=True |
| User-Input | shlex.quote() wenn Shell nötig ist | String-Konkatenation in Commands |
| Shell-Modus | shell=False (Default) | shell=True mit untrusted Input |
| Executable-Pfad | Vollständige Pfade wie /usr/bin/git | Für sicherheitskritischen Code auf PATH vertrauen |
| Input-Validierung | Vor dem Übergeben validieren und sanitizen | Rohes 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:
| Feature | Unix/macOS | Windows |
|---|---|---|
| Shell | /bin/sh | cmd.exe |
| Path-Separator | / | \\ |
| Built-in Commands (dir, copy) | Nicht verfügbar | Erfordern shell=True |
| Executable-Extension | Nicht nötig | Manchmal .exe erforderlich |
| Signal-Handling | Volle POSIX-Signale | Eingeschränkt |
shlex.quote() | Funktioniert | Verwende 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 outputFehler 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() # SafeFehler 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.