Skriptprogrammierung mit Python

Werkzeugkasten

Es ist nicht alles Shell, was glänzt. Auch mit Python lassen sich Systemaufgaben unkompliziert skripten. Dieser Artikel gibt eine Einführung in die moderne Skriptsprache.
Duell der Datenbanken: In einem Shootout messen sich MySQL und PostgreSQL. Der Schwerpunkt vom ADMIN 06/2011 überprüft, wer schneller ist und gibt einen ... (mehr)

Wenn ich auf einem Linux-Rechner ein komplizierteres Problem auf der Kommandozeile lösen möchte, mache ich das nur in einfachen Fällen mit einem Shellskript. Bei Fallunterscheidungen, Schleifen und Variablen ist mir die Syntax zu kompliziert. Das liegt sicher auch an fehlender Übung, aber warum sollte ich mich mit dem Erlernen einer weiteren Programmiersprache, in diesem Fall der Shell abmühen, wenn ich etwa mit Python einfache wie komplexe Programme schreiben kann?

Zudem hat sich Python in den letzten 15 Jahren als Standard auf allen Betriebssystemen und Linux-Distributionen etabliert. Die Versionen 2.5 bis 2.7 sind weitgehend kompatibel und auf den meisten Plattformen verfügbar – einschließlich Windows und Mac OS X. Python zeigt sich inklusive seiner umfangreichen Zusatzbibliotheken als ausgereiftere Plattform. Bei aller Sympathie für Ruby muss man feststellen, dass es in der Ruby-Welt häufige und teilweise inkompatible Änderungen gibt, die die Portabilität von Skripten erschweren (eine Lösung dafür bietet der Ruby Version Manager, den ein Artikel in diesem Heft näher vorstellt).

Über die Installation von Python gibt es nicht viele Worte zu verlieren, da es auf allen Linux-Distributionen wie auch auf Mac OS X standardmäßig zur Verfügung steht. Für Windows gibt es einfache Installer, die nur wenige Klicks benötigen. Eine interessante Alternative für Windows ist der Ironpython-Interpreter, der die Programmiersprache auf der Dotnet-Runtime implementiert.

Weißraum

Das charakteristische Feature von Python, an dem sich die Geister scheiden, ist die Kennzeichnung von Blocks per Whitespace, das heißt durch die Tiefe der Einrückung, sei es mittels Leer- oder Tab-Zeichen. Ruft man den Python-Interpreter »python« auf der Kommandozeile auf, gelangt man in einen Modus, der sich für die interaktive Erforschung der Sprache eignet. Alternativ dazu bietet sich der IPython-Interpreter [1] an, der ebenfalls eine interaktive Umgebung aber noch einigen Komfort mehr – wie etwa die automatische Vervollständigung von Methoden – bietet (Abbildung  1).

Abbildung 1: Die IPython-Umgebung bietet gegenüber dem normalen Python-Interpreter noch mehr Komfort: etwa Tab-Completion, automatische Ergänzung von Klammern, Objekt-Introspektion und vieles mehr.

Eine einfache If-Unterscheidung sieht in Python beispielsweise so aus:

user = "root"
if user == "root":
 print "Superuser"
 privileged = True
else:
 print "Normalo"
 privileged = False

Hier ist zu sehen, wie die einzelnen Blöcke durch die Einrückungstiefe gekennzeichnet werden. Vor der Print-Anweisung im If-Block und der Zuweisung an die Variable »privileged« muss die gleiche Zahl an Leer- beziehungsweise Tab-Zeichen stehen. Ich bevorzuge hierbei Leerzeichen, da es erfahrungsgemäß weniger Probleme damit gibt, ein Skript in unterschiedlichen Editoren und auf mehreren Plattformen zu bearbeiten. Hinweise zur entsprechenden Einstellung des Vi(m)-Editors gibt der Kasten "Vim-Konfiguration". Praktisch ist hierbei, dass man zur Eingabe trotzdem die Tab-Taste verwenden kann, die der Editor dann durch die konfigurierte Zahl von Leerzeichen ersetzt.

Vim-Konfiguration

Da Python die Grenzen von Blocks per Einrückungstiefe kennzeichnet, muss der verwendete Editor sorgfältig damit umgehen und am besten die Arbeit damit erleichtern. Es bietet sich an, ihn so einzustellen, dass ein Druck auf die Tab-Taste zwar den folgenden Text ein Stück weit einrückt, aber statt eines Tab-Zeichens die entsprechende Anzahl an Leerzeichen eingefügt wird. Im Vi(m)-Editor lässt sich das mit der folgenden Konfiguration in der Datei ».vimrc« erreichen:

