![]() |
Programmieren in Cvon Prof. Jürgen Plate |
Eine Datei besitzt also lauter Komponenten gleichen Typs. Zusammen mit der Dateivariable wird implizit auch ein Pufferbereich im Arbeitsspeicher definiert, der mindestens eine Dateikomponente (--> aktueller Wert) aufnehmen kann. Auf diesen Datenwert wird dann über die Dateivariable zugegriffen. Zusammen mit dem Typ "Datei" müssen einige Standardoperationen definiert sein, die den Zugriff auf die Datei erlauben:
| Programmname < Dateiname |
| Dieses Kommando bewirkt, daß das Programm von der angegebenen Datei liest. |
| Programmname > Dateiname |
| Dieses Kommando bewirkt, daß das Programm auf die angegebenen Datei ausgibt. |
Die sogenannten Standard-Filepointer sind immer initialisiert, die zugehörigen Dateien immer geöffnet:
| stdin | Standardeingabe | (Tastatur) |
| stdout | Standardausgabe | (Bildschirm) |
| stderr | Fehlerausgabe | (Bildschirm!) |
Die Unterscheidung zwischen stdout und stderr ist beispielsweise dann relevant, wenn man die Bildschirmausgabe (stdout) in irgendwelche Files umleitet (z. B. mit '>' unter DOS und UNIX), aber verhindert werden soll, daß auch etwaige Fehlermeldungen dorthin "verschwinden".
Ein einfaches Beispiel für Zugriff auf die Standarddateien stdin und stdout:
/* Programm 'cat' */
#include <stdio.h>
int main(void)
{
int ch;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
Solange Eingabedaten von der Standardeingabe kommen werden sie auf der Standardausgabe wieder ausgegeben. Das Programm "echot" also die Standardeingabe. Mit der Ausgabeumleitung kann man damit Tastatureingaben in eine Datei schreiben, z. B. durch den Programmaufruf: cat > mein.text. Mittels Eingabeumleitung kann man Dateiinhalte auf den Bildschirm bringen, z. B.: cat < mein.text.
Eine Unschönheit von C kann man an diesem Programm gut erkennen. Die Variable ch ist nicht - wie man es erwarten sollte - als char definiert, sondern als int. Das liegt einzig und alleine daran, daß die vordefinierte Konstante EOF den Wert -1 besitzt und daher bei der Verwendung einer char-Variablen das Datei- bzw. Eingabeende nicht erkannt würde.
Das Eingabeende unter UNIX wird durch das Zeichen Ctrl-D (bzw. auf der deutschen Tastatur Strg-D) repräsentiert. Unter DOS/Windows ist es das Zeichen Ctrl-Z (bzw. auf der deutschen Tastatur Strg-Z).
Beispiel: Kopieren der Standardeingabe zur Standardausgabe; Zählen der Byteanzahl.
#include <stdio.h>
int copy(void);
void main()
{
int bytes;
int blocks;
bytes = copy();
fprintf (stderr, "%d Bytes kopiert.\n", bytes);
}
int copy(void)
{
int c;
int bytes;
bytes = 0;
while ((c = getchar()) != EOF)
{
putchar(c);
bytes = bytes + 1;
}
return (bytes);
}
Beispiel: Kopieren der Standardeingabe zur Standardausgabe; Numerieren aller Zeilen.
#include <stdio.h>
#include <stdio.h>
#define YES 1
#define NO 0
main()
{
short nl_anz;
int c, zeile_nr;
zeile_nr = 1;
nl_anz = YES;
while ((c = getchar()) != EOF)
{
if(nl_anz)
{
printf("%6d ", zeile_nr);
zeile_nr = zeile_nr + 1;
}
if(c != '\n')
nl_anz = NO;
else
nl_anz = YES;
putchar(c);
}
}
Beispiel: Worte, Zeichen und Zeilen zählen.
#include <stdio.h>
#define IMWORT 1
#define AUSSEN 0
main()
{
int c,wo;
int nc, nw, nl; /* Zeichen, Worte, Zeilen */
wo = AUSSEN; /* Anfangswerte */
nc = nw = nl = 0;
while((c = getchar())!= EOF)
{
++nc;
if(c == '\n') ++nl;
if( (c == '\n') || (c == '\t') || (c == ' ') ) wo = AUSSEN;
else
if(wo == AUSSEN)
{
wo=IMWORT;
++nw;
}
}
printf("\nDas waren %d Zeichen, %d Worte und %d Zeilen.\n",nc,nw,nl);
}
Beispiel: Wortlängen im Eingabestrom ermitteln
Es wird die länge eines jeden Wortes bestimmt und die Längen zwischen
1 und 25 gezählt. Alle Wörter, die länger als 25 Zeichen sind,
werden in einem 26. Zähler registriert. Als Ausgabe erhält man die
Anzahlen der der Worte:
#include <stdio.h>
#define MAXWORDLEN 26 /* maximale Wort-Laenge + 1*/
int main(void)
{
int c; /* gelesenes Zeichen */
int wl,zl; /* Wortlaenge, Zeilenlaenge */
int i; /* Schleifenzaehler */
int words,lines; /* Wortzaehler, Zeilenzaehler */
int maxline,maxword; /* Maximallaengen */
int wordlen[MAXWORDLEN]; /* speichert die Wortlaengen */
zl = words = maxline = maxword = lines = 0;
for ( i=0; i < MAXWORDLEN; i++ )
wordlen[i] = 0;
c = getchar(); /* 1. Zeichen lesen */
while (c != EOF)
{
while (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))
&& (c != '\n') && (c != EOF))
{ /* Wortanfang suchen */
c = getchar(); /* Zeichen lesen */
zl++; /* Zeilenlaenge erhoehen */
}
wl = 0; /* Wortlaenge auf 0 setzen */
while ((c != EOF)
&& ((('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))
|| ('0' <= c && c <= '9') || (c == '_')))
{ /* naechstes Wort lesen */
c = getchar(); /* Zeichen lesen */
zl++; /* Zeilenlaenge erhoehen */
wl++; /* Wortlaenge erhoehen */
}
if (wl > 0) words++; /* Wortzaehler erhoehen */
if (wl > maxword ) maxword = wl; /* max. Wortlaenge */
if (wl >= MAXWORDLEN) /* groesser als MAXWORDLEN? */
wordlen[MAXWORDLEN-1]++; /* ja - ins letzte Element */
else
wordlen[wl-1]++; /* passenden Zaehler erhoehen */
if ((c == '\n') || (c == EOF)) /* neue Zeile? */
{
lines++; /* Zeilenzaehler erhoehen */
if (zl > maxline)
maxline = zl; /* max. Zeilenlaenge */
zl = 0;
c = getchar();
}
}
for ( i=0; i < MAXWORDLEN; i++ ) /* Maximalzahl der Worte */
printf("%10d | %10d\n", i, wordlen[i]);
return 0;
}
Im folgenden Beispiel wird eine Datei sequentiell beschrieben und wieder gelesen. An diesem Beispiel werden einige grundlegende Befehle erklärt, weshalb der Quelltext mit Zeilennummern in eckugen Klammern versehen wurde. Die Zeilennummern finden Sie in den Erklärungen unten wieder:
[ 1] #include <stdio.h>
[ 2]
[ 3] int main(void)
[ 4] {
[ 5] FILE *fp;
[ 6] int i,xi;
[ 7] static char dateiname[]="daten.datei";
[ 8] char text[80];
[ 9]
[10] /* Beschreiben der Datei */
[11] fp = fopen(dateiname,"w");
[12] if ( fp == NULL)
[13] {
[14] fprintf(stderr,"Datei %s kann nicht zum Schreiben\
[15] geoeffnet werden\n",dateiname);
[16] exit(1);
[17] }
[18] for ( i=0; i<10; i=i+2)
[19] fprintf(fp,"%d\n",i);
[20] fclose(fp);
[21]
[22] /* Lesen der Datei */
[23] fp = fopen("meine.dat","r");
[24] if (fp == NULL)
[25] {
[26] fprintf(stderr,"Datei %s kann nicht zum Lesen\
[27] geoeffnet werden\n",dateiname);
[28] exit(2);
[29] }
[30] while(feof(fp) == 0)
[31] {
[32] fscanf(fp,"%d",&xi);
[33] printf("%d",xi);
[34] }
[35] fclose(fp);
[36] exit(0);
[37] }
Im Programm finden wir folgende Dateianweisungen:
FILE *fopen(const char *filename, const char *modus)
als Zugriffsmodus steht zur Verfügung eine Kombination von "a", "r", "w" und "+":
Die Funktion fopen reagiert folgendermaßen:
int fprintf(FILE*, const char *format, ...)
int fscanf(FILE*, const char *format, ...)
Im 2. Programmbeispiel werden von der Datei "daten.dat" Datenzeilen gelesen. Jede Zeile enthält einen Integerwert, einen Double-Wert und einen Textstring. Diese Daten werden dann mit einer vorangestellten Zeilennummer in die Datei "tabelle.txt" geschrieben.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char msg[100];
int n, i;
double d;
FILE *fpin, *fpout;
/* Dateien oeffnen */
fpin = fopen("daten.dat", "rt");
fpout = fopen("tabelle.txt", "wt");
if((fpin == NULL) || (fpout == NULL))
{ /* ... hat nicht funktioniert */
fprintf(stderr, "Fehler: I/O");
exit(1);
}
/* jetzt neue Datei mit anlegen, mit den Wertesaetzen zeilenweise
und mit Zeilennummern versehen */
i = 1;
for(;;) /* Endlosschleife */
{
fscanf(fpin, "%d%lf%s", &n, &d, msg);
if(feof(fpin)) break; /* Dateiende gelesen, sofort aufhoeren! */
fprintf(fpout, "%d> %s %d %f\n", i++, msg, n, d);
}
/* Dateien schliessen */
fclose(fpout);
fclose(fpin);
return 0;
}
Ein weiteres Beispiel: Programm zum Kopieren einer Datei. Das Programm hat zwei Kommandozeilen-Parameter, die Namen von Quell- und Zieldatei. In diesem Beispiel wird besonders auf die Fehlerbehandlung eingegangen. Wenn etwas schief geht, interessiert uns die Ursache, die bei vielen Funktionen als Nummer in errno und als englischer Text in strerror(errno) hinterlegt sind. Deshalb wird die Headerdatei errno.h eingebunden.
Sämtliche Funktionen, deren Ergebniswerte nicht mehr interessieren, sind mit einem Cast nach void versehen:
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
FILE *infile,*outfile;
int c,i;
if(argc != 3)
{
(void)fprintf(stderr,
"Aufruf: %s quelldatei zieldatei\n", argv[0]);
return(EXIT_FAILURE);
}
infile = fopen(argv[1],"rb");
if(infile == NULL)
{
(void)fprintf(stderr,
"Fehler beim öffnen der Datei %s: %s\n",
argv[1],strerror(errno));
return(EXIT_FAILURE);
}
outfile = fopen(argv[2],"wb");
if(outfile == NULL)
{
(void)fprintf(stderr,
"Fehler beim Erzeugen der Datei %s: %s\n",
argv[2], strerror(errno));
(void)fclose(infile);
return(EXIT_FAILURE);
}
while((c = getc(infile)) != EOF)
if(putc(c,outfile) == EOF)
{
(void)fprintf(stderr,
"Fehler beim Schreiben der Datei %s: %s\n",
argv[2], strerror(errno));
(void)fclose(infile);
(void)fclose(outfile);
return(EXIT_FAILURE);
}
if(ferror(infile))
{
(void)fprintf(stderr,
"Fehler beim Lesen der Datei %s: %s\n",
argv[1], strerror(errno));
(void)fclose(infile);
(void)fclose(outfile);
return(EXIT_FAILURE);
}
if(fclose(infile) == EOF)
{
(void)fprintf(stderr,
"Fehler beim Schließen der Datei %s\n", argv[1]);
(void)fclose(outfile);
return(EXIT_FAILURE);
}
if(fclose(outfile) == EOF)
{
(void)fprintf(stderr,
"Fehler beim Schließen der Datei %s\n", argv[2]);
return(EXIT_FAILURE);
}
return(EXIT_SUCCESS);
}
Auch die Eingabe wird wie die Ausgabe gepuffert. Das heißt, es wird beim Aufruf von getchar() gewartet, bis nach dem Zeichen die Enter-Taste gedrückt wird. Um das zu vermeiden (wenn z. B. nur ein einziger Tastendruck erfolgen soll), muß das System in den ungepufferten Betrieb geschaltet werden. Sie erreichen dies unter UNIX durch Umschalten des Terminals in den ungepufferten (raw-)Modus. Das kann durch den Aufruf des Systemkommandos stty erreicht werden:
system ("stty raw");
Will man zusätzlich verhindern, daß das eingegebene Zeichen vom Betriebssystem geechot wird, kann man den Aufruf erweitern:
system ("stty raw -echo");
Danach wird das eingegebene Zeichen auch nicht mehr automatisch auf dem Bildschirm geechot, sondern erst durch die Ausgabe in Ihrem Programm. Auf diese Weise lassen sich beispielsweise auch Passworteingaben realisieren oder es kann verhindert werden, daß die Eingabe eine Bildschirmmaske verunstaltet. Der Funktionsaufruf fflush(stdout); nach jeder Ausgabe kann dann auch entfallen. Bevor das Programm beendet wird, müssen Sie jedoch wieder in den "Normalmodus" zurückschalten:
system ("stty -raw");
bzw.
system ("stty -raw echo");
Beispiel: Das folgende Programm macht in Textdateien solche Zeichen
sichtbar, die normalerweise nicht nicht sichtbar sind, nälich Steuerzeichen
mit den ASCII-Codes 0 bis 31. Diese Zeichen werden Hexadezimal ausgegeben und
als Markierung ein '\' davor, so würde das Formfeed-Zeichen als '\0C' ausgegeben.
Die Zeichen "Tabulator", "Newline" und das Leerzeichen bleiben unverändert,
die Zeilenstruktur der Datei bleibt also erhalten.
Auf der Kommandozeile können beliebig viele Dateinamen angegeben werden.
Bleibt die Kommandozeile leer, wird automatisch von der Standardeingabe gelesen.
#include <stdio.h>
#include <stdlib.h>
void vis(FILE *fp);
main(int argc, char *argv[])
{
int i; /* Zaehler fuer Parameter */
FILE *fp; /* Eingabedatei */
if (argc == 1) /* keine Datei angegeben */
vis(stdin);
else /* Dateiliste abarbeiten */
{
for (i = 1; i < argc; i++)
if ((fp=fopen(argv[i], "r")) == NULL)
{
fprintf(stderr, "Can't open %s\n", argv[1]);
exit(1);
}
else
{
vis(fp);
fclose(fp);
}
}
exit(0);
}
void vis(FILE *fp)
{
int c;
while ((c = getc(fp)) != EOF)
if (isascii(c) &&
(isprint(c) || c=='\n' || c=='\t' || c==' '))
putchar(c);
else
printf("\\%02x", c);
exit(0);
}
Als nächstes schreiben wir ein Programm, das den Mittelwert aus den eingelesenen Zahlen bildet:
#include <stdio.h>
#include <stdlib.h>
main(int argc, char *argv[])
{
double x; /* Eingabe-Werte */
int r; /* Rueckgabewert von fscanf */
int n; /* Anzahl der Werte */
double sx; /* in sx werden die eingelesenen Werte aufsummiert */
double mx; /* Mittelwert der x-Werte */
int i; /* Zaehler fuer Parameter */
FILE *fp; /* Eingabedatei */
if (argc == 1) /* keine Datei angegeben */
{
fprintf(stderr, "Keine Datei angegeben!\n");
exit(1);
}
else /* Dateiliste abarbeiten */
{
for (i = 1; i < argc; i++)
if ((fp=fopen(argv[i], "r")) == NULL)
{
fprintf(stderr, "Can't open %s\n", argv[i]);
exit(1);
}
else /* Datei bearbeiten */
{
n = 0; /* Zaehler und Summe auf Null setzen */
sx = 0;
for(;;)
{
r = fscanf (stdin,"%F",&x); /* Wert einlesen */
if (r == EOF) break; /* Dateiende -- fertig */
n = n + 1;
sx = sx + x;
}
mx = sx/n;
fprintf(stdout,"Datei: %s\n", argv[i]);
fprintf(stdout," Anzahl der verarbeiteten Werte: %d\n",n);
fprintf(stdout," Mittelwert: %f\n",mx);
fclose(fp);
}
}
exit(0);
}
Aufgabe: Erweitern Sie das Mittelwert-Programm so, daß gleichzeitig das Minimum und das Maximum der eingelesenen Zahlen ermittelt und ausgegeben wird.
Oft braucht man ein Programm, daß verschiedene Dateien nach einer Zeichenfolge durchsucht. In UNIX gibt es dazu das recht mächtige Tool grep und seine "Verwandten" egrep und fgrep. Hier soll eine einfache Variante vorgestellt werden, die mgrep heiße. Das Programm kann diverse Optionen verarbeiten:
mgrep <Optionen> <Suchbegriff> <Dateiname(n)>
wobei die Optionen auch fehlen dürfen, es werden dann Voreinstellungen verwendet. Der Teil des Programms, der die Kommandozeile auswertet, ist etwa genau so lang, wie der eigentliche Suchteil.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define MAXLINE 1024 /* max. Zeilenlänge */
#define TRUE 1
#define FALSE 0
char upper = FALSE; /* Option i */
char count = FALSE; /* Option c */
char fnonly = FALSE; /* Option l */
char linen = FALSE; /* Option n */
char header = TRUE; /* Option h */
char nomatch = FALSE; /* Option v */
void usage(void)
{
printf("Aufruf: mgrep [<Optionen>] <Suchmuster> <Datei(en)> \n\n");
printf("Optionen: i Groß-/Kleinschreibung nicht beachten\n");
printf(" l nur Dateinamen ausgeben\n");
printf(" c Zeilen zählen\n");
printf(" h kein Dateinamen/Zeilennummer ausgeben\n");
printf(" n Zeilennummer ausgeben\n");
printf(" v alle Zeilen ausgeben, die nicht übereinstimmen\n\n");
exit(1);
}
void delete(char *str,int pos, int n)
/* n Zeichen aus str ab pos löschen */
{
int i = pos + n;
while ((str[pos++] = str[i++]) != '\0');
}
void del_spaces(char *line)
/* Führende Leerzeichen (Space und Tab) entfernen */
{
while (line[0]==' ' || line[0]==0x09) delete(line, 0, 1);
}
FILE *open_file(char *name)
/* Datei öffnen */
{
FILE *fp;
if ((fp=fopen(name,"r")) == 0)
{
fprintf(stderr,"Datei %s kann nicht geöffnet werden",name);
exit(1);
}
return fp;
}
char strstrc(char *str1, char *str2)
/* Feststellen, ob str2 in str1 enthalten ist */
/* In Abhängigkeit von upper Groß-/Kleinschreibung */
/* beachten bzw. nicht beachten */
{
if (upper)
{
int i, n, len;
n = strlen(str2);
len = strlen(str1) - n;
for (i = 0; i <= len; i++)
if (strnicmp(&str1[i], str2, n) == NULL) return TRUE;
}
return strstr(str1, str2) != NULL;
}
int main(int argc, char *argv[])
{
char line[MAXLINE], search[MAXLINE], name[120], match;
int i, lineno, cnt = 0;
FILE *fp;
if (argc < 3) usage(); /* falsche Parameteranzahl */
/* zuerst die Optionen bearbeiten */
while (argc > 2 && argv[1][0] == '-')
{
printf("Argument: %s\n",argv[1]);
switch (argv[1][1])
{
case 'i':
case 'I': upper = TRUE; break;
case 'n':
case 'N': linen = TRUE; break;
case 'c':
case 'C': count = TRUE; break;
case 'l':
case 'L': fnonly = TRUE; break;
case 'h':
case 'H': header = FALSE; break;
case 'v':
case 'V': nomatch = TRUE; break;
default: usage(); exit(1);
}
argc--;
argv++;
}
/* jetzt sollte noch Suchbegriff und Dateiname da sein */
if (argc < 2)
{
usage();
exit(1);
}
/* suchbegriff uebernehmen */
strcpy(search, argv[1]);
argc--;
argv++;
/* Text in allen Dateien suchen */
for (i = 1; i < argc; i++)
{
lineno=0;
strcpy(name, argv[i]);
printf("Durchsuchen: %s\n",name);
fp = open_file(name);
while (fgets(line, MAXLINE, fp) != NULL)
{
if (((match = strstrc(line, search)) == TRUE
&& !nomatch) || (!match && nomatch))
{
cnt++;
if (!count)
{
if (header)
{
printf("%s ", name);
if (fnonly)
{
putchar('\n');
break;
}
if (linen) printf("%04d", lineno);
putchar(':');
}
del_spaces(line);
printf("%s", line);
}
}
lineno++;
}
fclose(fp);
}
if (count) printf("%d Zeilen gefunden",cnt);
return 0;
}
Wenn ASCII-Zeichen auf eine Zeichenkette eingelesen werden, muß gegebenenfalls eine Umwandlung in eine Zahl erfolgen. Die folgende Funktion atof() leistet das Gewünschte:
double atof(char s[])
/* Zeichenkette s nach double wandeln */
{
double val, power;
int i, sign;
for (i = 0; s[i] == ' ' || s[i] == '\n' || s[i] == '\t'; i++)
; /* Zwischenraum uebergehen */
sign = 1;
if (s[i] == '+' || s[i] == '-') /* Vorzeichen */
if (s[i++] == '-') sign = -1;
for (val = 0; s[i] >= '0' && s[i] <= '9'; i++)
val = 10 * val + (s[i] - (int)'0');
if s[i] == '.') i++;
for (power = 1, s[i] >= '0' && s[i] <= '9'; i++)
{
val = 10*val + (s[i] - (int)'0');
power = power*10;
}
return (sign*val/power);
}
Die Funktion fwrite schreibt eine angegebene Anzahl von Datenelementen gleicher Größe in eine Datei. übergeben werden muss:
size_t fwrite(const void *pt, size_t size, size_t n, FILE *f)
Die auszugebenden Daten müssen zusammenhängend im Speicher stehen. Dies ist bei Vektoren stets der Fall, ebenso wie bei Speicherplatz, der durch einen einzelnen malloc()-Aufruf (siehe Zeiger) zur Verfügung gestellt wurde.
Beispielprogramm für binäres Schreiben:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int Feld[] = {3, 91, 2134, 6, 33, 267, 9123, -5, 22, 0};
FILE *pf;
char dateiname[] = "daten.bin"; /* Dateiname */
pf = fopen(dateiname, "wb");
if (pf == NULL)
{
printf("Fehler beim Oeffnen der Datei\n");
exit(1);
}
fwrite( (void *)Feld, sizeof(int), 10, pf);
/* Das Feld wird komplett auf einmal geschrieben */
fclose(pf);
}
Die Größe der Datei daten.bin ist also 10 * sizeof(int).
Die Funktion fread ist die zu fwrite gehörige analoge Einlesefunktion. Ihr Rückgabewert ist die Anzahl der tatsächlich gelesenen Bytes. Diese Zahl kann kleiner sein, als die Zahl der zu lesenden Bytes, wenn das Dateiende vorzeitig erreicht wurde. SIe ist definiert als:
int fread(void *ptr, size_t size, size_t n, FILE *f)
Das folgende Programm kopiert eine Datei unter Verwendung der beiden Funktionen fread() und fwrite(). Dabei wird ein char-Array als Puffer verwendet. Diese Kopiermethode ist wesentlich schneller als zeichenweises Kopieren. Das Dateiende wird dadurch erkannt, daß fread() den Wert 0 zurückliefert. fwrite() schreibt genau soviele Bytes, wie von fread() gelesen wurden.
#include <stdio.h>
#include <stdlib.h>
#define MAXBUF 32768 /* Puffergroesse */
int main(void)
{
char buffer[MAXBUF]; /* Kopierpuffer */
FILE *rf, *wf; /* Lesedatei, Schreibdatei */
int numread; /* Anzahl gelesener Zeichen */
char readfrom[] = "daten.bin"; /* Dateinamen */
char writeto[] = "daten.bak";
rf = fopen(readfrom, "rb");
if (rf == NULL)
{
printf("Fehler beim Oeffnen der Datei\n");
exit(1);
}
wf = fopen(writeto, "wb");
if (wf == NULL)
{
printf("Fehler beim Oeffnen der Datei\n");
exit(1);
}
do
{
numread = fread(buffer, sizeof(char), MAXBUF, rf);
fwrite( buffer, sizeof(char), numread, wf);
} while (numread > 0);
fclose(wf);
fclose(rf);
return 0;
}
Beispiel: Zum Speichern von Daten für das Telefonverzeichnis des Fachbereichs wird die
folgende Struktur definiert:
struct Adresse
{
char Name[30];
char Vorname[30];
int Raumnummer;
int Telefon;
};
Gegeben ist eine Binärdatei namens "teldat", die Datensätze vom Struktur-Typ
"Adresse" enthält. Gesucht wird eine Funktion tabelle, welche die Binärdatei
zum Lesen öffnet, und alle Datensätze nacheinander als formatierte Tabelle ausdruckt:
int tabelle(char dateiname[])
{
FILE * fp;
struct Adresse Data;
fp = fopen(dateiname,"rb");
if (fp == NULL) return(1); /* Fehler */
printf("+------------------------------------------+------+------+\n");
printf("| Name | Raum | Tel. |\n");
printf("+------------------------------------------+------+------+\n");
while (!feof(fp))
{
fread(&Data,sizeof(Data),1,fp);
printf("| %-10s %-30s | %4d | %4d |\n",
Data.Vorname, Data.Name, Data.Raumnummer, Data.Telefon);
}
printf("+------------------------------------------+------+------+\n");
fclose(fp);
return(0);
}
Beachten Sie, daß im Gegensatz zum Feld oben, nun der Adressoperator '&'
bei fread verwendet werden muß.
Die Daten der Datei "teldat" sollen in eine zweite Binärdatei umkopiert werden. Dafür wird die Funktion kopiere verwendet, welche die Binärdatei mit den Adressen (also die Quelldatei) zum Lesen und gleichzeitig eine weitere Binärdatei, die Zieldatei, zum Schreiben öffnet. Die zweite Binärdatei soll Datensätze enthalten, die folgender Struktur genügen:
struct NeueAdresse
{
char Name[30];
int Telefon;
};
Aufgabe der Funktion ist es, jeweils einen Datensatz aus der Quelldatei zu lesen,
die Inhalte in einen Datensatz der Zieldatei zu übertragen und diesen dann in
die Zieldatei zu schreiben:
int kopiere(char quelldateiname[], char zieldateiname[])
{
FILE * qfp;
FILE * zfp;
struct Adresse Data;
struct NeueAdresse NData;
qfp = fopen(quelldateiname,"rb");
if (qfp == NULL) return(1);
zfp = fopen(zieldateiname,"wb");
if (zfp == NULL) return(1);
while (!feof(qfp))
{
fread(&Data,sizeof(Data),1,qfp);
strcpy(NData.Name,Data.Name);
NData.Telefon = Data.Telefon;
fwrite(&NData,sizeof(NData),1,zfp);
}
fclose(qfp);
fclose(zfp);
return(0);
}
int fseek(FILE *f, long offset, int origin)
Mit dieser Funktion kann man einen Zeiger auf eine bestimmte
Position innerhalb einer Datei setzen
Die Funktion positioniert um einen offset, der in Bytes gezählt wird.
Der Wert origin legt fest, worauf sich offset bezieht:
Die Funktion long ftell(FILE *f) gibt die aktuelle Position in der Datei an, auf die der Dateizeiger weist. Im Fehlerfall liefert ftell den Wert -1.
Die Funktion void rewind(FILE *f) positioniert auf den Dateianfang und löscht den Fehlerstatus.
Beispiel: Programm mit wahlfreiem Zugriff auf eine Datei
#include <stdio.h>
int main(void)
{
long pos;
int count;
FILE *fp;
int mode = 0;
char c;
fp = fopen("daten.bin","w+");
if (fp == NULL)
{
printf("Fehler beim Oeffnen der Datei\n");
exit(1);
}
/* Datei beschreiben */
fputs("abcdefghijklmnopqrstuvwxyz",fp);
puts("abcdefghijklmnopqrstuvwxyz");
printf("\n");
/* Wahlfreier Zugriff auf Datei */
printf("Eingabe der Position im File (0 bis 25):\n");
scanf("%ld",&pos);
fseek(fp,pos,mode);
pos = ftell(fp);
printf("Dateiposition ist %ld\n",pos);
fread(&c,1,1,fp);
printf("\nBuchstabe an dieser Position: %c\n\n",c);
fclose(fp);
return 0;
}
Beispiel: Hexdump
Der Inhalt einer Datei soll Hexadezimal und in ASCII ausgegeben werden.
Für die Bildschirmausgabe soll das Programm die Datei blockweise anzeigen,
jeweils 16 Zeilen. Der folgende Bildschirmabzug zeigt die Ausgabe:
Adresse 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 : 2F 2A 20 48 65 78 64 75 6D 70 20 65 69 6E 65 72 /* Hexdump einer
00000010 : 20 44 61 74 65 69 20 2A 2F 0D 0A 0D 0A 23 69 6E Datei */....#in
00000020 : 63 6C 75 64 65 20 3C 73 74 64 69 6F 2E 68 3E 0D clude <stdio.h>.
00000030 : 0A 23 69 6E 63 6C 75 64 65 20 3C 73 74 64 6C 69 .#include <stdli
00000040 : 62 2E 68 3E 0D 0A 0D 0A 23 64 65 66 69 6E 65 20 b.h>....#define
00000050 : 52 45 54 20 31 33 20 20 20 20 20 20 20 20 20 20 RET 13
00000060 : 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 2F /
00000070 : 2A 20 52 65 74 75 72 6E 2D 54 61 73 74 65 20 2A * Return-Taste *
00000080 : 2F 0D 0A 23 64 65 66 69 6E 65 20 42 45 45 50 20 /..#define BEEP
00000090 : 70 75 74 63 68 61 72 28 27 5C 30 37 27 29 20 20 putchar('\07')
000000A0 : 20 20 20 20 20 20 20 20 20 20 2F 2A 20 53 69 67 /* Sig
000000B0 : 6E 61 6C 74 6F 6E 20 61 75 73 67 65 62 65 6E 20 nalton ausgeben
000000C0 : 2A 2F 0D 0A 0D 0A 0D 0A 2F 2A 20 67 6C 6F 62 61 */....../* globa
000000D0 : 6C 65 20 56 61 72 69 61 62 6C 65 20 2A 2F 0D 0A le Variable */..
000000E0 : 46 49 4C 45 20 2A 65 69 6E 3B 20 20 20 20 20 20 FILE *ein;
000000F0 : 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
Adresse 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
Anmerkungen zum Programm:
#include <stdio.h>
#include <stdlib.h>
#define RET 13 /* Return-Taste */
#define BEEP putchar('\07') /* Signalton ausgeben */
/* globale Variable */
FILE *ein; /* Eingabedatei */
char name[64]; /* Dateiname */
int ende = 0; /* Dateiende erreicht? */
long position; /* aktuelle Position in 'ein' */
int puffer[16][16]; /* Puffer fuer eine Seite */
/* Funktions-Deklarationen */
void seitelesen(long position); /* Puffer aus Datei lesen */
void ausgabe(long position); /* Puffer darstellen */
/* Hauptprogramm */
int main(int argc, char *argv[])
{
int command; /* Bediener-Kommando */
if (argc < 2) /* Datei ueber Kommandozeile? */
{
printf("Dateiname: "); /* Dann ueber Tastatur */
scanf("%s",&name);
}
else
strcpy(name,argv[1]);
ein = fopen(name,"rb"); /* Eingabe eroeffnen */
if (ein == NULL)
{
printf("Eingabedatei kann nicht eroeffnet werden\n");
exit(1);
}
position = 0; /* Dateianfang */
for(;;) /* endlose */
{ /* Hauptschleife */
seitelesen(position);
ausgabe(position);
command = getchar(); /* Befehlstaste lesen */
switch (command)
{
case '+' : if (feof(ein) == 0) /* naechster Block */
position = position + 256;
else BEEP;
break;
case 'b' : /* vorhergehender Block */
case 'B' :
case '-' : if (position >= 256)
position = position - 256;
else BEEP;
break;
case 'Q' : /* Beenden */
case 'q' : fclose(ein); exit(0);
break;
default : BEEP; /* alle anderen Tasten */
} /* end switch */
} /* end for */
return 0;
}
/* Funktionsdefinitionen */
void seitelesen(long position) /* Puffer aus Datei lesen */
{
int i, j; /* Feldindexe */
int zeichen; /* gelesendes Zeichen */
fseek(ein,position,SEEK_SET); /* Pos. ab Dateianfang */
i = j = 0;
do
{ /* zeichenweise einlesen */
zeichen = getc(ein);
puffer[i][j] = zeichen;
j++;
if (j == 16) (i++, j = 0);
} /* bis Puffer voll oder EOF */
while (i*16+j < 256 && zeichen != EOF);
while (i*16+j < 256) /* Rest-Puffer auffuellen */
{
puffer[i][j] = -1;
j++;
if (j == 16) (i++,j = 0);
}
}
void ausgabe(long position) /* Puffer darstellen */
{
int i, j; /* Feldindexe */
printf("Dump von *** %s *** \n",name);
printf("\n");
printf("Adresse 0 1 2 3 4 5 6 7 8 9 A B C D E F");
printf(" 0123456789ABCDEF\n");
printf("\n");
for (i = 0; i < 16; i++) /* 16 Zeilen */
{
printf("%08lX : ",position);
for (j = 0; j < 16; j++) /* Inhalt hexadezimal */
if (puffer[i][j] < 0)
printf(" ");
else
printf(" %02X",puffer[i][j]);
printf(" ");
for (j = 0; j < 16; j++) /* Inhalt als ASCII-Zeichen */
if (puffer[i][j] > 31 && puffer[i][j] != 127)
printf("%c",puffer[i][j]);
else
if (puffer[i][j] < 0) /* Datei zuende - Blanks */
printf(" ");
else
printf("."); /* nicht druckbares Zeichen */
printf("\n");
position = position + 16;
}
printf("\n");
printf("Adresse 0 1 2 3 4 5 6 7 8 9 A B C D E F");
printf(" 0123456789ABCDEF\n");
printf("\n");
printf("Weiter mit <+> Zurueck mit <B>/<->");
printf(" Abbruch mit <Q>\n");
fflush(stdout);
}
#include <sys/types.h> /* manchmal benoetigt */ #include <dirent.h> /* Headerfile fuer die Verzeichnis-Funktionen */ DIR *opendir(const char *name); struct dirent *readdir(DIR *dir); int closedir(DIR *dir);
struct dirent
{
long d_ino; /* Inode Nummer */
off_t d_off; /* Offset zum naechsten dirent */
unsigned short d_reclen; /* Laenge dieses Eintrags */
char d_name[NAME_MAX+1]; /* Dateiname */
};
Für das Anwenderprogramm ist meist nur der Name des Eintrags interessant.
Will man mehr über diesen Eintrag erfahren, beispielsweise, ob es wieder ein
Verzeichnis ist, verwendet man andere Systemaufrufe, z. B. stat().
#include <sys/types.h>
#include <dirent.h>
int main(void)
{
DIR *dirHandle;
struct dirent * dirEntry;
dirHandle = opendir("."); /* oeffne aktuelles Verzeichnis */
if (dirHandle != NULL)
{
while ((dirEntry = readdir(dirHandle)) != NULL)
{
puts(dirEntry->d_name);
}
closedir(dirHandle);
}
return(0);
}
Wenn man mit den Dateinamen noch weiter arbeiten will, kann der jeweilige Name auch
in eine eigene Variable übertragen werden. Die Länge des Arrays passen
Sie am besten an die Größe an, die das Betriebssystem vorgibt. Sie ist als
Konstante in types.h vorgegeben. Eine Funktion zum Lesen eines
Verzeichnisses könnte dann folgendermaßen aussehen:
void read_dir(char *dirname)
{
DIR *dir;
struct dirent *dirzeiger;
char dateiname[MAXNAMELEN+1]; /* MAXNAMELEN definiert in types.h */
printf("%s\n",dirname);
dir = opendir(dirname);
if(dir != NULL)
{
while((dirzeiger = readdir(dir)) != NULL)
{
strcpy(dateiname,(*dirzeiger).d_name);
if (dateiname[0] != '.') printf("%s\n",dateiname);
}
closedir(dir);
}
void rewinddir(DIR *dir);
#include <unistd.h> char * getcwd(char *namebuffer, size_t size);In einigen Systemen ist es zulässig, NULL als Parameter für namebuffer zu übergeben. Dann alloziiert getcwd() selbst den benötigten Speicher und gibt den Zeiger darauf zurück. Die Anwendung muß dann durch einen Aufruf von free() den Speicher wieder freigeben.
#include <unistd.h> int chdir(char *Pfad);Bei Erfolg gibt die Funktion 0, sonst -1 zurück. Die Fehlernummer findet sich in der Variablen errno.
#include <fcntl.h> #include <unistd.h> int mkdir(char *Pfadname, mode_t mode); int rmdir(char *Pfadname);Bei Erfolg geben die Funktionen 0, ansonsten -1 zurück. Die Fehlernummer findet sich in der Variablen errno.
Dazu wird zuerst eine Pipe und ein Kindprozess erzeugt. Der Kindprozess schließt die nicht verwendete Seite der Pipe (in diesem Fall die Leseseite). Nun überlagert sich der Kindprozess mittels exec() mit einer Shell, die ihrerseits das externe Programm startet. Das alles muss man aber nicht selbst erledigen, sondern die Arbeit wird von der Bibliotheksfunktion popen() übernommen.
#include <stdio.h> FILE* popen(const char* commandline, const char* mode);Der erste Parameter commandline ist eine Zeichenkette mit dem gewünschten Kommandoaufruf. Der zweite Parameter mode ist entweder "r" für das Lesen aus der Pipe oder "w" für das Schreiben in die Pipe. Bei erfolreicher Ausführung erhält man einen Dateizeiger zurück, bei einem Fehler wird NULL geliefert. Nach Beendigung kann man die Pipe wieder schließen:
#include <stdio.h> int pclose(FILE* filep);Das folgende Beispiel zeigt das Abrufen der Uhrzeit:
#include <stdio.h>
#include <stdlib.h>
FILE* pipe; /* Filehandle fuer die Pipe */
char Datum[100]; /* Eingabepuffer */
int main(void)
{
/* Pipe zum date-Programm lesend oeffnen */
pipe = popen("date +%H-%M-%S", "r");
/* genau 10 Zeichen (hh-mm-ss\n\0) lesen,
Stringterminator '\0' wird von fgets angehaengt */
fgets(Datum,10,pipe);
/* Pipe wieder schliessen */
pclose(pipe);
/* irgendwas machen ... */
printf ("%s\n",Datum);
return 0;
}
Solche Pipes erlauben das "Anzapfen" beliebiger Programme um entweder deren
Ausgabe im eigenen Programm weiterzuverarbeiten oder um den Input eines
externen Programms zu erzeugen. So kann man beispielsweise mit relativ
wenig Aufwand aus den eigenen Daten eine Steuerdatei für das Tool GnuPlot
erzeugen und so aus dem eigenen Programm heraus beliebige Zwei- oder
Dreidimensionale Grafiken erzeugen.
Die bisher besprochenen Pipes fordern eine "Verwandtschaft" zwischen den jeweiligen Prozessen. Will man eine Verbindung zwischen beliebigen Prozessen herstellen, kann man sich auf "Named Pipes" (FIFOs) stützen. Mit dieser Mehode läßt sich auch einfach eine Client/Server-Anwendung realisieren. Named Pipes kann man wie Dateien mit dem Kommando mknod in einem beliebigen Verzeichnis anlegen. Über die FIFO läuft dann die Kommunikation zwischen den Prozessen (FIFO bezeichent übrigens das Arbeitsprinzip: First In, First Out).
Natürlich kann eine FIFO auch gleich vom C-Programm erzeugt werden. Dazu existiert eine Bibliotheksfunktion:
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char* pathname, mode_t mode)
Der erste Parameter gibt Pfad und Namen an, also die Position im Dateisystem, wo die Named Pipe angelegt werden soll. Der zweite Parameter gibt den Erzeugungsmodus an. Hier gibt es sehr viele Möglichkeiten, die im der Manualpage erklärt werden. Das folgende Beispiel legt eine FIFO an, um danach in diese Pipe etwas zu schreiben. Dach werden die Daten wieder aus der Pipe gelesen.
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int pipe; /* File-Handle der Pipe */
char puffer[500]; /* Lese-Puffer */
char test[] = "Dies ist ein Test\n"; /* Ausgabestring */
/* Pipe erzeugen Mode = Schreiben/Lesen */
mkfifo("fifi", O_RDWR);
/* Pipe als Datei oeffnen */
pipe = open("fifo01", O_RDWR);
/* irgend etwas in die Pipe schreiben */
write (pipe, test, strlen(test));
/* nun versuchen wir, das wieder zu lesen */
if (read(pipe, &puffer, sizeof(puffer)) < 0)
{ printf("Pipe leer !\n"); }
else
{ printf("%s\n",puffer); }
/* Pipe schliessen */
close(pipe);
/* Die Pipe ist weiter vorhanden! */
}
Schreibt ein Prozess in eine Pipe, die noch nicht von einem anderen Prozess
zum Lesen geöffnet wurde, wird das Signal SIGPIPE generiert.
Das folgende Beispiel zeigt, wie eine Client-Server-Anwendung aussehen könnte. Client und Server werden einfach aus dem obigen Beispiel "herausgezogen". Der Server legt eine Pipe mit Lese/Schreibzugriff an. Danach wartet er auf Daten, die in diese Pipe geschrieben werden, um sie auf der Standardausgabe auszugeben. Der Client darf erst nach dem Server gestartet werden, da die Pipe dann schon existieren muss. Er schreibt in die Pipe und beendet sich dann.
/*** CLIENT ***/
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int pipe; /* File-Handle der Pipe */
char test[] = "Dies ist ein Test\n"; /* Ausgabestring */
/* Pipe als Datei oeffnen */
pipe = open("fifo01", O_RDWR);
/* irgend etwas in die Pipe schreiben */
write (pipe, test, strlen(test));
/* Pipe schliessen */
close(pipe);
}
/*** SERVER ***/
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int pipe; /* File-Handle der Pipe */
char puffer[500]; /* Lese-Puffer */
/* Pipe erzeugen Mode = Schreiben/Lesen */
mkfifo("fifi", O_RDWR);
/* Pipe als Datei oeffnen */
pipe = open("fifo01", O_RDWR);
/* aus der Pipe lesen */
read(pipe, &puffer, sizeof(puffer));
/* Pipe schliessen */
close(pipe);
}
#include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h>
#include <fcntl.h> int open(const char *pfad, int modus); int open(const char *pfadname, int flags, mode_t zugriffsrechte); int close(int handle);Der Funktion wird der Name und der gewünschte Zugriffsmodus übergeben, der optionale dritte Parameter erlaubt das Setzen von Zugriffsrechten. Die Funktion liefert einen Dateideskriptor zurück, der in den weiteren Funktionsaufrufen als Parameter die Datei identifiziert. Die möglichen Zugriffsmodi sind:
O_RDONLY - nur lesen O_WRONLY - nur schreiben O_RDWR - lesen und schreiben O_APPEND - anhaengen O_CREAT - anlegen, falls noch nicht vorhanden O_EXCL - in Kombination mit O_CREAT verhindern des Überschreibens O_NOCTTY - kein Kontrollterminal des Prozesses O_NONBLOCK - bei Pipe/Gerät sind die I/O-Operationen nicht blockierend (Socket) O_SYNC - Schreibvorgang wird direkt ausgeführt O_BINARY - Binaerdatei (DOS/Windows) O_TEXT - Textdatei (DOS/Windows)Gegebenenfalls kann man die Werte "verodern" (z. B. O_WRONLY|O_CREAT).
Die Zugriffsrechte lauten:
S_IWRITE - Schreibrecht S_IREAD - Leserecht S_IWRITE|S_IREAD - Schreib- und LeserechtDie Funktion liefert die Handlenummer zurück oder bei Fehler -1.
Die Datei wird mit close(handle) wieder geschlossen. Die Funktion liefert 0 zurück oder bei Fehler -1. Beispiel:
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int fd;
fd = open("Testdatei",O_RDONLY); /* Datei muß existieren */
if (fd == -1)
perror("Datei existiert nicht");
else
close(fd);
return 0;
}
Der Aufruf von open erzeugt keine Datei, falls sie nicht existiert. Das
muss der Zugriffsparameter regeln, z. B.:
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int fd;
fd = open("Testdatei",O_WRONLY|O_EXCL|O_CREAT,0644);
close(fd);
return 0;
}
Sofern Sie mit der oktalen Schreibweise der Zugriffsrechte unter UNIX/Linux
vertraut sind, können Sie diese wie oben verwenden. Die führende Null nicht
vergessen, sonst ist es keine Oktalzahl.
#include <unistd.h> #include <fcntl.h> int write(int fh, const void *puffer, size_t bytezahl); int read(int fh, const void *puffer, size_t bytezahl);Transfermenge sind immer Bytes. Wichtig ist, dass die Transfermenge (bytezahl) nie mehr als die Puffergröße betragen darf. Das folgende Beispielprogramm kopiert eine Datei:
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int in, out; /* Filehandles */
char buffer[1024]; /* Datei-Puffer */
int count;
in = open("Quelle",O_RDONLY); /* muss vorhanden und lesbar sein */
out = open("Ziel",O_WRONLY|O_TRUNC|O_CREAT,0644);
while((count = read(in,buffer,1024)) > 0)
write(out,buffer,count);
close(in);
close(out);
return 0;
}
read() versucht, die angegebene Anzahl Bytes in den Puffer einzulesen, wobei
die tatsächliche Anzahl vom Aufruf zurückgeliefert wird. Deshalb sollten stets nur
count Bytes weggeschrieben werden. Der letzte Block kann auch weniger als
1024 Bytes enthalten, deshalb kann man nicht einfach immer 1024 wegschreiben.
#include <unistd.h> #include <fcntl.h> long lseek(int fh, long offset, int mode);Die Datei wird wieder durch den File-Deskriptor fh identifiziert, der natürlich zuvor geöffnet bzw. erzeugt wurde. Um wieviele Bytes der File-Deskriptor von der Position wie verschoben werden soll, wird mit offset angegeben. Die Angaben von mode sind dieselben wie bei fseek():
SEEK_SET oder 0 - vom Dateianfang aus um offset Bytes verschieben SEEK_CUR oder 1 - von der aktuellen Position um offset Bytes verschieben SEEK_END oder 2 - vom Dateiende um offset Bytes verschiebenDie Funktion gibt den Wert der aktuellen Position des File-Deskriptors zurück. Bei einem Fehler gibt diese Funktion –1 zurück. Es sollte keinesfalls auf " < 0" geprüft werden, sondern auf –1, da es möglicherweise Gerätedateien gibt, die einen negativen Wert zurückliefern. Beispiele:
long akt_pos; akt_pos = lseek(fh, 0L, SEEK_CUR);Deskriptor auf den Dateianfang setzen:
lseek(fh, 0L, SEEK_SET);Deskriptor um 222 Bytes nach vorn versetzen:
lseek(fh, 222L, SEEK_CUR);Deskriptor um 100 Bytes zurücksetzen:
lseek(fh, -100L, SEEK_CUR);Oft wird von einem Gerät mittels read() gelesen (serielle Schnittstelle, USB-Schnittstelle, I/O-Port bei Embedded Systemen etc.), aber man will die Daten zeilenweise weiterverarbeiten. Der Puffer enthält natürlich irgendwo Newline-Zeichen, man muss sie nur Suchen. Aus diesem Grund soll hier das Splitten des Puffers in einzelne Zeilen etwas näher betrachtet werden. Normalerweise wäre es ein Zufall, wenn ein Zeilenende genau mit dem Pufferende zusammenfällt, und man den String per Standardfunktion splitten kann. Die Regel ist, dass ein Puffer den Anfang und der nächgste Puffer das Ende einer Zeile enthält. Darum soll jetzt die Arbeit mit mehreren aufeinanderfolgenden Datenblöcken näher betrachtet werden:
Die erste Möglichkeit, die sich anbietet, ist das zeichenweise Lesen von der Netzwerk-Schnittstelle (das Betriebssystem puffert ja die Pakete für uns). Sobald dann ein Newline ('\n') auftritt, wird die Funktion verlassen und gibt den String zurück. Es versteht sich von selbst, dass vom aufrufenden Programm ein Zeichen-Array zur Verfügung gestell werden muss und dass man dessen Länge nicht überschreitet. Die folgende Funktion get_line() liest von einer vorhergeöffneten Datei genau eine Zeile ein (der Unterstrich in get_line() ist notwendig, weil getline() eine Bibliotheksfunktion ist). Als Parameter werden neben dem Datei-Descriptor das Array und dessen maximale Länge übergeben:
int get_line(int fd, char *buffer, unsigned int len)
{
/* read a '\n' terminated line from fd into buffer
* of size len. The line in the buffer is terminated
* with '\0'. It returns -1 in case of error and -2 if the
* capacity of the buffer is exceeded.
* It returns 0 if EOF is encountered before reading '\n'.
*/
int numbytes = 0;
int ret;
char buf;
buf = '\0';
while ((numbytes <= len) && (buf != '\n'))
{
ret = read(fd, &buf, 1); /* read a single byte */
if (ret == 0) break; /* nothing more to read */
if (ret < 0) return -1; /* error or disconnect */
buffer[numbytes] = buf; /* store byte */
numbytes++;
}
if (buf != '\n') return -2; /* numbytes > len */
buffer[numbytes-1] = '\0'; /* overwrite '\n' */
return numbytes;
}
Nachteil dieser Lösung ist die Geschwindigkeit bzw. deren Fehlen. Durch die vielen
read()-Aufrufe ist die Funktion ziemlich langsam. Besser wäre eine Lösung,
bei der ein Datenpaket komplett eingelesen und
dann Zeile für Zeile ans aufrufende Programm weitergereicht wird. Genau das macht
die folgende Funktion, bei der die Parameter die gleiche Aufgabe haben wie oben.
Diese Funktion hat einen internen Puffer, der mittels read() gefüllt
wird und dessen Inhalt Stück für Stück bei jedem Aufruf weitergegeben wird. Dazu
verwendet die Funktion die statischen Variablen bufptr, count
und mybuf, deren Werte erhalten bleiben und bei jedem Aufruf wieder zur
Verfügung stehen. Werden mit read() mehrere Zeilen gelesen, bleibt
der jeweilige Rest in mybuf erhalten und wird beim nächsten Aufruf der
Funktion verarbeitet:
int readline(int fd, char *buffer, unsigned int len)
{
/* read a '\n' terminated line from fd into buffer
* bufptr of size len. The line in the buffer is terminated
* with '\0'. It returns -1 in case of error or -2 if the
* capacity of the buffer is exceeded.
* It returns 0 if EOF is encountered before reading '\n'.
* Notice also that this routine reads up to '\n' and overwrites
* it with '\0'. Thus if the line is really terminated with
* "\r\n", the '\r' will remain unchanged.
*/
static char *bufptr;
static int count = 0;
static char mybuf[1500];
char *bufx = buffer;
char c;
while (--len > 0) /* repeat until end of line */
{ /* or end of external buffer */
count--;
if (count <= 0) /* internal buffer empty --> read data */
{
count = read(fd, mybuf, sizeof(mybuf));
if (count < 0) return -1;/* error or disconnect */
if (count == 0) return 0; /* nothing to read - so reset */
bufptr = mybuf; /* internal buffer pointer */
}
c = *bufptr++; /* get c from internal buffer */
if (c == '\n')
{
*buffer = '\0'; /* terminate string and exit */
return buffer - bufx;
}
else
{
*buffer++ = c; /* put c into external buffer */
}
}
return -2; /* external buffer to short */
}
Beim Senden von Daten sollte man eigentlich nicht zwischenpuffern, sondern
jede Zeile sofort auf die Reise schicken - schließlich wartet der Empfänger
darauf.
Zum Inhaltsverzeichnis |
Zum nächsten Abschnitt |