Shell-Befehle ausführen

PythonBeginner
Jetzt üben

Einführung

Bei der Arbeit im Operations-Bereich müssen Sie auch beim Schreiben von Python häufig vorhandene Systembefehle aufrufen. Deshalb ist es wichtig, Pythons Werkzeuge zur Prozessausführung zu verstehen.

Mit dem Modul subprocess kann Python andere Programme starten, deren Ausgabe erfassen und Erfolg oder Fehler sicher überprüfen. Es ist die standardmäßige Verbindung zwischen Python-Code und Shell-Befehlen.

In diesem Lab lernen Sie:

  1. das Modul subprocess zu importieren,
  2. mit Python einen einfachen Linux-Befehl (ls -l) auszuführen,
  3. die Ausgabe des Befehls zur weiteren Verarbeitung zu erfassen,
  4. die Ausgabe von Bytes in einen lesbaren String zu dekodieren,
  5. Fehler kontrolliert zu behandeln, wenn ein Befehl fehlschlägt.

Sie arbeiten in einer Linux-Umgebung im Verzeichnis ~/project.

Subprocess importieren

In diesem Schritt erstellen Sie ein Python-Skript und importieren das benötigte Modul, um mit der System-Shell zu interagieren.

Das Modul subprocess gehört zur Python-Standardbibliothek. Sie müssen daher nichts zusätzlich installieren. Es ermöglicht Ihrem Python-Skript, mit dem zugrunde liegenden Betriebssystem zu kommunizieren.

Dies ist die standardmäßige Verbindung zwischen Python-Code und vorhandenen Unix-Werkzeugen. Diese Verbindung ist im Operations-Bereich wichtig, weil Sie selten alle Shell-Befehle auf einmal ersetzen. Häufig übernimmt Python die Koordination: Es ruft Systemwerkzeuge auf, erfasst deren Ergebnisse und ergänzt sie um übergeordnete Logik.

Stellen Sie zunächst sicher, dass Sie sich im richtigen Verzeichnis befinden:

cd ~/project

Erstellen Sie nun mit dem Editor nano oder einer anderen bevorzugten Methode eine neue Python-Datei namens list_files.py:

nano list_files.py

Fügen Sie am Anfang der Datei die folgende Zeile ein, um das Modul zu importieren:

import subprocess

print("Module imported successfully")

Speichern Sie die Datei und schließen Sie den Editor. Drücken Sie in nano Ctrl+X, anschließend Y und dann Enter.

Führen Sie nun das Skript aus, um sicherzustellen, dass alles korrekt funktioniert:

python3 list_files.py

Sie sollten die Meldung „Module imported successfully“ sehen.

Einen Befehl ausführen

In diesem Schritt ändern Sie Ihr Skript so, dass es tatsächlich einen Linux-Befehl ausführt. Wir verwenden die Funktion subprocess.run(), die seit Python 3.5 der empfohlene Ansatz zum Aufrufen von Subprozessen ist.

Wenn Python ein anderes Programm startet, erstellt es einen eigenen Prozess. Dieser Kindprozess besitzt wie ein direkt in der Shell ausgeführter Befehl einen eigenen Exit-Code, eine Standardausgabe und eine Standardfehlerausgabe. Wenn Sie diese Prozessgrenze verstehen, lassen sich Fehler später leichter behandeln.

Öffnen Sie list_files.py erneut:

nano list_files.py

Ändern Sie den Inhalt so, dass er wie folgt aussieht. Mit dem Befehl ls -l listen wir die Dateien im aktuellen Verzeichnis mit Detailinformationen auf:

import subprocess

## Run the 'ls -l' command
## The command and its arguments are passed as a list of strings
subprocess.run(["ls", "-l"])

Beachten Sie, dass wir den Befehl als Liste übergeben: ["ls", "-l"]. Das ist sicherer und übersichtlicher, als einen einzelnen String zu übergeben, weil dadurch Probleme mit Shell-Injection und Leerzeichen in Dateinamen vermieden werden.

Speichern Sie die Datei und schließen Sie den Editor.

Führen Sie nun das Skript aus:

python3 list_files.py

Sie sollten die Ausgabe des Befehls ls -l direkt in Ihrem Terminal sehen. Das liegt daran, dass subprocess.run die Ausgabe des Befehls standardmäßig an die Standardausgabe, also an Ihren Bildschirm, weiterleitet.

Ausgabe erfassen

Im vorherigen Schritt wurde die Ausgabe direkt auf dem Bildschirm angezeigt. In Automatisierungsskripten möchten Sie diese Ausgabe jedoch normalerweise in einer Variablen erfassen, damit Ihr Python-Code sie verarbeiten kann, zum Beispiel um Dateinamen auszuwerten oder Fehler zu prüfen.

Das ist der Unterschied zwischen dem bloßen Ausführen eines Befehls und seiner Integration in ein Programm. Sobald die Ausgabe als Daten vorliegt, kann Ihr Skript Entscheidungen auf ihrer Grundlage treffen, anstatt das Ergebnis nur für eine Person im Terminal anzuzeigen.

Dazu müssen Sie das Argument capture_output=True verwenden.

Öffnen Sie list_files.py erneut:

nano list_files.py

