Python und die Shell

Shell

Python und Shell Shell ist ein Begriff, der viel benutzt wird und doch oft unterschiedlich verstanden und auch missverstanden wird. Wie leicht zu erkennen ist kommt der Name aus dem Englischen. Shell bedeutet Schale und Hülle, so wie zum Beispiel die Muschelschale. Die Funktion einer Muschelschale besteht in erster Linie im Schutz der Muschel. Die Muschelschale trennt und schützt das Innere vom Äußere. So trennt auch die Shell, den Kern des Betriebssystems von dem Shell-Benutzer. Die Shell gibt ihm oder ihr robuste und einfach zu bedienende Funktionen an die Hand, damit er oder sie nicht die komplizierten und fehleranfälligen Betriebssystemfunktionen benutzen muss.

Generell unterscheidet man bei Betriebssystemen zwei Arten von Shells, In den meisten Fällen wird der Begriff jedoch als Synonym für Kommandozeileninterpreter (CLI) benutzt, so wie zum Beispiel die Bourne-Shell, C-Shell oder Bash unter Unix.

Systemprogrammierung

Unter Systemprogrammierung versteht man die Programmierung von Softwarekomponenten, die Teil des Betriebssystems sind oder die eng mit dem Betriebssystem bzw. mit der darunter liegenden Hardware kommunizieren müssen. Systemnahe Software, wie beispielsweise das sys-Modul in Python, fungiert als Abstraktionsschicht zwischen der Anwendung, dem Python-Programm, und dem Betriebssystem. Mit Hilfe dieser Abstraktionsschicht lassen sich Applikationen plattformunabhängig implementieren, auch wenn sie direkt auf Betriebssystemfunktionalitäten zugreifen.

Python eignet sich besonders gut für die Systemprogrammierung. All das, was auch im Allgemeinen für Python gilt, zeichnet auch die Vorteile von Python in der systemnahen Programmierung aus:

Das os-Modul

Das os-Modul ist das wichtigste Modul zur Interaktion mit dem Betriebssystem und ermöglicht durch abstrakte Methoden ein plattformunabhängiges Programmieren. Gleichzeitig ist es auch mit Funktionen wie os.system() oder der exec*()-Funktionsfamile möglich, betriebssystemabhängige Programmteile einzubauen. (Die Familie der exec*()-Funktionen haben wir im Kapitel "Forks und Forking in Python" besprochen.)
Das os-Modul bietet viele unterschiedliche Methoden, wie beispielsweise zum Zugriff auf das Dateisystem.

Shell-Skripte mit os.system() ausführen

In Python ist es schwierig ein Zeichen ohne Betätigung der Return-Taste einzulesen. In der Bash-Shell ist das sehr einfach. Dort gibt es den read-Befehl. "read -n 1" wartet bis eine Taste gedrückt wird. Benutzt man os.system() in Python kann man sehr leicht eine Funktion getch() schreiben, die auch in Python mittels read auf ein Zeichen wartet.
import os
def getch():
     os.system("bash -c \"read -n 1\"")
 
getch()
Obiges Skript funktioniert allerdings nur unter Linux. Unter Windows muss man das Modul msvcrt importieren. Genaugenommen aus diesem Modul die Funktion getch. Auch wenn es uns hier in erster Linie um Linux geht, hier dennoch die Lösung unter Windows:
from msvcrt import getch
Die folgende Lösung implementiert in Abhängigkeit vom Betriebssystem eine Funktion getch():
import os, platform
if platform.system() == "Windows":
    import msvcrt
def getch():
    if platform.system() == "Linux":
        os.system("bash -c \"read -n 1\"")
    else:
        msvcrt.getch()

print("Taste druecken!")
getch()
print("Okay")
Das vorige Beispiel birgt aber ein Problem. Die Funktion getch() des vorigen Beispiels sollte eigentlich einen Wert zurückgeben, d.h. das Zeichen, das eingelesen wurde. os.system() gibt aber nicht den ausgewerteten Wert des Kommando-Strings zurück. os.system() ist als Aufruf der Standard C-Funktion system() implementiert. Der Rückgabewert von os.system() ist nicht von POSIX spezifiziert und damit systemabhängig. Im folgenden Skript zeigen wir, wie man mittels os.popen() Shell-Befehle und Shell-Skripte ausführen kann und die Ergebniss in Python einlesen kann:
>>> import os
>>> dir = os.popen("ls").readlines()
>>> print dir
['curses.py\n', 'curses.pyc\n', 'errors.txt\n', 'getch.py\n', 'getch.pyc\n', 'more.py\n',
'numbers.txt\n', 'output.txt\n', 'redirecting_output.py\n', 'redirecting_stderr2.py\n', 
'redirecting_stderr.py\n', 'streams.py\n',  'test.txt\n']
>>> 
Die Shell-Ausgaben kann man auch Zeile für Zeile einlesen, wie man im folgenden Skript sieht:
import os

