Vorlesungsskript 16

Input und Output

STDIN, STDOUT und STDERR

Mit wenigen Ausnahmen liest jedes Programm irgendwelche Daten als Input und gibt irgendwelche Daten als Output aus. Daten können aus Dateien gelesen werden oder vom Standard-Input (abgekürzt: STDIN), den wir bisher einfach als „Input“ bezeichnet haben. Ebenso können Daten in Dateien geschrieben werden oder auf den Standard-Output (STDOUT), den wir bisher einfach als „Output“ bezeichnet haben. STDIN und STDOUT sind so genannte Datenströme. Sie stehen in der Unix-Programmierumgebung jedem Programm automatisch zur Verfügung.

Betrachten wir noch einmal eine unserer Pipelines aus Vorlesungsskript 04:

$ cat pride-and-prejudice.txt | tr -c 'A-Za-z' '\n' | tail
our
email
newsletter
to
hear
about
new
eBooks


Hier liest das Programm cat die Datei pride-and-prejudice.txt und schreibt auf seinen Standard-Output. Dieser ist durch die Pipe mit dem Standard-Input von tr verknüpft. tr liest von seinem Standard-Input und schreibt auf seinen Standard-Output, der wiederum mit dem Standard-Input von tail verknüpft ist. tail schreibt auf seinen Standard-Output. Diesen liest wiederum die Shell und gibt ihn im Terminal aus.

Wir haben auch bereits gesehen, dass die Shell den Output eines Programms in eine Datei umleiten kann:

$ cat pride-and-prejudice.txt | tr -c 'A-Za-z' '\n' > tokens.txt

cp ist ein Beispiel für ein Programm, das normalerweise weder Standard-Input noch Standard-Output gebraucht, sondern Daten direkt aus einer Datei liest (hier: moby-dick.txt) und in eine Datei schreibt (hier: copy-dick.txt), auch ohne Pipeline oder Umleitung:

$ cp moby-dick.txt copy-dick.txt

Neben STDIN und STDOUT gibt es noch einen dritten Standard-Datenstrom: STDERR. Er wird zur Ausgabe von Fehlermeldungen und Warnungen genutzt, zum Beispiel, wenn man cp ohne Argumente aufruft:

$ cp
cp: missing file operand
Try 'cp --help' for more information.

Dass die Ausgabe hier auf STDERR und nicht auf STDOUT erfolgt, kann man daran sehen, dass sie immer noch im Terminal (und nicht in der Datei) landet, wenn man STDOUT umleitet:

$ cp > output.txt
cp: missing file operand
Try 'cp --help' for more information.

STDOUT und STDERR sind deshalb getrennt, damit Fehlermeldungen von Programmen in Pipelines vom Benutzer gesehen werden und nicht das nächste Programm versucht, die Fehlermeldung weiterzuverarbeiten.

STDIN, STDOUT und STDERR in Python

In Python greift man auf STDIN, STDOUT und STDERR mit Hilfe spezieller Objekte zu: sys.stdin, sys.stdout und sys.stderr. Ihr Typ heißt _io.TextIOWrapper. Man kann sie auf verschiedene Weisen benutzen.

Zum Beispiel kann man mit Hilfe einer for-Schleife sys.stdin Zeile für Zeile lesen, wie wir das schon oft gesehen haben. Man kann aber auch den gesamten Input auf einen Schlag als einen String einlesen, mit Hilfe der read-Methode:

complete_input = sys.stdin.read()

Die print-Funktion

Für Output ist die print-Funktion das wichtigste Werkzeug. Zur Erinnerung: Man kann sie mit beliebig vielen Argumenten aufrufen. Diese werden dann alle – wenn nötig – in Strings umgewandelt und ausgegeben, getrennt von Leerzeichen und gefolgt von einem Newline-Zeichen. Zum Beispiel:

>>> print('abc', 32, ['a', 4])
abc 32 ['a', 4]

Standardmäßig erfolgt die Ausgabe bei print auf sys.stdout, deswegen muss man das nicht ausdrücklich angeben. Kann man aber, nämlich mit dem Schlüsselwort-Argument file:

>>> print('abc', 32, ['a', 4], file=sys.stdout)
abc 32 ['a', 4]

