subprocess: Unterprozesse starten und ihre Ausgaben lesen
Ziel
Ich kann programmatisch von Python aus ein Shell-Kommando starten und kann bei Bedarf seine Ausgaben verarbeiten.
Hintergrund
Oft gibt es für ein Teilproblem beim Programmieren schon eine Lösung X, die sich aber nicht leicht direkt aus Python aufrufen lässt, z.B. weil sie in einer anderen Sprache geschrieben ist. Ein häufiger Fall sind leistungsfähige Unix-Kommandozeilenprogramme.
Dann kommt manchmal in Frage, das externe Programm X als Unterprozess zu starten, programmatisch mit Eingaben zu versorgen und seine Ausgaben zu lesen und als Text zu verarbeiten. Das probieren wir in dieser Aufgabe aus.
Dadurch kann man manchmal in 5 Zeilen ein Problem lösen, das sonst hunderte oder tausende von Zeilen verlangt hätte. Sie können im Anschluss an diese Aufgabe ein Beispiel dafür kennenlernen: m_subprocess2
Arbeitsschritte
Wir nehmen uns vor, ein Programm zu schreiben, das ausgibt, wie viele interaktive bash-Shells gerade laufen.
Das kann man (wenn auch nicht ganz zuverlässig) mit dem Kommando ps aux
herausbekommen.
ps
listet auf Unix-Systemen Prozesse auf; jede Ausgabezeile beschreibt einen Prozess.
Alle Ausgabezeilen von ps aux
, die auf " -bash" enden, zeigen eine interaktive
bash-Shell an.
- 1 Probieren Sie
ps aux
aus.
Modul subprocess
- 1 importieren Sie
subprocess
. - Überfliegen Sie die Dokumentation: https://docs.python.org/3/library/subprocess.html. Ganz schön kompliziertes Modul! Wir benutzen davon aber nur einen relativ einfachen Anwendungsfall.
- Lesen Sie die Dokumentation von
Popen.communicate()
- 2 Schreiben Sie das Idiom in Ihr Programm, das dort angegeben ist,
und verstehen Sie es.
Verstehen Sie insbesondere die Rolle des (unvollständigen)
Popen()
-Aufrufs darin. Dafür müssen Sie ein wenig über denPopen
-Konstruktor nachlesen. - 3 Ergänzen Sie die nötigen Argumente des
Popen
-Aufrufs. Wir brauchenargs
,stdout
undshell
. - Nun haben wir in
out
die Ausgabe des Kommandos. - 4 Die lesen wir nun zeilenweise durch,
suchen darin alle Zeilen, für die gilt
endswith(b" -bash")
, und zählen sie. - 5 Das Ergebnis geben wir mit
print
aus.
Das war's schon!
Hinweis (nur bei Bedarf): Welches Argument brauche ich für args
?
"ps aux"
genügt.
Für ein ernsthaftes Programm wäre es besser, den ganzen Pfadnamen
des ps
-Kommandos anzugeben, damit man nicht versehentlich etwas anderes erwischt,
das zufällig bei diesem Benutzer unter diesem Namen im Aufrufpfad hängt.
Den Pfadnamen von ps
bekommt man mit which -a ps
heraus
oder mit command -v ps
(was auf mehr Unix-Varianten funktioniert).
Allerdings hat ein Weglassen des Pfades den Vorteil, dass der Pfad auf einem anderen
Unix-System eventuell ein anderer sein müsste, unser Programm ohne den Pfad also
möglicherweise weniger plattformabhängig ist.
Hinweis (nur bei Bedarf): Welches Argument brauche ich für stdout
?
subprocess.PIPE
, damit communicate()
die Ausgabe auffangen kann und sie nicht
auf der normalen Standardausgabe erscheinen.
Hinweis (nur bei Bedarf): Welches Argument brauche ich für shell
?
Bitte schlagen Sie nicht sämtliche Hinweise auf. Selber nachdenken ist Trumpf!
Hinweis (nur bei Bedarf): Wie geht die zeilenweise Schleife?
Iterieren Sie über out.split(b"\n")
- 2
python m_subprocess.py
Manchmal die kleine Lösung: os.system()
Gelegentlich reicht es einem, ein Kommando nur aufzurufen, ohne dabei Ein- und Ausgaben selber zu verarbeiten. Warum? Entweder, weil die Wirkung des Kommandos von Interesse ist oder weil seine Ausgaben einfach auf der Standardausgabe erscheinen sollen.
Das geht mit der Funktion system(cmd_string)
aus dem Modul os
.
das kann man z.B. für die Aufgabe mlh-gitac gebrauchen.
Abgabe
Geben Sie ein Kommandoprotokoll ab, das genau nur die Eingaben und Ausgaben der obigen Kommandos 1, 2, … enthält. Entfernen Sie vor Abgabe eventuelle Fehlversuche und sonstige zusätzliche Kommandos aus dem Protokoll.
Geben Sie den Quellcode ab, wie er am Ende der Aufgabe vorliegt.