command = " "
while (command != "exit"):
    command = raw_input("Kommando: ")
    handle = os.popen(command)
    line = " "
    while line:
        line = handle.read()
        print line
    handle.close()

print "Ciao, that's it!"

subprocess-Modul

Seit Python 2.4. gibt es das Modul subprocess.
Mit subprocess ist es möglich neue Prozesse per spawn zu kreieren und sich mit deren input/output/error-Kanälen zu verbinden um die return-Codes zu erhalten.
Modul subprocess soll andere Module ersetzen:

Arbeiten mit subprocess

Statt dem system-Kommando des os-Moduls
os.system('touch xyz')
formuliert man im subprocess-Modul die folgenden Anweisungen, um die Resultate des Skripts zu erhalten:
>>> import subprocess
>>> x = subprocess.Popen(['touch', 'xyz'])
>>> print x
<subprocess.Popen object at 0x7f29882f6f50>
>>> x.poll()
0
>>> x.returncode
0
Den Befehl cp -r xyz abc kann man unter Python mit dem subprocess-Modul folgendermaßen abschicken:
p = subprocess.Popen(['cp','-r', "xyz", "abc"])
Leerzeichen und Shell-Metazeichen ($, > usw.) müssen nicht "escaped" werden. Will man das Verhalten von os.system emulieren, setzt man shell=True und gibt einen String statt einer Liste an:
p = subprocess.Popen("cp -r xyz abc", shell=True)

Will man die Ausgabe eines Shell-Skriptes in Python übernehmen, so muss man stdout auf subprocess.PIPE setzen:
>>> process = subprocess.Popen(['ls','-l'], stdout=subprocess.PIPE)
>>> print process.stdout.read()
total 132
-rw-r--r-- 1 bernd bernd   0 2010-10-06 10:03 abc
-rw-r--r-- 1 bernd bernd   0 2010-10-06 10:04 abcd
-rw-r--r-- 1 bernd bernd 660 2010-09-30 21:34 curses.py


Es kann sinnvoll sein nach dem Starten eines subprocesses mit .wait() zu warten bis das gestartete Skript komplett ausgeführt worden ist:
>>> process = subprocess.Popen(['ls','-l'], stdout=subprocess.PIPE)
>>> process.wait()
0

Funktionen zur Bearbeitung von Verzeichnissen und Dateien

Funktion Beschreibung
chdir(path) Das aktuelle Arbeitsverzeichnis (working directory) wird in path geändert.
getcwd() Liefert einen String mit dem aktuellen Arbeitsverzeichnis zurück.
getcwdu() wie getcwd() nur unicode als Ausgabe
listdir(path) Eine Liste des Inhaltes (Dateinamen, Unterverzeichnisse usw.) des Verzeichnisses path
mkdir(path[, mode=0755]) Ein neues Verzeichnis wird erzeugt. Falls übergeordnete Verzeichnisse in path nicht existieren, wird ein Fehler erzeugt und kein Verzeichnis angelegt.
makedirs(name[, mode=511]) Wie mkdir, legt jedoch auch übergeordnete Verzeichnisse automatisch an.
rename(old, new) Eine Datei oder Verzeichnis wird umbenannt
renames(old, new) Wie rename, aber auch übergeordnete Verzeichnisse in "old" werden, falls nicht vorhanden angelegt. Allerdings nur, wenn die nötige Berechtigung vorliegt.
rmdir(path) Ein Verzeichnis wird gelöscht, falls es leer ist.

Weitere Funktionen, die auf Dateien und Verzeichnissen arbeiten, liefert das Modul shutil. Es bietet unter anderem Möglichkeiten Dateien und Verzeichnisse zu kopieren, z.B. shutil.copyfile(src,dst).