set expandtab
set tabstop=3
set shiftwidth=3
set smartindent

Was das obige Code-Beispiel außerdem illustriert, ist die Tatsache, dass Anweisungen nicht durch Semikolons abgeschlossen werden müssen (wie etwa in Perl oder PHP). Hinter den Fall-Unterscheidungen von If und Else folgt jeweils ein Doppelpunkt. Das ist beispielsweise auch bei For- oder While-Schleifen der Fall, mit denen man über häufig verwendete Datenstrukturen wie numerische und assoziative Arrays (sogenannte Dictionaries) iteriert, etwa:

packages = ['apache', 'xftpd', 'postfix']
for package in packages:
 print package

Diese Syntax ist auch für Nicht-Programmierer beinahe intuitiv verständlich, während die Verarbeitung von Arrays in einem Bash-Skript mit vielen eckigen Klammern mir eher qualvoll erscheint. Bei jedem Schleifendurchgang findet hier eine Zuweisung statt, die jeweils das aktuelle Element der Liste »packages« an die Variable »package« zuweist. Etwas komplizierter gestaltet sich das Gleiche mit Dictionaries. Mit der eingebauten Methode »keys()« lassen sich die Schlüssel auslesen, mit »values()« die dazugehörigen Werte. Iteriert man über die Keys, kann man den jeweiligen Schlüssel dazu verwenden, um an den Wert zu gelangen:

versions = {'apache': 2.4, 'xftpd': 1.0}
for package in versions.keys():
 print package, versions[package]

Das lässt sich mit der Methode »items()« auch in einem Schritt erreichen:

for package, version in versions.items():
 print package, version

Wie man hier sieht, besitzt Python als objektorientierte Programmiersprache für die eingebauten Datentypen eine Reihe praktischer Methoden, um zum Beispiel Listen (Arrays) zu verändern: »append()« hängt ein Element an, »del()« löscht ein Element per Index, »remove()« löscht es nach dem Wert, »len()« gibt die Länge des Arrays aus und so weiter. Alle Methoden, die zu einem Datentyp (oder anderen Klassen) gehören, lassen sich der Python-API-Dokumentation entnehmen. Beispielsweise empfiehlt sich ein Blick in die String-Klasse [2], da man ohne die Verarbeitung von Strings wohl in den seltensten Fällen auskommen wird.

Module

Python besteht aus dem Interpreter, der den Sprachkern implementiert und aus einer Sammlung von Standard-Modulen, ohne die sich kaum Skripts schreiben lassen, die wirklich Arbeit verrichten. So implementiert das Modul »os« eine Schnittstelle zum Betriebssystem [3]. Viele Methoden sind auf allen Betriebssystemen gleichermaßen verfügbar, manche unterscheiden sich zwischen Unix (Linux, Mac OS X …) und Windows, andere lassen sich sogar dazu verwenden, Portabilitätsprobleme zu lösen. Einen Überblick über die aktuelle Umgebung liefert beispielsweise das Dictionary »os.environ« , dessen Ausgabe auf Mac OS X in Listing 1 abgedruckt ist.

Listing 1

os.environ auf OS X

01 >>> import os
02 >>> for key, value in os.environ.items():
03 ...   print key, value
04 ...
05 VERSIONER_PYTHON_PREFER_32_BIT no
06 TERM_PROGRAM_VERSION 273.1
07 LOGNAME oliver
08 USER oliver
09 PATH /opt/local/bin:/opt/local/sbin:/Users/oliver/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/texbin:/usr/X11/bin
10 DISPLAY /tmp/launch-X9bzHU/org.x:0
11 TERM_PROGRAM Apple_Terminal
12 LANG de_DE.UTF-8
13 TERM xterm-color
14 VERSIONER_PYTHON_VERSION 2.6
15 SHLVL 1
16 _ /usr/bin/python
17 HOME /Users/oliver
18 SSH_AUTH_SOCK /tmp/launch-9nXMJp/Listeners
19 SHELL /bin/bash
20 TMPDIR /var/folders/Sd/SdnFaCHZE5CuSIG3-c1lpU+++TI/-Tmp-/
21 __CF_USER_TEXT_ENCODING 0x1F5:0:3
22 PWD /Users/oliver
23 COMMAND_MODE unix2003

In Listing 1 ist auch zu sehen, dass nicht im Sprachkern vorhandene Module über die Import-Anweisung geladen werden müssen. Als "Bad Practice" gilt es dabei eigentlich, ein komplettes Modul per »import Modul« zu laden. Stattdessen sollte man nur die Methoden beziehungsweise Klassen importieren, die man auch wirklich verwendet, etwa mit »from string import lower« .

