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.