basics michael poeltl © 2013

arbeiten mit files

Es ist an der Zeit (endlich) zu lernen, wie man files lesen kann, deren Inhalt (im script) nutzen kann, in files schreiben kann, die in file-Objekten enthaltene Information abrufen kann.

Wir wollen uns hier bloß mit *regulären* files abgeben, und nicht mit binaries, sockets, blockdevices, etcetc.
Dazu müssen freilich auch die nötigen Rechte (vor allem read-Recht) gesetzt sein. Wir werden also im py-script nicht /etc/shadow auslesen koennen, wenn wir nicht gerade als root das script ausführen.

open()

Zum Öffnen eines files nutzen wir die open()-Funktion.
help(open)
gibt dir eine Vorstellung, was alles mit open geregelt werden kann. Ich werde mich hier aber auf das Wesentliche, Einfache und *Übliche* beschränken.
Zum Beispiel

user@host:~> cat /etc/host.conf
# The "order" line is only used by old versions of the C library.
order hosts, bind
multi on
user@host:~> python3
...
>>> datei = open('/etc/host.conf')
>>> datei.readable()
True
>>> datei.writable()
False
>>> print ( datei.read() )
# The "order" line is only used by old versions of the C library.
order hosts,bind
multi on

>>>

Du siehst schon mal, dass der Zugriff auf das file per default auf *lesend* (readable() -> True) und nicht *schreibend* (writeable() -> False) gesetzt wird.
Welche Infos können wir sonst noch über das file bekommen, bevor wir uns den Inhalt ansehen?

>>> datei.name
'/etc/host.conf'
>>> datei.encoding
'UTF-8'
>>> datei.closed
False
>>>

