Einsteigerfreundlich, schnell zu lernen und flexibel: Die Skriptsprache Python genießt trotz des vermeintlich gefährlichen Namens einen guten Ruf. Dank zahlreicher Module und einfacher Syntax reichen für Datei-Operationen, Ein- und Ausgaben und selbst fürs Monitoring eines Servers wenige Zeilen Code.
Und jetzt zu etwas völlig anderem: Eric Idle, John Cleese, Terry Gilliam, Terry Jones, Michael Palin und Graham Chapman, besser bekannt als “Monty Python”, hätten sich 1969 sicher nicht träumen lassen, dass einmal ein Fan ihrer Sketche auf die Idee käme, eine Skriptsprache nach ihnen zu benennen. Entgegen der ersten Assoziation hat die Sprache Python eben nichts mit der Familie der Riesenschlangen zu tun, sondern verdankt ihren Namen Guido van Rossums Vorliebe für die englische Komikertruppe [1].
Ein Sinn für schwarzen, hintergründigen Humor sei aber keine Voraussetzung für die Arbeit mit Python, versichert das FAQ [2]. Im Gegenteil: Immer mehr Admins entdecken Python für sich, weil die Sprache schnell, einfach und klar erscheint und den Ruf genießt, den Einstieg [3] einfach zu machen. Ihre Fans schätzen es, dass die intuitive und simple Syntax fremden Code nachvollziehbar macht, selbst wenn über Jahre hinweg Kollegen “schnell mal was” geändert oder hinzugefügt haben. Vorteilhaft ist auch, dass alle Tools, die Entwickler üblicherweise erwarten (von regulären Ausdrücken bis zu String-Manipulation und Datei-Operationen), bereits vorhanden sind. Es gibt viele Bibliotheken, mit denen sich selbst komplexeste Aufgaben lösen lassen.
Hallo Welt
Schon auf den ersten Blick fällt die Klarheit und Kompaktheit des Python-Codes auf. Das liegt daran, dass die Sprache Wert auf korrekte Einrückungen (Tabulatoren) legt. Der simple Dreizeiler
for i in range(0, 10): print "Hallo" print "Welt"
gibt abwechselnd »Hallo« und »Welt« in zehn aufeinanderfolgenden Zeilen aus (Abbildung 1), wobei Python schon an der Einrückung erkennt, dass es die »print« -Befehle als innerhalb der »for« -Schleife zu interpretieren hat. Das spart lästige »do« und »done« -Einträge.
Pythons Listen sind echte Listen, die Arrays lassen sich deutlich einfacher lesen, verändern und schreiben als andernorts. Der folgende Code gibt die in »fruit« spezifizierte Liste aus.
fruit = ['Apfel', 'Banane', 'Birne']
fruit.append('Orange')
for a in fruit:
print a
Viele Aufgaben des Admin drehen sich in irgendeiner Form ums Reporting, also darum, Informationen aus einer Quelle abzufragen und zu verarbeiten, meist von einem Server im Netzwerk. Die High-Level-Bibliotheken, die Python dafür bringt, halten den Aufwand für den skriptenden Admin überschaubar. Wer beispielsweise seinen Apache Webserver überwachen will, installiert dort das »mod_status« -Apache-Modul [4] und programmiert sich sein Monitoringskript mit wenigen Zeilen Python selbst.
Apache-Monitor
Mod_status gibt seinen Zustandsbericht für den Indianer standardmäßig unter der URL »http://Apache-Server/server-status« aus, die dort präsentierten Informationen lassen sich mit wenigen Zeilen und der Hilfe der URL-Bibliothek »urllib« leicht abfragen:
import urllib
data = urllib.urlopen('http://localhost/server-status?auto')
Die beiden Zeilen reichen völlig aus, um einen HTTP-Netzwerkrequest zu erzeugen und die Ausgabe in der Variablen »data« zu speichern. Neben vielen anderen Informationen findet sich:
BusyWorkers: 1 IdleWorkers: 5
An dieser Stelle kommt Pythons String-Manipulation ins Spiel, denn für die Weiterverarbeitung des Statusberichts wäre der ganze Rest an Informationen eher hinderlich. Wer mehr als beispielsweise 20 Worker auf seinem Apachen laufen hat – das dürfte bei der Mehrheit der Server der Fall sein –, kommt nicht umhin, eine Schleife über die Daten laufen zu lassen. Die »readlines()« erledigt das zeilenweise, »strip()« entfernt die Zeilenumbrüche und »split()« trennt den Text in zwei Teile auf (vor und nach dem Doppelpunkt):
for line in data.readlines():
key, val = line.strip().split(':', 1)
Die »1« weist »split()« an, immer nur am ersten Vorkommen des »:« zu trennen. Auch die Ausgabe bereitet dem Einsteiger kein Problem. Die Zeile:
print 'Die Anzahl der Workers ist {0}, abervielleicht sind es {1}'.format(1,2)
benutzt bereits ein paar der Methoden zur Ausgabeformatierung, die Python mitbringt. Ein Skript gäbe jetzt aus:
Die Anzahl der Workers ist 1 aber vielleicht sind es 2
Das Formatierungssystem von Python gestattet sowohl nummerierte als auch benannte Indexe, zumindest bei so genannten »dict« -Objekten (Dictionary, Wörterbuch). Diese Dictionarys sind Arrays, aber mit einem Key statt der Strings. Für das Beispiel ist das hilfreich, der Code zum Extrahieren der Statusmeldung lautet deshalb:
print 'Die Anzahl der Workers ist {BusyWorkers}'.format(**vars)
Dies übergibt das Dictionary »vars« , in dem der Key »BusyWorkers« hinterlegt ist. Listing 1 zeigt den gesamten Code für das Auslesen der Apache-Statusmeldung als Datei »apache-monitor.py« . Acht Zeilen Code erledigen einen HTTP-Request, das Parsen der erhaltenen Daten und die Aufbereitung der Ausgabe.
Listing 1
apache-monitor.py
01 #!/usr/bin/python
02
03 import urllib
04
05 data = urllib.urlopen('http://localhost/server-status?auto')
06
07 vars = {}
08 for line in data.readlines():
09 key, val = line.strip().split(':',1)
10 vars[key] = val
11
12 print vars
13 print 'The number of workers is {BusyWorkers}'.format(**vars)
Das Os.path-Modul
Das Gleiche ist sicherlich auch an der Bash möglich, wo Tools wie Wget, Grep, Sed und Awk Dienste tun. Doch solche Skripte sind meist weniger robust und schwerer lesbar als Listing 1. Außerdem läuft hier alles in einem Python-Prozess ab, statt wie die Bash mehrere Sub-Prozesse zu forken.
Ein weitere typischer Fall für Skriptsprachen ist der Umgang mit Dateien: An der Bash sind – unter anderem [5] – Leerzeichen Fehlerquellen, die bei sicherheitsbewussten Admins regelmäßig Skripte aufblasen oder aber fehlerträchtig machen. Die Bash verarbeitet einen Dateinamen nur als String, nichts mehr. Von Haus aus kann sie Leerzeichen und Separatoren nicht unterscheiden. In Python beinhaltet das »os.path« -Interface bereits alle Operationen, die im Admin-Alltag im Zusammenhang mit Dateien vorkommen.
Generatoren
Eine Schlüsselrolle in Python nehmen die Generatoren ein. Diese simplen Funktionen arbeiten ganz ähnlich wie Listen:
for root, dirs, files in os.walk('/tmp'):
print root
In diesem Fall erzeugt »os.walk()« eine lange Liste, die sich mit einer For-Schleife beackern lässt. Nett dabei ist, dass »os.walk()« das Filesystem just in dem Moment ausliest, wenn es die Daten ausgibt, ähnlich dem »find« -Kommando:
find /tmp -type d -exec echo {} \;
Um alle Dateien aus dem Verzeichnis »/tmp« zu löschen, reicht in Python:
for root, dirs, files in os.walk('/tmp'):
for file in files:
os.remove(os.path.join(root, file))
»os.path.join« macht das Skript sicherer und transportabel. Die Funktion bewirkt, dass Python – auch auf Windows-Systemen – immer den richtigen, für das Betriebssystem definierten Trenner (Field separator) für Dateinamen verwendet. Das Kommando »print os.path.join(‘/tmp’,’Dateiname‘)« gibt dann »/tmp/Dateiname« zurück.
Zum Öffnen von Dateien dient »f = open(‘Dateiname‘, ‘r’)« , was sich stark an den C-Stil anlehnt und einen Modus für das Öffnen annimmt. Hier steht »r« für read-only, »w« für Lesen und Schreiben. Ein »print f.readlines()« schreibt jetzt die Ausgabe in die Datei »Dateiname« . Analog zum Apache-Beispiel oben liest »readlines()« alle Daten aus dem File und gibt sie als Liste von Strings zurück.
Für kleine Dateien mag das ausreichen, bei großen Datenmengen wird es jedoch zum Speicherfresser. Deshalb bietet es sich an, mit »f.readline()« immer nur die nächste Zeile oder mit »f.read(1024)« die nächsten 1024 Bytes einzulesen. Ersteres reicht in fast allen Fällen.
Das »os.path« -Modul bietet noch viele weitere Funktionen, zu denen Basename, Abspath (zum Normalisieren von symbolischen Links), Getatime, Getmtime, Getctime und Exists gehören. Ipython ([6], siehe Kasten “Interaktives Python”) bringt sogar eine Kommandozeile für die Skriptsprache.
Interaktives Python
Wer eine Aufgabe vor sich hat, die ein wenig abseits der klassischen Pfade liegt und die er am Bash-Prompt nicht lösen kann, sollte sich Ipython [6] ansehen. Er erhält nach dem Aufruf von »ipython« eine interaktive Shell mit einem Python-Interpreter. Die kombiniert Shellkommandos wie Ls und Cd mit Tab-Completion und einem vollständigen Python-Environment.
Ipython verwenden
Nach dem Start präsentiert Ipython dem Anwender einen Prompt in Form von »In [1]: « . In der ersten Zeile des Beispiels aus Abbildung 2 gibt der Benutzer »import os« ein, um das »os« -Modul zu laden, und bestätigt mit [Return]. Der Prompt verändert sich zu »In [2]: « . Hier tippt der User die For-Schleife »for dir in os.walk(‘/tmp’):« ein, wiederum gefolgt von [Return]. Jetzt ändert Ipython den Prompt zu »…: « – bereits gefolgt von der richtigen Anzahl an Tabulatoren. Um die korrekte Einrückung braucht der Anwender sich hier nicht zu kümmern, diese Aufgabe nimmt ihm Ipython ab. Das eingegebene »print dir« , gefolgt von dreimal [Return], lässt Ipython den Inhalt des Verzeichnisses »/tmp« auflisten. Wie in der Bash funktioniert auch hier die automatische Vervollständigung von Kommandos mit der Tabulatortaste.
Ipython bietet sich vor allem fürs Erstellen eigener Skripte und das Erlernen der Syntax an. Auch das Einrücken erledigt die Shell von selbst, eine Leerzeile entfernt unerwünschte Einzüge. Die Ausgabe der Bash lässt sich mit Befehlen wie »a = !ps aux« in Variablen umleiten, analog zur Parametersubstitution der Linux-Shell. Ein einfaches »a« zeigt den Inhalt der Variablen an, mit »page a« benutzt Ipython den Standardpager der Bash (in der Regel Less).
Die Programmieraufgabe
Listing 2 schlägt eine Lösung für die Aufgabenstellung aus der Einleitung dieser Titelstrecke in Python vor. Zunächst definiert »def« die Funktion »createAccount()« , die sich des Useradd-Kommandos bedient. Dann folgt die Prüfung, ob das Skript als Root läuft (mit »os.getuid()« . Das eingangs geladene Modul »csv« ermöglicht den Umgang mit der CSV-Datei, die Zeile 21 einliest. Die Schleife ab Zeile 23 erledigt anschließend die verlangten Prüfungen, ehe »createAccount()« in Zeile 41 den User anlegt.
Listing 2
aufgabe.py
01 #!/usr/bin/python
03 import csv
04 import sys
05 import os
06 import re
07 import subprocess
08 import pwd
10 def createAccount(username, lastname, firstname):
11 return subprocess.call(['useradd', '-m', username, '-c', '%s, %s' % (lastname, firstname), '-U', '-s', '/bin/bash'])
13 if os.geteuid() != 0:
14 print "You must run this program as root"
15 exit(1)
17 if len(sys.argv) != 2:
18 print "Usage: %s <input.csv>" %
(sys.argv[0])
19 exit(1)
21 input = csv.reader(open(sys.argv[1]))
23 for row in input:
24 if len(row) != 3:
25 print "Line %d: Not enough data" %
input.line_num
26 continue
28 (username, lastname, firstname) = row
30 if not re.match('^[a-z][a-z0-9]+$', username):
31 print "Line %d: Login name must start with a letter, and contain only letters and numbers" % input.line_num
32 continue
34 try:
35 pwd.getpwnam(username)
36 print "Line %d: Account '%s' already exists" % (input.line_num, username)
37 continue;
38 except KeyError:
39 pass
40 createAccount(username, lastname, firstname)
Infos
- Python-FAQ: http://docs.python.org/faq/
- Monty Python: http://de.wikipedia.org/wiki/Monty_Python
- Python-Tutorial: http://python.about.com
- Mod_status«: http://httpd.apache.org/docs/2.0/mod/mod_status.html
- Bash Bashing: https://www.linux-magazin.de/NEWS/In-eigener-Sache-Statt-Boxen-Bundle-Bash-Bashing-zum-Streiten-und-Lieben
- Ipython: http://ipython.org








