11.2. Ein- und Ausgabefunktionen für Dateien
Lesen und Schreiben einzelner Zeichen
Für diesen Zweck stehen die Bibliotheksfunktionen fgetc() und fputc() zur Verfügung. Die Funktion fgetc() erwartet als einziges Argument einen Pointer auf FILE und gibt das gelesene Zeichen als int zurück. Dies ist sinnvoll, da fgetc() EOF zurückgibt, sobald sie das Ende einer Datei erreicht. Auf manchen Systemen steht diese symbolische Konstante für den Wert -1. Aufgrund der Darstellung negativer Zahlen durch das sogenannte Zweier-Komplement (siehe Abschnitt 3.3.4) kann eine Variable vom Typ char diesen Wert nicht adäquat darstellen, und ein Lesevorgang würde deswegen am Ende einer Datei nicht Halt machen. Auch wenn auf Ihrem System EOF durch einen positiven Wert (< 256) repräsentiert wird, sollten Sie aus Gründen der Portierbarkeit den Rückgabewert von fgetc() an eine int-Variable zuweisen.
Die Funktion fputc() schreibt ein Zeichen in eine Datei. Dieses muß als erstes Argument übergeben werden; durch einen FILE-Pointer an der zweiten Argumentstelle wird fputc() über den Namen der Datei informiert. Diese Funktion liefert bei Erfolg das geschriebene Zeichen als Rückgabewert, bei Auftreten eines Fehlers EOF.
Allen Ein-/Ausgabefunktionen für Dateien ist gemeinsam, daß sie nach einem Lese- oder Schreibvorgang den Positionszeiger aktualisieren, d.h., ihn auf die Stelle richten, bis zu der sie zuletzt gelesen oder geschrieben haben. Alle nachfolgenden Lese-/Schreib-Operationen beginnen an der Stelle, auf die der Positionszeiger zuletzt gerichtet wurde. Dies ist sehr nützlich, wenn z.B. innerhalb einer Schleife mit fgetc() mehrere Zeichen aus einer Datei gelesen werden sollen. Dann wird nämlich nicht immer das gleiche Zeichen ausgelesen, sondern bei jedem Durchlauf das nächstfolgende. Das Beispiel filesize.c ermittelt mit Hilfe der Funktion fgetc() die Größe einer Datei:
/* filesize.c
* ermittelt mit Hilfe von fgetc()
* die Größe einer Datei
*/
#include <stdio.h> /* u.a. wg. fgetc() */
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
FILE *ChkSize;
unsigned long ulCount = 0;
/* Die symbolische Konstante FILENAME_MAX
* steht für die maximal zulässige Länge
* eines Dateinamens.
*/
char *pszTemp, szFileName[FILENAME_MAX];
if( argc == 1 ){
/* Wenn kein Dateiname als Kommandozeilen-
* Argument übergeben wurde, einen solchen
* interaktiv ermitteln.
*/
printf("Datei, deren Größe"\
ermittelt werden soll> ");
fgets( szFileName, FILENAME_MAX, stdin);
if( (pszTemp = strchr(szFileName, '\n')) != NULL )
*pszTemp = '\0';
}
else
strncpy(szFileName, argv[1], FILENAME_MAX);
/* Datei für Lesen im Binärmodus öffnen */
ChkSize = fopen( szFileName, "rb");
if( ChkSize == NULL ){
printf("Fehler beim Öffnen von %s\n", szFileName);
exit(EXIT_FAILURE);
}
/* Binärdateien enthalten in der Regel viele
* EOF-Zeichen (z.B. unter DOS ASCII 26). Die
* Anweisung while( fgetc(ChkSize) != EOF )
* würde beim ersten solchen Zeichen abbrechen.
* Die Funktion feof() hingegen gibt verläßlich
* Auskunft über das tatsächliche Dateiende.
*/
while(1){
(void) fgetc(ChkSize);
if( feof(ChkSize) != 0 )
break;
ulCount++;
}
printf("%lu Byte\n", ulCount);
fclose( ChkSize);
return 0;
}
Die schlichte Aufgabe, durch das Auslesen sämtlicher Zeichen die Größe einer Datei zu ermitteln, verlangt schließlich doch die Überwindung einiger Hürden. Hat die Variable argc den Wert 1 — es liegt also kein Kommandozeilen-Argument vor — dann wird der Benutzer um die Eingabe des Dateinamens gebeten. In beiden Fällen findet sich schließlich der Dateiname in der Variablen szFileName wieder, entweder durch Einlesen mit fgets() oder durch Kopieren mit strncpy(). Für die Größenangabe des Arrays szFileName verwenden wir die symbolische Konstante FILENAME_MAX, die in stdio.h definiert wird. Sie repräsentiert unter dem jeweiligen System den höchstmöglichen Wert für die Länge eines Dateinamens und ist der Verwendung von irgendwelchen Hausnummern jedenfalls vorzuziehen!
Da filesize die tatsächliche Anzahl an gespeicherten Byte ermitteln soll, sind Übersetzungen einzelner Zeichen nur störend und verfälschen das Ergebnis. Deshalb darf die Datei nicht im Textmodus, sondern muß im Binärmodus geöffnet werden. Dies wird durch die Zeichenkette "rb" im Aufruf von fopen() erreicht.
Ein weiteres Problem ergibt sich daraus, daß mit filesize auch Binärdateien untersucht werden sollen. Diese enthalten in der Regel mehrere EOF-Zeichen und könnten daher die while-Schleife vorzeitig beenden, falls wir dort den Rückgabewert von fgetc() überprüfen würden. Die Funktion feof() hingegen erkennt verläßlich das tatsächliche Dateiende. Ist dieses erreicht, dann gibt sie einen Wert ungleich 0 zurück.
Der ANSI-Standard definiert keine Funktion zur Ermittlung von Dateigrößen. In der Praxis würde man wohl nicht das Verfahren aus filesize.c wählen, sondern zu diesem Zweck eine entsprechende Funktion vorziehen, die zum Lieferumfang der meisten Compiler gehört. Auf UNIX-Systemen bietet sich dafür die Funktion stat() an; genauere Informationen darüber erhalten Sie unter UNIX in der zugehörigen man page mit dem Aufruf man stat.
Formatierte Ein- und Ausgabe
Wenn Sie bei diesem Stichwort an die Funktionen printf() und scanf() denken, dann liegen Sie damit auch beim Thema Datei-Manipulation richtig. Diese mächtigen und vielseitigen Funktionen stehen dafür in eigenen Versionen zur Verfügung: fprintf() und fscanf(). Sie unterscheiden sich von printf() bzw. scanf() nur dadurch, daß sie vor der Formatierungs-Zeichenkette ein weiteres Argument verlangen. Dabei handelt es sich erwartungsgemäß um einen Zeiger auf FILE, der durch einen Aufruf von fopen() zurückgegeben wurde.
Aufgrund der identischen Formatierungs-Möglichkeiten teilen sie alle Vorzüge und Nachteile ihrer Verwandten printf() bzw. scanf(). Der Einsatz von fscanf() ist daher weniger interessant, wenn die Länge und das Format der Eingabe-Daten nicht bekannt ist, da sie beim Auftreten sogenannter "Whitespace"-Zeichen das Einlesen in eine Variable abbricht. Sollen allerdings Daten von einer Datei eingelesen werden, die regelmäßig aufgebaut sind, dann kann scanf() gute Dienste leisten.
Tabellenkalkulationen und Datenbank-Systeme bieten im allgemeinen eine Exportfunktion an, bei der jeder Datensatz in einer eigenen Zeile steht und die einzelnen Felder durch einen Tabulator getrennt werden. Unser nächstes Beispiel demonstriert, wie fscanf() und fprintf() benutzt werden, um Daten aus einer solchen Datei einzulesen und in ein Format zu konvertieren, bei dem alle Feldwerte durch ein Semikolon separiert werden. Wir gehen davon aus, daß die Datenbasis in folgender Form vorliegt:
Mayer Hans 12.7.34 München
Kohl Friedrich 11.11.41 Frankfurt
Schuster Emil 2.6.68 Hamburg
/* conv2csv.c
* Konvertiert eine Datei, deren Feldwerte durch
* Tabulatoren getrennt sind, ins CSV-Format (comma
* separated values)
*/
#include <stdio.h>
#include <stdlib.h>
int main(void){
char szName[15], szVorName[15];
char szWohnOrt[15];
int iTag, iMonat, iJahr, iNoRecords=0;
FILE *DBasis, *CsvFile;
DBasis = fopen("db.dat", "r");
CsvFile= fopen("csv.dat", "w");
if( DBasis == NULL || CsvFile == NULL){
/* Mit fprintf() können Fehlermeldungen auf
* das Gerät "Standard Error" ausgegeben werden.
*/
fprintf( stderr, "Fehler beim Öffnen der Dateien\n");
exit(EXIT_FAILURE);
}
while( fscanf( DBasis, "%s%s%2d.%2d.%2d%s",
szName,
szVorName,
&iTag,
&iMonat,
&iJahr,
szWohnOrt) != EOF){
iNoRecords++;
/* Format für Tag und Monat wird immer 2stellig
* ausgegeben, bei Bedarf wird vorne eine 0 einge-
* fügt
* Ausgabe erfolgt in die Datei csv.dat */
fprintf(CsvFile, "%s;%s;%02d.%02d.%02d;%s\n",
szName,
szVorName,
iTag,
iMonat,
iJahr,
szWohnOrt);
}
printf("Zahl der gelesenen Datensätze: %d\n", iNoRecords);
/* Offene Dateien schließen */
fclose(DBasis);
fclose(CsvFile);
return 0;
}
Ein Konvertierungs-Programm, das wie unser Beispiel fscanf() verwendet, glänzt nicht gerade durch Flexibilität, weil der Formatierungs-String genau auf das Format der zu erwartenden Daten zugeschnitten sein muß. Wenn sich also das Format der Eingabe-Datei ändert, muß der Aufruf von fscanf() angepaßt werden. Andererseits ist die Verwendung von fscanf() sehr praktisch, wenn Sie ein Programm benötigen, das auf eine stets gleichbleibende Konvertier-Aufgabe zugeschnitten sein soll.
Bei fprintf() sei noch darauf hingewiesen, daß diese Funktion nicht nur vorzüglich geeignet ist, Daten verschiedensten Typs in eine Datei zu schreiben, sondern auch für die Ausgabe von Fehlermeldungen empfehlenswert ist. Die bisher verwendeten Funktionen printf() und puts() schreiben immer auf stdout; fprintf() hingegen läßt die Spezifizierung des Ausgabe-Streams zu. Die Angabe von stderr hat den Vorteil, daß Fehlermeldungen beispielsweise auch dann auf dem Bildschirm sichtbar werden, wenn stdout umgeleitet wurde.
Ein- und Ausgabe von Zeichenketten
Auch hier finden sich bereits bekannte Funktionen. Zum Einlesen von Zeichenketten steht die Funktion fgets() zur Verfügung, die wir bisher nur benutzten, um Eingaben von stdin entgegenzunehmen. Eine genauere Beschreibung ihrer Form und Verwendungsweise findet sich in Abschnitt 9.1.
Die Ausgabefunktion fputs() unterscheidet sich von puts() nur dadurch, daß ihr als zweites Argument der Name eines Streams übergeben werden muß (im Unterschied zu fscanf(), das diesen als erstes Argument verlangt!). Tritt ein Fehler auf, dann gibt fputs() den Wert EOF zurück.
Folgendes Beispiel benutzt fgets(), um Daten zeilenweise einzulesen. Jede Zeile soll dann nach einer bestimmten Zeichenkette durchsucht werden. Das Programm funktioniert nach dem Vorbild von find, das mit DOS und OS/2 ausgeliefert wird, weist aber gegenüber den Originalen einige Verbesserungen auf. Für den UNIX-Anwender hält sich der Gebrauchswert von find in Grenzen, da dort die diversen grep-Utilities nicht die Mängel des DOS-Programms aufweisen oder sich diese zumindest leicht z.B. durch ein alias beheben lassen.
Da der Anwender bei einer Suche meistens nicht zwischen Groß- und Kleinschreibung unterscheiden will, sollte auch kein eigener Schalter notwendig sein, um dieses Standard-Verhalten zu aktivieren. Während dafür also beim "originalen" find der Schalter /i (ignore case) bemüht werden muß, verlangt umgekehrt unser find den Schalter /s, wenn zwischen Groß- und Kleinschreibung unterschieden werden soll. Das find-Programm von OS/2 und älterer DOS-Versionen ist für die Batch-Verarbeitung unbrauchbar, weil es immer den Wert 0 an das System zurückgibt, egal ob es fündig geworden ist oder nicht. Beim vorliegenden Beispiel bestimmt der Ausgang der Suche den Rückgabewert. Unser find verzichtet vor allem darauf, den Dienst aufgrund "falscher DOS-Version" zu verweigern.
/* find.c */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#define TRUE 1u
#define FALSE 0u
/* Maximale Zeilenlänge */
#define MAXLINE 1000
/* Max. Länge des Suchbegriffs */
#define MAXPATTERN 127
/* Verwendung von typedef siehe Kapitel 13 */
typedef unsigned BOOL;
/* Funktions-Prototypen*/
BOOL search( const char *szText,
const char *szPattern );
void stri_cpy( char *pszDest,
const char *pszSrc);
void usage( void );
/* Globale Variable */
static BOOL bCaseSensitive = FALSE;
/* Flag für die Option /s (case-senstive) */
int main( int argc, char **argv ){
/* Buffer für Textzeile */
char szText[MAXLINE];
/* Buffer für Suchmuster */
char szPattern[MAXPATTERN];
/* Zeilenzähler */
unsigned long ulLineCount = 0;
/* Zähler für Zeilen, die Suchmuster enthalten */
unsigned long ulLinesFound = 0;
/* Wird ans System zurückgegeben */
BOOL bResult = FALSE;
/* für Returncode der Suchfunktion */
BOOL bFound = FALSE;
BOOL bPrintNumbers = FALSE; /* Flag für Option /n */
BOOL bReverse = FALSE; /* Flag für Option /v */
BOOL bPrintOnlySum = FALSE; /* Flag für Option /c */
FILE *SearchFile; /* File-Pointer für Such-Datei */
/* Kommandozeilen-Optionen ausfindig machen
* (/c /s /n /v /?). Schleife läuft so lange,
* bis keine Argumente mehr vorhanden sind
* oder Argument ohne führenden Schrägstrich
* erreicht ist.
*/
while( argc > 1 && argv[1][0] == '/'){
/* Options-Zeichen in Kleinschreibung */
switch( tolower( argv[1][1] ) )
{
case 'n':
bPrintNumbers = TRUE;
break;
case 'v':
bReverse = TRUE;
break;
case 'c':
bPrintOnlySum = TRUE;
break;
case 's':
bCaseSensitive = TRUE;
break;
case '?':
usage();
break;
default:
fprintf( stderr,
"Ungültige Option %s !\n", argv[1] );
usage();
}
/* Pointer auf Argumenten-Liste hochzählen */
argv++;
/* und den Argument-Zähler dekrementieren */
argc--;
}
if( argc > 1 )
/* 1. Argument in den Buffer für
* Suchmuster kopieren */
strcpy( szPattern, argv[1] );
else
/* Bei falschem Aufruf ab zur Belehrung */
usage();
/* Wenn Dateiname angegeben, ...*/
if( argc > 2 ){
/* ...Datei für "Lesen" öffnen */
SearchFile = fopen( argv[2], "r");
printf("---------- %s:\n", argv[2]);
}
else
/* sonst Eingabe von stdin */
SearchFile = stdin;
if( SearchFile == NULL ) {
fprintf( stderr,
"Fehler beim Öffnen von %s", argv[2] );
exit(2);
}
/* fgets gibt NULL zurück, wenn ein Fehler
* beim Lesen auftritt oder EOF erreicht wird
*/
while( fgets( szText, MAXLINE, SearchFile ) != NULL ){
/* bei jeder neuen Zeile Zähler erhöhen */
ulLineCount++;
bFound = search( szText, szPattern );
/* Falls die Option /v angegeben wurde (Suche
* nach Zeilen, die Suchmuster nicht enthalten),
* dann den Returncode der Fuktion search
* invertieren
*/
if( bReverse == TRUE )
bFound = !bFound;
/* Falls die Option /c angegeben wurde, bei
* jedem Treffer den Zähler erhöhen und das Flag
* bResult auf FALSE setzen (wird ans System
* zurückgegeben)
*/
if( bPrintOnlySum == TRUE && bFound == TRUE ){
ulLinesFound++;
bResult = TRUE;
}
/* Wenn die Option /c nicht gewählt wurde, bei
* jedem Treffer die entsprechende Zeile drucken
*/
if( bPrintOnlySum == FALSE && bFound == TRUE ){
if (bPrintNumbers == TRUE )
/* Ausgabe mit Zeilennummern */
printf( "%lu %s", ulLineCount, szText );
else
/* Ausgabe ohne Zeilennummern */
printf( "%s", szText );
bResult = TRUE;
}
}
/* Bei Option /c Zahl der Zeilen, die gesuchtes
* Muster enthalten, ausgeben
*/
if( bPrintOnlySum == TRUE )
printf("%lu\n", ulLinesFound );
/* Datei schließen */
fclose( SearchFile );
return( bResult );
}
/***************************************************
*
* BOOL search( const char *pszText,
* const char *pszPattern )
*
* Sucht unter Verwendung von strstr() in
* pszText nach der Zeichenkette pszPattern
*
**************************************************/
BOOL search( const char *pszText, const char *pszPattern ){
char szDupText[MAXLINE];
char szDupPattern[MAXPATTERN];
char *pszPos;
/* Falls Suche nicht case-sensitive */
if(bCaseSensitive == FALSE){
/* pszText und pszPattern umkopieren
* und auf Kleinbuchstaben setzen */
stri_cpy(szDupText, pszText);
stri_cpy(szDupPattern, pszPattern);
pszPos = strstr(szDupText, szDupPattern);
}
else
pszPos = strstr(pszText, pszPattern);
if(pszPos != NULL )
/* wenn Muster gefunden wurde, TRUE...*/
return TRUE;
else
/* sonst FALSE zurückgeben */
return FALSE;
}
/***************************************************
*
* void stri_cpy( char *pszDest, const char *pszSrc )
*
* kopiert Zeichenkette pszSrc nach pszDest
* und setzt pszDest gleich auf 'lower case'
*
***************************************************/
void stri_cpy( char *pszDest, const char *pszSrc ){
while( *pszSrc != '\0'){
if( *pszSrc >= 'A' && *pszSrc <= 'Z')
*pszDest = *pszSrc + ('a'-'A');
else if( *pszSrc == 'Ä')
*pszDest = 'ä';
else if( *pszSrc == 'Ö')
*pszDest = 'ö';
else if( *pszSrc == 'Ü')
*pszDest = 'ü';
else
*pszDest = *pszSrc;
pszSrc++;
pszDest++;
}
/* Kopierziel mit '\0' abschließen */
*pszDest = '\0';
}
/***************************************************
*
* void usage( void)
* Gibt bei falschem Aufruf Meldung über
* die richtige Verwendung von find aus
*
***************************************************/
void usage( void){
fprintf( stderr, "Aufruf: find [/s][/c][/n][/v]"
" \"Suchmuster\" [Datei]\n");
fprintf( stderr, "\t/v findet alle Zeilen, die das"
" Suchmuster NICHT enthalten\n");
fprintf( stderr, "\t/c Gibt die Anzahl der Zeilen"
" aus, in denen das Suchmuster vorkommt\n" );
fprintf( stderr, "\t/n versieht alle angezeigten"
" Zeilen mit Nummern\n");
fprintf( stderr, "\t/s unterscheidet zwischen"
" Groß- und Kleinbuchstaben\n");
fprintf( stderr, "\nRückgabewert:\n");
fprintf( stderr, "\t0: Suchmuster nicht gefunden\n");
fprintf( stderr, "\t1: Suchmuster mindestens 1x"
" gefunden\n");
fprintf( stderr, "\t2: Vorzeitiger Abbruch von find\n");
exit(2);
}
Da der Aufruf von find eine Reihe von optionalen Schaltern zuläßt, müssen die einzelnen Argumente daraufhin überprüft werden, ob sie mit einem / beginnen. Falls ein bestimmter Schalter angegeben wurde, dann wird innerhalb der switch-Anweisung ein entsprechendes Flag gesetzt. Dabei handelt es sich um eine Variable vom Typ BOOL, bei der nur die logischen Werte "wahr" oder "falsch
"
gesetzt bzw. abgefragt werden. Mit ihrer Hilfe kann im Verlauf des Programms leicht festgestellt werden, ob eine bestimmte Option berücksichtigt werden soll oder nicht.Da dem Anwender überlassen bleibt, ob find entweder in einer Datei oder in Daten aus (dem umgeleiteten) stdin suchen soll, muß nach der Berücksichtigung eventueller Schalter festgestellt werden, wieviele Kommandozeilen-Argumente noch verblieben sind. Gibt es davon noch zwei, dann kann es sich dabei nur um den Programmnamen und das Suchmuster handeln, und find muß vom Stream stdin lesen; deswegen wird dieser dem FILE-Pointer SearchFile zugeordnet. Bei drei Argumenten hingegen liegt beim letzten der Name der Datei vor, in der gesucht werden soll. Sie wird anschließend für "Lesen" geöffnet.
Daten werden von find so lange eingelesen, bis fgets() den Wert 0 zurückgibt. Das passiert dann, wenn ein Fehler auftritt oder EOF im Eingabestrom auftaucht. Schon daraus läßt sich ersehen, daß find nur für die Suche in Textdateien konzipiert und für Binärdateien kaum zu gebrauchen ist.
Für die Suche selbst verwendet die Funktion search() die Bibliotheks-Routine strstr(); diese erscheint als ausreichend, da im allgemeinen die Eingabe-Zeichenketten nur ein paar Dutzend Zeichen lang sind und eine raffiniertere Suchmethode wohl kaum Geschwindigkeitsvorteile bringen würde. Die Funktion strstr() unterscheidet bei ihrer Suche zwischen Groß- und Kleinbuchstaben, find soll dies aber per Voreinstellung nicht tun. Deshalb ruft search() bei nicht case-sensitiver Suche die Funktion stri_cpy() auf, die sowohl die Eingabe-Zeichenkette als auch das Suchmuster auf Kleinbuchstaben setzt. Der Kopiervorgang ist notwendig, da Zeichenketten, die das Suchmuster enthalten, in der ursprünglichen Schreibweise, d.h. unverändert ausgegeben werden sollen. Geringe Geschwindigkeitsverbesserungen wären wohl zu erreichen, wenn das Suchmuster nicht bei jedem Aufruf von search() auf szDupPattern umkopiert wird. Alternativ könnte daher das Suchmuster in einer globalen Variablen gespeichert und gleich in main() — vor dem Aufruf von fgets() — auf Kleinschreibung gesetzt werden.
Entgegen den bisherigen Gepflogenheiten ruft find die Funktion exit() nicht mit den symbolischen Konstanten EXIT_FAILURE oder EXIT_SUCCESS auf, sondern mit numerischen Werten. Dies soll sicherstellen, daß die Rückgabewerte von find unter verschiedenen Systemen immer gleich zu interpretieren sind.
Lesen und Schreiben von beliebig großen Datenblöcken
Während die bisher vorgestellten Funktionen einzelne bzw. Ketten von Zeichen bearbeiten konnten oder mit Hilfe der Formatierungs-Zeichenkette auf eine Anzahl von Daten bestimmten Typs zugriffen, können fread() und fwrite() mit jeder Anzahl von Daten beliebigen Typs - auch benutzerdefinierten (siehe dazu Kapitel 13) - umgehen. Beide Funktionen erwarten als Argumente einen Zeiger auf einen Speicherbereich, aus dem Daten gelesen bzw. in den Daten geschrieben werden sollen, die Größe des Datentyps, die Anzahl der Datenobjekte und einen FILE-Pointer. Die Prototypen von fread() und fwrite() sind in stdio.h folgendermaßen definiert:
/* size_t ist i.a. identisch mit unsigned */
size_t fread( void *buffer,
size_t size,
size_t count,
FILE *fp)
size_t fwrite( void *buffer,
size_t size,
size_t count,
FILE *fp)
Es sei beispielsweise das Array long lArray[8]={1,2,3,4,5,6,7,8} definiert. Um die Werte aller Elemente auf einen Schlag in eine Datei zu schreiben, können Sie fwrite() folgendermaßen aufrufen:
fwrite( lArray, sizeof(lArray), 1, DatFile);
Sowohl fread() als auch fwrite() liefern als Rückgabewert die Anzahl der erfolgreich gelesenen bzw. geschriebenen Datenobjekte (nicht Byte). Sobald ein Fehler auftritt, ist dieser Wert kleiner als count, da ja nicht alle Elemente gelesen bzw. geschrieben werden konnten.
Das nächste Beispiel implementiert unter Einsatz von fread() ein Textsuch-Programm. Im Gegensatz zu find kommt dieses auch mit Binärdateien zurecht und ist nicht zeilenorientiert. Mit Hilfe von malloc() wird ein möglichst großer Speicherblock angefordert, der dann mit Daten aus der angegebenen Datei "vollgesogen" wird. Beim Durchsuchen eines ca. 64 KByte großen Datenblocks lohnt sich der Einsatz eines effizienten Suchalgorithmus: Hier kommt jener von Knuth/Morris/Pratt zum Einsatz. Einzelheiten zu diesem Suchverfahren sollen hier nicht erläutert werden, in der einschlägigen Literatur finden sich aber ausführliche Erörterungen dazu (siehe z.B. Robert Sedgewick, Algorithmen in C, München 1992).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Funktions-Prototypen */
int KnuthMorrisPratt( char *pszPattern,
char *pszBuf,
unsigned uiBufSize);
void InitNext( int *iNext,
char *pszPattern,
unsigned uiPatternLen );
int main(int argc, char **argv){
char *pszIOBuffer = NULL;
FILE *SFile;
unsigned uiBufSize = 65535;
static unsigned uiOffset, uiSum = 0;
long lFound = 0;
char cTemp;
/* Belehrung bei falschem Aufruf */
if(argc != 3){
puts("Aufruf: tsearch Datei Suchmuster");
exit(EXIT_FAILURE);
}
SFile = fopen(argv[1], "r");
if( SFile == NULL){
fprintf(stderr,
"Fehler beim Öffnen von %s\n",
argv[1]);
exit(EXIT_FAILURE);
}
/* uiBufSize wurde mit 65535 initialisiert.
* Wenn malloc() nicht soviel Speicher zur
* Verfügung stellen kann, dann uiBufSize
* bei jedem Durchlauf um 1000 reduzieren und
* so lange malloc() aufrufen, bis es erfolgreich
* ist. Mit einem Puffer < 1000 Byte wollen wir
* uns aber nicht zufriedengeben. Je größer der
* Puffer, umso weniger Plattenzugriffe sind nötig!
*/
while( uiBufSize >= 1000 ){
pszIOBuffer = (char *) malloc( uiBufSize *
sizeof(char));
if( pszIOBuffer != NULL)
break;
uiBufSize -= 1000;
}
if(pszIOBuffer == NULL){
puts("Kein Speicher mehr frei!");
exit(EXIT_FAILURE);
}
printf("Größe des Lesepuffers: %u Byte\n",
uiBufSize);
while( feof(SFile) == 0 ){
uiBufSize= fread( pszIOBuffer,
sizeof(char),
uiBufSize,
SFile);
/* Puffer mit '\0' abschließen */
pszIOBuffer[uiBufSize] = '\0';
uiOffset = 0;
/* Die Funktion KnuthMorrisPratt() gibt -1
* zurück, wenn sie den Suchbegriff nicht
* findet, ansonsten die Position des Such-
* begriffs im Puffer. Dieser Wert wird in
* lFound gespeichert. Bei jedem neuen Aufruf
* soll KnuthMorrisPratt() dort weitersuchen,
* wo sie zuletzt fündig geworden ist, also an
* pszIOBuffer + uiOffset. uiOffset muß bei
* jedem neuen Puffer, den fread() von der Datei
* einliest, auf 0 gesetzt werden, damit dieser
* von Anfang an durchsucht wird.
*/
while( (lFound =
KnuthMorrisPratt(
argv[2],
pszIOBuffer + uiOffset,
uiBufSize —
uiOffset)) != -1){
uiOffset += (unsigned) lFound;
/* Zähler für die Anzahl der Treffer */
uiSum++;
/* Ab der Fundstelle sollen 100 Zeichen
* ausgegeben werden. printf() braucht bei
* der Ausgabe von Zeichenketten eine ab-
* schließende '\0'. Deshalb '\0' an die Posi-
* tion 100 nach der Fundstelle schreiben
*/
cTemp = pszIOBuffer[uiOffset + 100];
pszIOBuffer[uiOffset + 100] = '\0';
printf("\n===>%s\n",
pszIOBuffer + uiOffset —
strlen(argv[2]));
pszIOBuffer[uiOffset + 100] = cTemp;
}
}
printf(" %u Fundstellen von \"%s\" in %s\n",
uiSum, argv[2], argv[1]);
fclose( SFile );
free( pszIOBuffer );
return 0;
}
/***********************************************
*
* Suchfunktion
*
***********************************************/
int KnuthMorrisPratt( char *pszPattern,
char *pszBuf,
unsigned uiBufSize){
int iCount, iCount1;
unsigned int uiPatternLen = strlen( pszPattern );
int iNext[128];
InitNext( iNext, pszPattern, uiPatternLen );
for( iCount = 0, iCount1 = 0;
iCount1 < uiPatternLen && iCount < uiBufSize;
iCount++, iCount1++){
while( (iCount1 >= 0) &&
( pszBuf[iCount] != pszPattern[iCount1]))
iCount1 = iNext[iCount1];
}
if( iCount1 == uiPatternLen )
return iCount;
else
return -1;
}
/***********************************************
*
* Hilfsfunktion für KnuthMorrisPratt()
*
***********************************************/
void InitNext( int *iNext,
char *pszPattern,
unsigned int uiPatternLen ){
int iCount, iCount1;
iNext[0]= -1;
for(iCount = 0, iCount1 = -1;
iCount < uiPatternLen;
iCount++, iCount1++){
iNext[iCount] = iCount1;
while((iCount1 >= 0) &&
(pszPattern[iCount] != pszPattern[iCount1]))
iCount1 = iNext[iCount1];
}
}
Die Kombination aus großem Lesepuffer und schnellem Suchalgorithmus erweist sich als ziemlich effizient: Die DOS-Version muß sich z.B. vor der Konkurrenz durch Norton’s Text Search nicht verstecken.