PS-Trainer C - Entwicklung
Lehreinheit Nr. 5: files
Homepage von PS-Trainer - C-Entwicklung - Bibliotheken - an PS-Trainer
PS-Trainer PS-Trainer

Dateien / files:
Ziel dieser Lehreinheit ist die Arbeit mit Dateien. Sie lernen, einfache Text-Dateien zu lesen und zu schreiben, anschließend den Umgang mit Binärdateien.
Dazu finden sie auf dieser Seite die Beschreibung einiger ausgewählter Funktionen sowie 4 Übungs-Beispiele.

Stream:
Sie kennen bereits die Datenströme stdin und stdout - ihre Konsole. Zusätzlich können weitere Datenströme benutzt werden, z.B. von und zu Dateien im Filesystem ihres PC oder Netzwerks.

Bei Verwendung eines Streams unterscheiden sie 3 Phasen
Stream öffnen
Stream verwenden
Stream schliessen.

Stream öffnen:
Jedes Betriebssystem / Programm, das mit Streams arbeitet, benutzt dazu (unterschiedliche) Strukturen, meist handles (Handgriffe) genannt, die üblicherweise per Pointer übergeben oder mit fortlaufenden ganzen Zahlen bezeichnet werden. Um einen Stream zu öffnen, müssen sie daher ein file handle erzeugen. Meist dienen dazu "...open..."-Befehle:
Zum Öffnen von Streams (Files) benötigen sie einige Angaben, üblicherweise werden für nicht definierte Parameter Standardwerte (default values) angenommen: Dateiname (filename) bzw. Pfad (inkl. volume und directory), Dateitype (Text (text), Binär (binary)), Zugriff (Sequentiell, Random access), Zugriffsrechte (Read, write, append, delete...) usw.
Ein open-Befehl übergibt bei korrekter Funktion ein handle (file-number), bei nicht korrekter Funktion einen Fehlerwert - das müssen sie prüfen, bevor sie weitermachen.

Stream verwenden:
Je nach Art der Datei, des Zugriffs usw. haben sie nach dem Öffnen eines Streams Zugriff auf diesen, d.h. sie können Daten lesen und / oder schreiben. Jeder diesbezügliche Befehl benötigt die Angabe des handles (der file-number), andernfalls werden Standardwerte (stdin, stdout = Konsole) eingesetzt. Damit wird auch die Unterscheidung zwischen mehreren gleichzeitig geöffneten files klar: Sie verwenden die gleichen Befehle, allerdings mit unterschiedlichen handle-Pointern.

Stream schliessen.
Die Anzahl gleichzeitig geöffneter streams ist je nach Betriebssystem unterschiedlich, jedoch stets begrenzt. Vergessen sie daher niemals, offene streams (files) nach Gebrauch (auch nach Fehlfunktion !) zu schließen !


Einige ausgewählte Stream-Funktionen:

FILE *stream1, *stream2;
Diese Definition legt die Variablen stream1 und stream2 als Pointer auf eine Struktur vom Typ FILE (file handle) fest.
Bibliothek: #include <stdio.h>

fopen( stream, optionstring)
Funktion öffnet ein file.
Parameter: 1. Parameter: FILE-pointer, 2. Paremeter: options-string
options string:
"r"=read (file muss vorhanden sein),
"w"=write (neues file wird erzeugt, wenn es bereits existiert, überschrieben),
"a"=append (wenn file existiert, wird neuer Inhalt am Ende angefügt, wenn nicht, wird ein neues file erzeugt)
"r+"=read+write (file muss existieren),
"w+"=read+write(neues file wird erzeugt, wenn es bereits existiert, überschrieben);
zusätzliche optionen: "t"=text, "b"=binary)
Return value: Pointer auf ein offenes file oder NULL als Fehlerwert.
Beispiel: stream = fopen( infile, "rt" );

fgets(buffer,charmax,stream)
Funktion liest eine Zeile (einen String) von einem Datenstrom (stream)
Parameter: 1. Parameter (string) erhält die Daten, 2. Parameter (int) max. Anzahl von Zeichen, 3. Parameter FILE-pointer
Die Funktion liest so lange Zeichen aus dem Stream, bis entweder ein LF (linefeed) auftritt, oder charmax erreicht ist, oder EOF end-of-file) erreicht ist, oder ein Fehler auftritt. Die bis dahin gelesenen Zeichen sind im String buffer verfügbar. Der result-String wird mit 0-byte abgeschlossen.
Return value: String oder NULL als Fehlerwert.
Beispiel: while ( fgets(line,charmax,stream) != NULL) {...}