Mit dem OS-Modul lassen sich typische Teilschritte von Shellskripten lösen, wie etwa in ein Verzeichnis zu wechseln (»chdir()« ), das aktuelle Arbeitsverzeichnis auslesen (»getcwd()« ). Die Eigentumsverhältnisse ändert »os.chown()« , die Rechte »os.chmod()« . Dateien und Verzeichnisse lassen sich mit dem Modul anlegen, löschen, umbenennen. Darüber hinaus enthält es Low-Level-Methoden, um Dateien zu öffen, zu lesen, zu schreiben und so weiter. Mit der Methode »walk()« kann ein Skript einen Verzeichnisbaum durchschreiten, wie es Listing 2, ein Beispiel aus der Python-Dokumentation, vormacht. Ab Python 2.6 versteht die Walk-Methode einen Parameter »followlinks« , der festlegt, ob sie symbolischen Links folgt oder nicht.

Listing 2

os.walk()

01 import os
02 from os.path import join, getsize
03 for root, dirs, files in os.walk('python/Lib/email'):
04     print root, "consumes",
05     print sum(getsize(join(root, name)) for name in files),
06     print "bytes in", len(files), "non-directory files"
07     if 'CVS' in dirs:
08         dirs.remove('CVS')  # don't visit CVS directories

Bis zu Python 2.5 waren auch die Popen-Methoden, die die Kommunikation mit Subprozessen ermöglichen, Bestandteil des OS-Moduls. Mit Version 2.6 wurden sie durch ein neues Subprocess-Modul ersetzt [4]. Damit lassen sich zum Beispiel Unix-Tools aufrufen und deren Ausgabe im Python-Skript weiterverarbeiten. Im einfachsten Fall geht das mit »subprocess.Popen()« , dem man als erstes Argument die auszuführende Kommandozeile als Array übergibt. Die dafür nötigen Tokens gewinnt man aus einer einfachen Zeichenkette beispielsweise mit »split()« aus dem »shlex« -Modul:

cmd = '''import -window root screen.png'''
args = shlex.split(cmd)
subprocess.Popen(args)

Die Popen-Methode kennt noch eine Reihe weiterer Parameter, etwa um ein Dictionary von Umgebungsvariablen zu übergeben. Insbesondere sollte man in den meisten Fällen darauf verzichten, per »Shell=true« eine Subshell zu starten, weil das mögliche Sicherheitslücken öffnet.

Um an die Ergebnisse zu gelangen, bietet es sich an, die Standardausgabe umzuleiten und dann mit der Communicate-Methode auszulesen:

result = subprocess.Popen("ls", stdout=subprocess.PIPE).communicate()
print result
('artikel.txt\n', None)
print result[0]
'artikel.txt\nbild.png\n'

Das ist zugegebenermaßen keine besonders schöne Methode und ziemlich viel zu tippen. Einfacher geht es mit dem OS-Modul, auch wenn dessen Verwendung nicht mehr empfohlen wird:

result = os.popen('ls').read()
'artikel.txt\nbild.png\n'

Das Ergebnis kann mithilfe der »split()« -Methode der String-Klasse wieder leicht in eine Liste zerlegt werden. Man sollte auch nur im Ausnahmefall auf das Aufrufen von Unix-Kommandos ausweichen und stattdessen eines der über 3000 Python-Module verwenden, die die meisten Anwendungsgebiete abdecken.

Ähnliche Artikel

comments powered by Disqus

Artikel der Woche

Skalierbares Monitoring mit Prometheus

Klassischen Monitoring-Lösungen geht die Puste aus, wenn sie mit den Anforderungen moderner, skalierbarer Setups konfrontiert sind. Prometheus tritt an, in solchen Umgebungen Monitoring, Alerting und Trending zu verwirklichen. (mehr)
Einmal pro Woche aktuelle News, kostenlose Artikel und nützliche ADMIN-Tipps.
Ich habe die Datenschutzerklärung gelesen und bin einverstanden.

Linux-Backup

Welche Backup-Lösung setzen Sie für Linux im professionellen Umfeld ein?

  • keine
  • eigene Scripts
  • rsnapshot
  • rdiff-backup
  • Bacula
  • Bareos
  • Borg
  • Duplicity
  • Amanda
  • Burp
  • Clonezilla
  • Acronis
  • Arkeia
  • SEP sesam
  • Veeam
  • Redo Backup
  • Relax-and-Recover
  • andere kommerzielle Software
  • andere Open-Source-Software

Google+

Ausgabe /2017

Microsite