[Allegro] Trick 58: Datei Byte fuer Byte lesen und verarbeiten
Bernhard Eversberg
ev at biblio.tu-bs.de
Di Okt 16 09:31:49 CEST 2007
Trick 58: Strategie der kleinsten Schritte
(Binäre Dateien Byte für Byte verarbeiten)
Aufgabe: Schnell mal eben eine Binärdatei durchsehen und schauen, ob
eine bestimmte Codefolge darin vorkommt und wie oft.
Schön wäre, die Vorkommnisse auch angezeigt zu bekommen,
aber mit 30 Zeichen oder so links und rechts davon. Und
auch die Position innerhalb der Datei (sog. "Offset")!
Warum: Es gibt Dateien ohne Zeilenstruktur, und zwar nicht wenige.
Oft steht kein lesbarer Text drin, sondern codierte Daten.
Man nennt sie oft "Binärdateien", obwohl JEDE Datei aus Bytes
und jedes Byte aus Bits (= binary digits) besteht und somit
ALLES binär gespeichert ist.
Neben den Programmdateien (.EXE u.a.), von denen man besser
die Finger läßt, gibt es Bild- und Videodateien (.JPG. .AVI
u.a.), mit denen aus Sicht von allegro-Anwendungen wohl auch
nichts gemacht werden muß, aber es ist z.B. auch möglich,
daß eine XML- oder HTML-Datei keine Zeilentrenner enthält!
Das trifft auch auf MARC-Originaldateien zu. Und schließlich
sind die allegro-Dateien der Typen .ALD/.ALG nicht zeilenweise
strukturiert und enthalten neben Text- auch Steuerzeichen. Das
bequeme Einlesen einer Zeile mit "get iV" geht jedenfalls dann
nicht, weil die Zeilen-Steuerzeichen 13 10 fehlen.
Anm.: Das Durchsuchen und Verarbeiten von .ALD und .ALG-Dateien geht
natürlich am allerbequemsten mit der Volltextsuche, wenn man
Zeichenfolgen sucht, die in den Datenfeldern vorkommen.
Will man sich nicht auf Dateien unterhalb einer bestimmten Länge
beschränken, sondern prinzipiell in der Lage sein, beliebig lange
Dateien durchzusehen, dann hilft nur die Verarbeitung Byte für
Byte. Ist das nicht furchtbar langsam? Es geht.
Zwar ist auch das Einlesen von Blöcken fester Länge
möglich (z.B. fetch 1000), aber wenn die zu suchende Zeichenfolge
genau auf einer Blockgrenze liegt (z.B. auf Position 999 beginnt),
gibt es ein Problem...
Lösung:
Ein einzelnes Byte einlesen, das kann man auf zwei Arten machen:
fetch 1 holt ein Byte in die iV, wobei Steuercodes unterhalb
32 ersetzt werden durch ^ und einen nachfolgenden Buchstaben,
und zwar A für 1 usw., @ für 0, ^Z für 26
Mit if "1" ... prüft man, ob das Byte die Ziffer 1 ist
Mit if "^A" ... dagegen, ob es der Bytecode 01 ist.
fetch b holt das nächste Byte als Dezimalzahl; statt A also 65
und statt a die Zahl 97, statt Ziffer 1 die 49.
Mit if 49 ... prüft man, ob das Byte die Ziffer 1 ist
Mit if 1 ... dagegen, ob es der Bytecode 01 ist.
Beide Befehle eignen sich für den hier in Rede stehenden Zweck.
(Umcodiert wird hierbei übrigens nie!)
Nur der erste eignet sich, wenn man das gelesene Byte hernach mit
"write ^" wieder korrekt in eine andere Datei hinausschreiben will.
Der erste Trick besteht nun darin, Zeichen für Zeichen mit einem der
beiden Befehle zu lesen und jeweils mit dem ersten Zeichen der
gesuchten Folge zu vergleichen. Nur bei Gleichheit geht es dann
weiter mit dem Lesen des nächsten Zeichens und Vergleich mit dem
zweiten Zeichen der Folge usw., sonst braucht das zweite Zeichen
ja nicht mehr verglichen zu werden.
Der zweite Trick ist, bei einer Übereinstimmung des ersten Zeichens
dessen Position in der Datei mit fetch p zu bestimmen und zu sichern.
Bei Nichtübereinstimmung des zweiten, dritten ... Zeichens wird dann
zu der gesicherten Position zurückgekehrt (mit fetch m) und das
nächste Zeichen geholt. Nur so kann man, wenn z.B. nach der Folge
'121' gesucht wird, zwei Treffer ermitteln, wenn in der Datei die
Folge '12121' auftritt, d.h. die gesuchte Folge innerhalb ihrer
selbst neu beginnt.
Beispiel
========
Es soll festgestellt werden, ob und wie oft in der Datei abc.xyz
die Zeichenfolge '121' auftritt. (Leider muß man an mehreren Stellen
eingreifen, wenn es eine andere Folge sein soll, siehe ACHTUNG...
Enorm elegant ist diese Lösung also nicht, zugegeben.)
------------------------ MUSTER ------------------------
Die Datei öffnen
open abc.xyz
Protokolldatei öffnen
open x ergebnis.txt
Zähler für die Vorkommnisse
z=0
^^^^^^^^^^^^^^^^^ Beginn der Schleife
:GLOOP
naechstes Zeichen lesen, als dezimale Bytezahl
fet b
war denn noch eins da? Sonst Ende
if can jump GLEND
ein gelesenes Zeichen steht in der iV als Zahl
***************************************************
Hier ist der Platz zum Manipulieren!
Erstes Zeichen vergleichen: (Ziffer 1 = Byte 49)
if =49 jump MATCH // <- ACHTUNG: anpassen
***************************************************
Das erste Zeichen wurde noch nicht gefunden
jump GLOOP
erstes Zeichen gefunden, die weiteren einzeln vergleichen
:MATCH
Offset-Position hinter dem ersten Zeichen in $pos vermerken
fet p
ins $pos
jetzt einzeln lesen und vergleichen, bei Ungleichheit -> :NEXTP
fet b
ACHTUNG: hier ebenfalls anpassen für die weiteren Bytes
if not =50 jump NEXTP // Ziffer 2
fet b
if not =49 jump NEXTP // Ziffer 1
... hier evtl. noch weitere Bytes in dieser Weise behandeln
Treffer! Zähler erhöhen
z+1
Meldung in die Ergebnisdatei
wri "Pos. " $pos ": "
Umgebung 30 Zeichen links und rechts abgreifen
eval $pos -30
if <0 var "0"
Pos. 30 Byte nach links setzen
fet m
50 Zeichen holen
fet 60
ins $umg
und mit ausgeben
write "..." $umg "..." n
:NEXTP
Zur gemerkten Position zurück
var $pos
fet m
und dort weitermachen
jump GLOOP
:GLEND
^^^^^^^^^^ Ende der Schleife
Datei schliessen
close
Zähler ausgeben (ACHTUNG: Wert "121" anpassen)
wri n "121 wurde " z "mal gefunden"
close x
Ergebnisdatei zeigen
help ergebnis.txt
--------------------- MUSTER ENDE ------------------------
Hinweis:
Mitgeliefert wird ein FLEX bfile.flx, der angeblich eine Binärdatei
lesen und wieder schreiben kann. Er müßte jedoch modifiziert werden,
sonst kann er u.U. Zeichen verfälschen, was gerade bei einer
Binärdatei fatal sein kann. Der Fall wird aber eh und je kaum
jemals wirklich auftreten, daß man mal eine Binärdatei auf diese
Weise würde behandeln wollen! Das Problem ist: Mangels brauchbarer
Begrenzungszeichen kann man nur Blöcke fester Länge einlesen.
Soll etwa eine bestimmte Zeichenfolge durch eine andere ersetzt
werden, kann es passieren, daß eine Blockgrenze gerade mitten in
einer solchen Zeichenfolge auftritt - dann würde die Ersetzung
an dieser Stelle nicht funktionieren! Höchstens einzelne Zeichen
könnte man also ersetzen, jedoch kann jedes einzelne Zeichen an
verschiedenen Stellen verschiedene Funktionen haben. Das ist z.B.
bei den allegro-Datenbankdateien (Typ .ALD) so!
Also: Finger weg von Binärdateien, es sei denn, man kennt sich mit
dem betr. Typ ganz genau aus und kennt die Funktion jedes Bytes.
Dann wird man i.d.R. einen Hex-Editor haben, mit dem man evtl.
nötige Änderungen machen kann. Solche Editoren haben auch Hex-
Suchfunktionen, d.h. Trick 58 ist dann auch irrelevant.
Mehr Informationen über die Mailingliste Allegro