fputs( string, stream )
Funktion schreibt einen String auf einen Datenstrom (stream)
Parameter: 1. Parameter enthält die zu schreibenden Daten, 2. Parameter FILE-pointer.
Return value: >=0 wenn erfolgreich, EOF als Fehlerwert.
Beispiel:
gets(line);
fputs(line,stream);
fputs("\n",stream);


getchar (stream)
Funktion liest ein Zeichen von der aktuellen Position eines Datenstroms (stream) und setzt den file-pointer um 1 weiter.
Parameter: FILE-pointer.
Return value: das gelesene Zeichen oder EOF
Beispiel: while ( (ch = getchar(stream) != EOF) && (ch != '\n') ) {...}

putc ( ch, stream )
Funktion schreibt ein Zeichen an die aktuelle Position eines Datenstroms (stream) und setzt den file-pointer um 1 weiter.
Parameter: 1. Parameter (char) das zu schreibende Zeichen, 2. Parameter FILE-pointer
Return value: Das tatsächlich geschrieben Zeichen oder EOF als Fehlerwert.
Beispiel: test = putc( ch, stream );

fclose( stream )
Funktion schließt einen offenen Datenstrom
Parameter: FILE-pointer.
Return: (int) 0 wenn stream geschlossen, EOF wenn Fehler.

fcloseall ()
Funktion schließt alle offenen Datenströme (außer Konsole).
Return: (int) Anzahl der geschlossenen Datenströme oder EOF als Fehlerwert.

ftell (stream)
Liest die aktuelle Position des file-pointers.
Parameter: FILE-pointer
Return: (long) file pointer oder -1L als Fehlerwert.

fseek (stream, offset, origin)
Stellt den file-pointer eines streams auf einen bestimmten Wert ein. (Nur in Binärfiles verwenden, da die Effekte von CR oder CR/LF stören können).
Parameter: 1. Parameter FILE-pointer, 2. Parameter (long) Entfernung von Origin in bytes, 3. Parameter (int) Startposition.
Origin muss einen der folgenden Werte annehmen: SEEK_CUR (aktuelle Position), SEEK_END (EOF), SEEK_SET (file-Anfang)
Return Value: 0 wenn erfolgreich.
Beispiel: result = fseek( stream, 23L, SEEK_SET);

rewind (stream)
Stellt den file-pointer an den Anfang eines files.


Übungs-Beispiele:
001 Lesen einer Text-Datei
002 Schreiben einer Text-Datei
003 Lesen und Schreiben einer Textdatei
004 Lesen und Schreiben einer Binärdatei

001
Lesen einer Text-Datei
Erzeugen sie ein einfaches Programm, mit dem sie Text-Dateien einlesen (und verarbeiten) können. Hier ein Beispiel:

Erzeugen sie vor Beginn ihrer Arbeit mit einem Text-Editor eine (unformatierte !) Text-Datei (z.B. "datain.txt" im gleichen verzeichnis wie ihre cpp-Datei.

Zunächst müssen die die Bibliothek <stdio> laden und mit Hilfe der darin definierten Struktur FILE ein handle (hier: stream) definieren.

Um außer dem reinen Lesen einen Effekt zu demonstrieren, wird hier ein Zähler (zeile) mitgeführt, der die Anzahl der read-operationen mitzählt.

Weiters wird zur Sicherheit die max. Anzahl der Zeichen pro Zeile und die max. Anzahl der Zeilen pro file festgelegt. In beiden Fällen reagiert das Programm so, dass unerwartete Effekte verhindert werden.

Mit den Funktionen fopen bzw. fclose wird die Datei geöffnet bzw. geschlossen, mit Funktion fgets jeweils 1 string gelesen.

#include <stdio.h>

void main( void )
{
FILE *stream;
int charmax,linemax,zeile;
char line[100];
char infile[]="datain.txt";

zeile=0;
charmax=100; // max chars in a single line
linemax=100; // max lines in document

if( (stream = fopen( infile, "rt" )) != NULL )
{
printf("> File %s opened\n",infile);
while ( (fgets(line,charmax,stream) != NULL) &&
(zeile<=linemax)) {
printf( ">%3d: %s", zeile++,line);
}
fclose( stream );
printf("\n> File %s was closed\n",infile);
}
else printf("> Error: File %s could not open\n",infile);

}


002
Schreiben einer Datei

Erzeugen sie ein einfaches Programm, mit dem sie Text-Dateien schreiben können.
Hier ein Beispiel:

Zunächst müssen die die Bibliothek <stdio> laden und mit Hilfe der darin definierten Struktur FILE ein handle (hier: stream) definieren.

Der Zähler zeile wird hier ausschließlich für die laufende Beschriftung der Eingabe-Aufforderung verwendet und nicht in die Ausgangedatei geschrieben.

In einer Schleife wird jeweils 1 Zeile Text über die Konsole eingegeben. Eingabe von <return> ohne anderes Zeichen beendet die Eingabe und schließt die Datei.

Beachten sie, daß das newline-byte im 2. Aufruf der Funktion fputs in Doppel-"" steht und daher als String (!), nicht als einzelner char verwendet wird !

Kontrollieren sie die Ausgabedatei (im gleichen Verzeichnis wie ihre cpp-Datei) mit einem Text-Editor.
#include <stdio.h>
#include <string.h>

void main( void )
{
FILE *stream;
int zeile;
char line[100];
char outfile[]="dataout.txt";

zeile=0;
if( (stream = fopen( outfile, "wt" )) != NULL )
{
printf("> File %s opened for output\n",outfile);
line[0]='$';
line[1]='\0';
while ( strlen(line)>0) {
printf( ">Write line %3d: ",zeile++);
gets(line);
fputs(line,stream);
fputs("\n",stream);

}
fclose( stream );
printf("\n> File %s was closed\n",outfile);
}
else printf("> Error: File %s could not open\n",outfile);

}


003
Lesen und Schreiben einer Text-Datei

Erzeugen sie ein einfaches "Filter" für Text-Dateien, das die Zeilen numeriert. Eingang: eine beliebige Text-Datei. Ausgang: Die gleichen Daten, jedoch am Anfang jeder Zeile numeriert.

Nach diesem Muster können sie "Filter" für jede gewünschte Funktion erzeugen: Sie lesen die Eingabedaten, verändern ("interpretieren") sie nach Bedarf und geben die Ausgabedatei aus. Da sie immer nur einen kleinen Datenbereich beider Dateien offen haben (sliding window), können sie so mit winzigen Programmen fast beliebig große Dateien bearbeiten.

004
Lesen und Schreiben einer Binär-Datei


Erzeugen sie ein einfaches "Filter" für Binär-Dateien.
Erhöhen sie den Wert jedes einzelnen bytes um 1. - Schieben sie nach einer bestimmten Anzahl von bytes ("chunk") ein zusätzliches byte ein, - Schreiben sie ein zusätzliches byte an das Ende der Ausgabedatei.

Binärdateien sind genauso zu bearbeiten wie Text-Dateien. Zum Unterschied von diesen können die bytes jedoch jeden beliebigen Wert annehmen, also 0...255, sind also nicht mehr "printable". Daher gibt es auch keine "Zeilen" sondern einen kontinuierlichen Datenstrom, der jeden beliebigen, von Ihnen oder vom Hersteller der Datei definierten Sinn haben kann.
Damit sie einen derartigen Datenstrom bearbeiten können, teilen sie ihn in "verdaubare Brocken" (chunks, z.B. #define chunk 7) ein, die sie in einen Puffer einlesen. Sie müssen lediglich diese(n) Puffer geschickt verwalten, dann ist die Arbeit mit Binärdateien kein Problem. Verwenden sie z.B. als Kontrollzeichen nach jedem chunk ein "*", am Datei-Ende ein "$".

Zur "manuellen" Bearbeitung und zur Kontrolle von Binärdateien benötigen sie einen Hex-Editor, mit dem sie beliebige Daten anzeigen (Hex und / oder ASCII) und ändern können. Diese und ähnliche Programme finden sie auf Freeware- und Shareware-Seiten z.B. unter "Entwicklung", "development", usw... Ausgewählte Beispiele (ohne Wertung, Vollständigkeit oder sonstige Gewähr):
Ashe (free+share, Grand River, bei ZDnet), Binary Workshop (free, SpyTech bei ZDnet), File Editor (share, bei ZDnet), Frhed (Free Hex Editor, SourceForge, bei ZDnet), Hackman (free, Technologismiki), HexEdit (Expertcomsoft mit C-SourceCode !), HexEditor 2001 (NextSoft), Hex Editor II (share, Diplodock, bei ZDnet), Hex Workshop (share, BreakPoint, bei ZDnet), Hextreme (free, MikerSoft, bei ZDnet oder SimTel), NitroHex (Nitrobit), SunnyNotePad (share, Slavco Illic, bei ZDnet oder tucows), SynEdit (SourceForge, bei nonags), UltraEdit (IDM computer solutions), WinHex (Stefan Fleischmann), ... ... ...
Ratsam ist weiter ein Hex-Calculator (Taschenrechner), soferne nicht bereits im Hex Editor integriert: Damit können sie Decimal <-> Hex umrechnen und einfache Rechnungen (z.B. Addition, Subtraktion, log. AND, OR, NOT...) direkt in Hex ausführen können. Praktisch: Ein zusätzlicher Binär-Modus. Ausgewählte Beispiele (ohne Wertung, Vollständigkeit oder Gewähr):
Brains RPN-Calc (Adatech), DBcalc (dirkbilland), HexRech u.a. tools (Michael Schikora), khs-calc (khs-instruments), Programmers Calculator inkl. C-Source (Art Dahm, bei vr-web), web-online (bei bloobery, duluth, nyctnetw, TU Berlin), ......


Eine Auswahl nützlicher Funktionen:
Verwenden sie hier z.B. nicht die MS-spezifischen Funktionen (wie oben), sondern die portablen Standard ANSI-Funktionen. Diese benutzen zur Referenz von Dateien nicht Pointer auf Strukturen, sondern (int) file-handles, welche die gleiche Wirkung haben: sie definieren jeweils den Zugang zu einem geöffneten Datenstrom.

int _open( const char *filename, int oflag [, int pmode] );
Öffnet eine beliebige Datei (ASNI-kompatibel, nicht MS-spezifisch) - Bibliothek <io.h>
Parameter: filename (string), oflag (erlaubte Operationen), pmode (Permission mode):
oflag is an integer expression formed from one or more of the following manifest constants or constant combinations defined in FCNTL.H. When two or more manifest constants are used to form the oflag argument, the constants are combined with the bitwise-OR operator ( | ).
_O_APPEND Moves file pointer to end of file before every write operation.
_O_BINARY Opens file in binary (untranslated) mode.
_O_CREAT Creates and opens new file for writing. Has no effect if file specified by filename exists. pmode argument is required when _O_CREAT is specified.
_O_CREAT | _O_EXCL Returns error value if file specified by filename exists. Applies only when used with _O_CREAT.
_O_RANDOM Specifies primarily random access from disk
_O_RDONLY Opens file for reading only.
_O_RDWR Opens file for both reading and writing.
_O_SEQUENTIAL Specifies primarily sequential access from disk
_O_TEXT Opens file in text (translated) mode.
_O_TRUNC Opens file and truncates it to zero length; file must have write permission. _O_TRUNC used with _O_CREAT opens an existing file or creates a new file.

The pmode argument is required only when _O_CREAT is specified. If the file already exists, pmode is ignored. Otherwise, pmode specifies the file permission settings, which are set when the new file is closed the first time. _open applies the current file-permission mask to pmode before setting the permissions.
_S_IREAD Reading only permitted
_S_IWRITE Writing permitted (effectively permits reading and writing)
_S_IREAD | _S_IWRITE Reading and writing permitted

Return value: returns an (int) file handle for the opened file. A return value of –1 indicates an error.

Beispiel:
#define chunk 100
... ... ...
int fh1, fh2, nbytes;

unsigned int bytesread, out_done;
char inbuffer[chunk],outbuffer[chunk];
... ... ...
fh1 = _open("INPUT.BIN",_O_RDONLY | _O_BINARY );
fh2 = _open("OUTPUT.BIN",_O_RDWR | _O_CREAT | _O_BINARY ,_S_IREAD | _S_IWRITE );
... ... ...
bytesread = _read( fh1, inbuffer, nbytes);
byteswrite = _write( fh2, outbuffer, nbytes);
... ... ...
_close( fh1 );
_chsize( fh2, out_done );
_close( fh2 );

long _filelength( int handle );
Liest die Länge einer Datei in bytes.
Parameter: (int) file handle.
Return: Länge in bytes.

int _read( int handle, void *buffer, unsigned int count );
Liest Daten von einer geöffneten Datei in einen vorbereiteten Puffer.
Parameter: file handle, byte-Puffer, Anzahl der zu lesenden bytes.
Return value: Anzahl der tatsächlich gelesenen bytes (kann z.B. am Datei-Ende kleiner als angefordert sein), oder 0 bei EOF oder -1 bei Fehlern.

int _write( int handle, const void *buffer, unsigned int count );
Schreibt Daten in eine geöffnete Datei.
Parameter: file handle, byte-Puffer, Anzahl der zu schreibenden bytes.
Return Value: Anzahl der tatsächlich geschriebenen bytes, -1 bei Fehler.

int _chsize( int handle, long size );
Ändert die Dateigröße einer geöffneten Datei.
Parameters: file handle, Größe in byte.
Return =0 bei Erfolg, oder -1 bei Fehler.

Homepage von PS-Trainer - Entwicklung - an PS-Trainer

Aktuelle Daten dieser Seite Letzte Änderung:
  Geocities