Schlüsselwort-Argumente (engl. keyword arguments) können bei vielen Funktionen und Methoden dazu verwendet werden, Details ihres Verhaltens zu beeinflussen, ähnlich wie Optionen bei Kommandozeilen-Kommandos.

Anders als die normalen Argumente – auch positionsabhängige Argumente (engl. positional arguments) genannt – ist die Reihenfolge von Schlüsselwort-Argumenten egal; sie werden durch das mit Gleichheitszeichen vorangestellte Schlüsselwort identifiziert, in obigem Beispiel file.

Schlüsselwort-Argumente können miteinander kombiniert werden und sind fast immer optional. Werden sie nicht angegeben, verwendet die jeweilige Funktion oder Methode einen Standardwert. Zum Beispiel ist der Standardwert der print-Funktion für das Schlüsselwort-Argument file: sys.stdout.

Wenn man man mit print Fehlermeldungen ausgibt, sollte man file=sys.stderr angeben, damit die Fehlermeldung auf dem STDERR-Datenstrom ausgegeben wird. Zum Beispiel:

print('ERROR: invalid input', file=sys.stderr)

Standardmäßig gibt die print-Funktion am Ende ein Newline-Zeichen aus, um die Zeile abzuschließen. Das kann man mit dem Schlüsselwort-Argument end (Standardwert: '\n') ändern:

print('Not done yet!', end=' ')
print("*Now* I'm done.")
print('New line.')

Das obige Programm gibt aus:

Not done yet! *Now* I'm done.
New line.

Auch, dass print standardmäßig ein Leerzeichen zwischen die ausgegebenen Objekte setzt, ist konfigurierbar. Das Schlüsselwort-Argument hierfür lautet sep (für separator). Hier verwenden wir zum Beispiel das Tab-Zeichen:

>>> print('der', 'die', 'ART', 1000000, sep='\t')
der	die	ART	1000000

Dateizugriff in Python

Die Standard-Datenströme sys.stdin, sys.stdout und sys.stderr sind automatisch verfügbar. Will man stattdessen aus einer Datei lesen oder in eine Datei schreiben, muss man dagegen erst einen Datenstrom für diese Datei erzeugen, indem man die Datei öffnet. Dafür gibt es die eingebaute open-Funktion. Man ruft sie mit zwei Argumenten auf: einem Pfad zu der zu öffnenden Datei und einem String, der normalerweise entweder 'r' (für read) oder 'w' (für write) ist, je nach dem, ob man lesen oder schreiben will. Sie gibt ein Dateiobjekt zurück, das man genau so benutzen kann wie sys.stdin bzw. wie sys.stdout und sys.stderr. Zu beachten ist allerdings, dass man Dateiobjekte möglichst bald nach Benutzung wieder schließen sollte, um anderen Programmen nicht in die Quere zu kommen, die möglicherweise dieselbe Datei lesen oder bearbeiten wollen. Dazu haben Dateiobjekte die close-Methode.

Hier ist ein Beispielprogramm namens linenum.py, das aus der Datei moby-dick.txt liest und ihren Inhalt mit Zeilennummern auf dem Standard-Output ausgibt:

infile = open('moby-dick.txt', 'r')
i = 0
for line in infile:
    i += 1
    print(i, line, end='')
infile.close()

Ein Beispiel-Aufruf:

$ python3 linenum.py | tail -n 20
23846 lives!'"
23847   --WHARTON THE WHALE KILLER.
23848 
23849     "So be cheery, my lads, let your hearts never fail,
23850     While the bold harpooneer is striking the whale!"
     23851   --NANTUCKET SONG.
23852 
23853     "Oh, the rare old Whale, mid storm and gale
23854       In his ocean home will be
23855     A giant in might, where might is right,
23856       And King of the boundless sea."
23857   --WHALE SONG.
23858 
23859 
23860 
23861 
23862 
23863 End of The Project Gutenberg Etext of Moby Dick, by Herman Melville
23864 
23865

Hier das gleiche Programm, diesmal allerdings mit Ausgabe in die Datei moby-dick-numbered.txt statt auf den Standard-Output:

infile = open('moby-dick.txt', 'r')
outfile = open('moby-dick-numbered.txt', 'w')
i = 0
for line in infile:
    i += 1
    print(i, line, end='', file=outfile)
infile.close()
outfile.close()

Öffnet man bestehende Dateien im Schreib-Modus, werden sie überschrieben.