das file-object ist solange *geöffnet* (Frage: datei.closed?, Antwort: False (also Nein, fileobject auf das datei zeigt ist noch offen.)
Schließen wir das fileobject einmal, um zu sehen, was dann zurückgegeben wird (wir erwarten als Antwort True).

close()

>>> datei.close()
>>> datei.closed
True

as expected!
Mit der close()-Methode am file-object wird das file geschlossen, aber was ist nun mit dem file-object, auf das der identifier datei zeigt? Augenscheinlich ist das Objekt ja noch vorhanden, denn sonst koennte ich ja nicht den status von datei.closed (False/True) abfragen.

>>> datei.name
'/etc/host.conf'
>>> datei.encoding
'UTF-8'
>>> datei.readable()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file
>>>

Das fileobject ist nicht verschwunden - wir bekommen die Info über das fileobject mit closed==True, obwohl Attribute wie readable() oder writeaable() bei einem geschlossenen fileobject nicht mehr vorhanden zu sein scheinen, und daher eine ValueError-Exception geraised wird.
Erst wenn wir den identifier aus'm namespace entfernen (del datei oder man lässt den Namen datei auf ein anderes Objekt zeigen) und sonst kein Name (identifier) mehr auf dieses Objekt zeigte, erst dann ist das fileobject bereit, vom garbage collector zum Überschreiben im RAM frei gegeben zu werden.

WICHTIG! Nachdem man das file genutzt hat, es also nicht mehr notwendig ist, das file offen zu halten, sollte das file in jedem Fall geschlossen werden mit der close()-Methode. wir werden noch das with statement kennenlernen, das uns diese Arbeit abnimmt, oder noch besser formuliert, das uns hilft, nicht auf's Schließen zu vergessen.
Ein offenes fileobject sollte nicht unreferenziert im RAM herumsumpern!

Wie kann man nun das vorige fileobject wieder öffnen? Ich kenne bislang kein reopen, aber wie wäre es damit?

>>> id(datei)
140626017577800
>>> datei = open(datei.name)
>>> datei.closed
False
>>> id(datei)
25268304
>>> datei.name
'/etc/host.conf'
>>> datei.close()

Wir haben nun eine Möglichkeit kennengelernt, das file /etc/host.conf erneut zu öffnen. Dabei wurde aber ein neues fileobject angelegt, was wir über die Speicheradresse im RAM gezeigt haben.

file descriptor

>>> datei = open(datei.name)
>>> datei.fileno()
3
>>>

fileno() gibt uns den filedescriptor (fd) zurück. 0 ist reserviert für stdin, 1 für stdout und 2 ist reserviert für stderr.
anhand des filedescriptors sieht man, wieviele fileobjects im Arbeitsspeicher liegen, auf die referenziert wird. 3-2 => 1 fileobject (es gibt aber keinen Rückschluss darauf, wieviele fileobjects davon offen sind).

datei ist nun nicht nur ein Name, der auf ein fileobject mit fd 3 zeigt, sondern auch namespace für Methodennamen, die am fileobject angewendet werden können.

>>> dir(datei)
['_CHUNK_SIZE', '__class__', '__delattr__', '__dict__', '__doc__', '__enter__', '__eq__',
'__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__',
'__hash__', '__init__', '__iter__', '__le__', '__lt__', '__ne__', '__new__',
'__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable',
 '_checkSeekable', '_checkWritable', 'buffer', 'close', 'closed', 'detach',
'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode',
'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'seek',
'seekable', 'tell', 'truncate', 'writable', 'write', 'writelines']
>>> print ( [ x for x in dir(datei) if x.startswith('read') ] )
['read', 'readable', 'readline', 'readlines']
>>>

Und weil es so lustig ist, öffnen wir dasselbe file nocheinmal, lassen aber einen anderen identifier drauf zeigen, und das dritte Mal lassen wir einen identifier aufs selbe fileobject zeigen.

>>> datei2 = open(datei.name)
>>> datei is datei2
False
>>> datei == datei2
False
>>> datei3 = datei
>>> datei3 is datei
True
>>> datei3.fileno()
3
>>> datei2.fileno()
4
>>> import sys
>>> sys.getrefcount(datei)
3
>>> sys.getrefcount(datei2)
2
>>> datei.close()
>>> datei2.close()
>>> del datei,datei2,datei3
>>>

Ich kann jetzt nicht beantworten, wer außer datei2 noch auf das fileobject mit fd 4 zeigt, da ich es nicht weiß. (Vielleicht kommt mir die Antwort 'mal unter, dann reich ich sie gleich weiter.)

write()

>>> erstes_file = open('/tmp/erstes_file.txt', 'w')
>>> erstes_file.write('Hello world\ndies ist mein erstesr filecontent')
45
>>> erstes_file.close()
>>> erstes_file=open(erstes_file.name)
>>> print(erstes_file)
<_io.TextIOWrapper name='/tmp/erstes_file.txt' mode='r' encoding='UTF-8'>
>>> print( erstes_file.read() )
Hello world
dies ist mein erstesr filecontent
>>> print( erstes_file.read() )

>>> 

Hier wurde also ein fileobject gebaut, mit write (w) mode. Warum der zweite Aufruf von erstes_file.read() nichts mehr ausgegeben hat, sehen wir uns a bissi später an. Vielleicht weißt aber eh schon, warum das so ist.
Hätte es das file im filesystem schon gegeben, wäre der Inhalt komplett gelöscht worden nach dem Aufruf der open()-function, bevor es mit dem von mir verfassten characters gefüllt wurde. Du kannst dir ja in einem anderen Terminal das file öffnen mit
tail -f /tmp/erstes_file.txt
um zu sehen, dass, obwohl die write()-Methode fertig abgesetzt war, der content NOCH NICHT im filesystem gelandet ist. Der Moment, an dem das file geschrieben wurde, ist jener, an dem ich das fileobject schließe (close()) ODER, wo ich die flush()-Methode anwende (close() führt ein final-flush durch -> wieder ein Grund, warum close() so wichtig ist).
flush() dumped also den content, der noch im RAM liegt ins file auf der disk.

Wollte man nun ins file content hinzufügen, dann wiederum mit einem write(). Und das kann man sooft machen, solange das fileobject offen (closed => False) ist.

Würde dieses file nun geschlossen werden (close()), und erneut mit Modus 'w' geöffnet werden, dann wäre der vorige content wieder komplett gewiped worden. Wir haben also den Moment erreicht, an dem wir uns über modi des file-Öffnens unterhalten müssen.

Zuvor noch eine Erklärung zur Zahl, die nach dem write() ausgegeben wurde. Das sind einfach nur die Anzahl der characters (bytes), die von der aktuellen Position des cursors aus ins file geschrieben wurden.

seek() und tell()

Man kann sich das geöffnete file wie ein Magnetband vorstellen, von dem via eines Lesekopfes gelesen werden kann bzw. auf das Magnetband geschrieben werden kann. Dieser Lese-/Schreibkopf wird cursor genannt, der sich an einer gewissen Position im file befindet.

Wenn wir den Inhalt eines files mit der read()-Methode auslesen lassen, dann wandert der cursor vom ersten byte weg an die unterste Stelle. Und der erneute Versuch, das file auslesen zu wollen, schlug fehl, da das Lesen (auch das Schreiben) immer AB cursor-Position passiert. Da der cursor ganz unten war, und danach kein Inhalt mehr zu lesen war, gab die print-function scheinbar Nichts mehr aus; tatsächlich gab die print-function einen leeren string ('') zurück. Also nochmal von vorne, diesmal mit seek() und tell().

>>> zweites = open('/tmp/zweites', 'w')
>>> zweites.write('Das ist das zweite file\nich bin beeindruckt\n')
44
>>> 

OK - wir wissen - es wurden 44 Bytes geschrieben. Öffnen wir das file nun lesend...

>>> zweites.close()
>>> zweites = open(zweites.name)
>>> zweites.tell()
0
>>> print ( zweites.read() )
Das ist das zweite file
ich bin beeindruckt

>>> zweites.tell()
44
>>> print ( zweites.read() )

>>> zweites.seek(0)
0
>>> print ( zweites.read() )
Das ist das zweite file
ich bin beeindruckt

>>>

Hier sieht man den Einsatz von seek() und tell() recht gut.
Lass uns 'mal checken, wie das ist, wenn wir read() die Bytes vorgeben, die es ab der cursor-Position lesen soll.

>>> zweites.seek(0)
0
>>> zweites.read(3)
'Das'
>>> zweites.seek(4)
4
>>> zweites.read(3)
'ist'
>>> zweites.tell()
7
>>>

tell() bestätigte uns, was wir uns selber ausrechnen konnten. Von cursor-Position vier (4) ausgehend drei (3) Bytes weiter.

Dem cursor wurden über die seek()-methode immer nur eine ABSOLUTE Position angegeben, zu der dieser sich hinbewegen sollte. Bevor wir uns dem thema widmen, wie man den cursor relativ zur aktuellen Position bewegen kann, oder gleich ans Ende des files, m&oum;chte ich noch die modi einflechten, in denen ein file geöffnet werden kann. Click here, falls Du (vorerst) den modi-part überspringen magst...

modus

nochmal zu seek()

Jetzt, da wir die modi kenne, in denen das file geöffnet werdenkann, fällt es mir leichter, auf die relative Positionierung des cursors näher einzugehen.
Wenn man die python-docu liest, dann sieht man, dass der seek-Methode ein zweiter Parameter (optional) mitgegeben werden kann.

seek(offset, from_what
wobei from_wat folgende Werte annehmen kann:

Probieren wir das gleich einmal aus (absichtlich *unschuldig*) und lass' 'mas ami krachen ;-)

>>> f = open('/etc/host.conf')
>>> f.tell()
0
>>> f.seek(20)
20
>>> f.seek(2,1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
io.UnsupportedOperation: can't do nonzero cur-relative seeks
>>>

Das kracht, weil wir das file im Textmodus (default) geöffnet haben. Hier funktionieren die what_from mit einer Ausnahme NICHT.
Zuerst die ausnahme; will man den cursor ans ende des files lotsen, dann geht das mit der Angabe:

>>> f.seek(0,2)
92
>>>

Und so weiß man nunauch, wieviele bytes (characters) dasfile groß ist.
Will man relativ zur aktuellen cursor-Position den cursor bewegen, dann kann man sich via tell() helfen.
Zum Beispiel

>>> f.tell()
92
>>> f.seek(f.tell() - 22)
70
>>>

wurde das file im binary-mode geöffnet, braucht man den workaround mit tell() nicht bemühen.

>>> f.close()
>>> f = open('/etc/host.conf', 'rb')
>>> f.tell()
0
>>> f.read(1)
b'#'
>>> f.seek(5,2)
97
>>> f.read(1)
b''
f.seek(0,2)
92
>>> f.seek(-10,2)
82
>>> f.seek(1,1)
83
>>> f.seek(-100,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 22] Invalid argument
>>>

Wir durften zwar unter dem eigentlichen ende des files den cursor platzieren (97), erhielten aber eine IOError-Exception beim Versuch, den cursor vor den eigentlichen anfang des files zu platzieren. Köm;nnten wir an der Position 97 schreiben? hmm, um das zu checken,öffnen wir besser ein file, wo wir auch verlässlich schreiben können.

>>> f.close()
>>> f = open('/tmp/drittes','wb+')
>>> f.write(b'hallo, jetzt einmal ein Test im\nbinary-mode, und wir starten den Versuch\nunterhalb des eof den cursor zu positionieren\nund von da ausgehend einen weiteren write zu starten.')
172
>>> f.tell()
172
>>> f.seek(0)
0
>>> print ( f.read() )
b'hallo, jetzt einmal ein Test im\nbinary-mode, und wir starten den Versuch\nunterhalb des eof den cursor zu positionieren\nund von da ausgehend einen weiteren write zu starten.'
>>> f.seek(5,2)
177
>>> f.write(b'angehaengt 5bytes weiter weg vom eof, also\nnicht 172 sondern pos 177')
68
>>> f.tell()
245
>>> f.seek(0)
0
>>> print ( f.read().decode() )
hallo, jetzt einmal ein Test im
binary-mode, und wir starten den Versuch
unterhalb des eof den cursor zu positionieren
und von da ausgehend einen weiteren write zu starten.angehaengt 5bytes weiter weg vom eof, also
nicht 172 sondern pos 177
>>>

staeunenswererweise hat das funktioniert! Was aber steht zwischen pos 172 und pos 175??

>>> f.seek(172)
172
>>> f.read(5)
b'\x00\x00\x00\x00\x00'
>>> f.seek(172)
172
>>> print ( f.read(5).decode() )

>>>

das wurde also mit spaces gefüllt? Nicht wirklich - scheint eher so, als würde der Platz auf der Platte ausgespart werden und als EIN space ausgegeben werden.

with

>>> with open('inputfile.txt') as infile, open('outputfile.txt', 'w') as outfile:
...     for line in infile:
...         if '<critical>' in line:
...             outfile.write(line)

Das gibt es seit python3.1.X.


Hier geht es zum Seitenanfang und da zum Überblick der basics.