Ändern Sie den Code so, dass das Ergebnis erfasst wird:

import subprocess

## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)

## Print the captured result object
print(result)

Speichern Sie die Datei und schließen Sie den Editor.

Führen Sie das Skript aus:

python3 list_files.py

Die Ausgabe sieht ungefähr so aus:

CompletedProcess(args=['ls', '-l'], returncode=0, stdout=b'total 0\n-rw-rw-r-- 1 labex labex 0 ...', stderr=b'')

Dies ist ein CompletedProcess-Objekt. Beachten Sie den Teil stdout=b'...'. Das b steht für Bytes. Python hat die Rohdaten genau so erfasst, wie der Computer sie verarbeitet, und nicht als lesbaren Text, also als String. Im nächsten Schritt wandeln wir diese Daten in für Menschen lesbaren Text um.

Ausgabe dekodieren

Da die Ausgabe als Bytes erfasst wird, müssen Sie sie in einen standardmäßigen Python-String (UTF-8) dekodieren, damit sie lesbar und verwendbar ist.

Der Unterschied zwischen Bytes und Text ist in Python grundlegend. Computer übertragen letztlich Rohdaten in Form von Bytes, während Menschen normalerweise Text benötigen. Viele schwer verständliche Fehler in Automatisierungsskripten entstehen, weil nicht beachtet wird, in welcher Form die Daten gerade vorliegen.

Öffnen Sie list_files.py noch einmal:

nano list_files.py

Ändern Sie den Code so, dass er auf das Attribut stdout zugreift und die Ausgabe dekodiert:

import subprocess

## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)

## Decode the bytes to a string
output_string = result.stdout.decode("utf-8")

## Print the clean string
print("--- Command Output ---")
print(output_string)

Speichern Sie die Datei und schließen Sie den Editor.

Führen Sie das Skript aus:

python3 list_files.py

Nun sollten Sie die übersichtlich formatierte Ausgabe des Befehls ls -l sehen, genau wie bei einer direkten Ausführung in der Shell. Jetzt wird sie jedoch von Python ausgegeben.

Hinweis: Alternativ können Sie text=True oder universal_newlines=True an subprocess.run() übergeben. Dann übernimmt Python die Dekodierung automatisch. Trotzdem ist es wichtig, die Methode .decode() zu verstehen, wenn Sie Binärdaten verarbeiten.

Fehler behandeln

Was passiert, wenn der Befehl fehlschlägt? Wenn Sie beispielsweise versuchen, ein nicht vorhandenes Verzeichnis aufzulisten, tritt ein Fehler auf. Gute Skripte müssen solche Fehler behandeln.

In Unix-ähnlichen Systemen wird ein fehlgeschlagener Befehl normalerweise durch einen Exit-Status ungleich null signalisiert. subprocess.run(..., check=True) überträgt diese Konvention des Betriebssystems in eine Python-Ausnahme. Dadurch kann Ihr Skript mit dem normalen Kontrollfluss von Python darauf reagieren.

Erstellen Sie ein neues Skript namens handle_error.py, um dies zu demonstrieren.

nano handle_error.py

Fügen Sie den folgenden Code ein. Wir versuchen, ein nicht vorhandenes Verzeichnis namens /nonexistent aufzulisten. Außerdem verwenden wir check=True. Dadurch löst Python eine Ausnahme vom Typ CalledProcessError aus, wenn der Befehl einen Exit-Code ungleich null zurückgibt, was auf einen Fehler hinweist.

import subprocess

try:
    ## Try to list a directory that does not exist
    ## check=True raises an exception if the command fails
    subprocess.run(["ls", "/nonexistent"], check=True, capture_output=True)

except subprocess.CalledProcessError as e:
    print("An error occurred!")
    print(f"Return code: {e.returncode}")
    ## We can also print the error message generated by the command (stderr)
    print(f"Error message: {e.stderr.decode('utf-8')}")

Speichern Sie die Datei und schließen Sie den Editor.

Führen Sie das Skript aus:

python3 handle_error.py

Sie sollten eine Ausgabe sehen, die bestätigt, dass der Fehler abgefangen wurde:

An error occurred!
Return code: 2
Error message: ls: cannot access '/nonexistent': No such file or directory

Ohne den Block try-except würde das Programm mit einem unübersichtlichen Traceback abbrechen. Mit diesem Ansatz kann Ihr Skript Fehler erkennen und entscheiden, wie es weiter vorgeht, zum Beispiel den Fehler protokollieren, den Befehl erneut ausführen oder sauber beendet werden.

Zusammenfassung

In diesem Lab haben Sie die Grundlagen zum Ausführen von Shell-Befehlen mit Pythons Modul subprocess gelernt. Sie haben Folgendes geübt:

  • das Modul subprocess zu importieren,
  • Befehle mit subprocess.run() auszuführen,
  • die Standardausgabe mit capture_output=True zu erfassen,
  • Byte-Ausgaben in lesbare Strings zu dekodieren,
  • fehlgeschlagene Befehle mit check=True und try-except-Blöcken zu behandeln.

Diese Kenntnisse sind wichtig, um mit Python Automatisierungsskripte, Deployment-Werkzeuge und Dienstprogramme für die Systemverwaltung zu erstellen.