Kapitel 16: Datei-Zugriff

Inhaltsverzeichnis

Kapitel 18: Datum und Zeit

17. Betriebssystem-Anweisungen

In Kapitel 16 haben wir bereits Dateien ausgelesen und beschrieben. Jetzt wollen wir weitere Befehle zum Umgang mit Dateien und Ordner kennenlernen. Zunächst geht es um das Navigieren im Dateiensystem und das Anlegen und Löschen von Ordnern, danach um das Umbenennen, Verschieben und Kopieren von Dateien sowie das Abrufen von Dateiinformationen. Außerdem erfahren Sie, wie Sie externe Programme aufrufen können.

17.1 Ordnerfunktionen

17.1.1 Kurze Einführung in das Ordnersystem

Dateien werden in einer hierarchischen Ordnerstruktur gespeichert. Dabei arbeiten verschiedene Betriebssysteme leicht unterschiedlich, es gibt aber auch deutliche Gemeinsamkeiten. Darunter ist zunächst einmal der Dateipfad zu nennen. Pfade können absolut oder relativ angegeben werden, die durchlaufenen Ordner werden durch ein betriebssystemabhängiges Trennzeichen getrennt.

17.1.1.1 Absolute Pfadangaben

Eine absolute Pfadangabe verweist auf den gewünschten Ordnerpfad, unabhängig davon, von welchem Pfad aus sie aufgerufen wird.

  • Unter Windows beginnt der absolute Pfad mit einem Laufwerksbuchstaben gefolgt von einem Doppelpunkt, oder aber bei einem Netzwerkordner mit zwei Backslashes gefolgt vom Netzwerkordner-Namen (Uniform Naming Convention). Das Trennzeichen zwischen den Ordnern ist der Backslash, wobei Windows jedoch auch den Slash akzeptiert. Ein absoluter Pfad könnte also folgendermaßen aussehen:
    C:\Users\Peter\Documents\BASIC\ (Ordner auf dem Laufwerk C:)
    \\PC-WOHNZIMMER\Users\Paul\Documents\BASIC\ (Ordner auf dem Computer namens PC-WOHNZIMMER)

  • Unter Linux sind sämtliche Ressourcen im Root-Verzeichnis / eingebunden. Es gibt also keine Laufwerksbuchstaben, sondern alle Dateien, egal ob Festplatte, DVD-Laufwerk oder Drucker (ja, auch Drucker und andere Hardware werden als Dateien angesprochen) sind über das Root-Verzeichnis eingebunden. Als Trennzeichen wird / verwendet. Ein möglicher Pfad könnte lauten:
    /home/Mary/Dokumente/BASIC/

Da der Slash als Trennzeichen von Linux benötigt und von Windows akzeptiert wird, ist auch unter Windows die Verwendung des Slashes anzuraten. Dadurch bleibt der Quelltext systemunabhängig (natürlich nicht bei Verwendung absoluter Pfade). Nur wenn Sie Programme für DOS schreiben wollen, müssen Sie den Backslash verwenden.
Das Buch verwendet im Folgenden immer den Slash als Trennzeichen. Außerdem werden Ordnerpfade im Buch immer mit einem Trennzeichen abgeschlossen, um deutlich zu machen, dass es sich um einen Ordner und nicht um eine Datei handelt.1

Sowohl unter Linux als auch unter Windows sind in jedem Ordner automatisch zwei Ordner fest angelegt, nämlich ./ (verweist auf den Ordner selbst, was insb. unter Linux wichtig ist) und ../ (verweist auf den ihm übergeordneten Ordner, also den Elternordner). Im Dateibrowser werden sie meist nicht angezeigt, sie sind jedoch für die Pfadnavigation wichtig.

17.1.1.2 Relative Pfadangaben

Eine relative Pfadangabe ist abhängig vom Pfad, von dem aus sie aufgerufen wird. Eine Pfadangabe wie inc/freetype2/ etwa gibt an, dass vom aktuellen Ordner ausgehend der Unterordner inc/ und dort der Unterordner freetype2/ aufgerufen werden soll. Befindet man sich z. B. gerade im FreeBASIC-Installationsordner, dann verweist der Pfad inc/Lua/ auf die Include-Dateien von Lua, die sich in Unterordnern des FreeBASIC-Installationsordners befinden.

In der Regel werden wir immer mit relativen Pfadangaben arbeiten. Sie werden z. B. des Öfteren auf Dateien innerhalb Ihres Projekt zugreifen wollen, die in einem Unterodner Ihres Projektordners liegen. Wenn Sie ausschließlich relative Pfadangaben verwenden, können Sie das komplette Projekt problemlos an einen anderen Ort bewegen — etwa auf den USB-Stick ziehen oder auf einen anderen Rechner kopieren — ohne dass die Pfadbezüge verloren gehen. Bei Verwendung absoluter Pfade ist das für gewöhnlich nicht möglich.

An dieser Stelle kommt auch der Elternordner ins Spiel. Dazu gleich ein konkretes Beispiel: Sie befinden sich im Projektordner
C:/Users/Peter/Documents/BASIC/einsteigerhandbuch
und rufen von dort aus den Pfad ../gemeinsameDateien/Bilder/ auf. Damit landen Sie im Ordner
C:/Users/Peter/Documents/BASIC/gemeinsameDateien/Bilder
Die zwei Punkte wechseln in den Elternordner C:/Users/Peter/Documents/BASIC/, und von dort aus geht es weiter in den Ordner gemeinsameDateien/ und Bilder/.
Beachten Sie jedoch, dass durch die Einbindung des Elternordners und dessen Unterordner die Pfadunabhängigkeit des Projekts verloren gehen kann!

17.1.2 Arbeitsverzeichnis anzeigen und wechseln: CURDIR() und CHDIR()

Auch FreeBASIC-Programme werden von einem Pfad aus ausgeführt, d. h. alle relativen Pfadangaben innerhalb des Programmes werden von diesem Pfad aus berechnet. Man spricht hierbei vom Arbeitsverzeichnis. In der Regel ist das der Pfad, von dem aus das Programm gestartet wurde, und das wiederum ist häufig der Pfad, in dem sich das ausgeführte Programm befindet. Allerdings muss das nicht so sein! Sie können Ihre Programme über Konsole von jedem beliebigen Pfad aus starten, womit sich dann natürlich das Arbeitsverzeichnis ändert.

Das aktuelle Arbeitsverzeichnis erhalten Sie über die Funktion CURDIR() (CURrent DIRectory). Mithilfe des Befehls CHDIR() (CHange DIRectory) können Sie das Arbeitsverzeichnis ändern, wobei hier, wie üblich, relative und absolute Pfadangaben erlaubt sind. Der Rückgabewert gibt an, ob der Verzeichniswechsel erfolgreich war (dazu unten mehr). Wie die meisten Funktionen lässt sich CHDIR() auch als Anweisung aufrufen.

Quelltext 17.1: Arbeitsverzeichnis anzeigen und wechseln
PRINT "Sie befinden sich aktuell im Verzeichnis " & CURDIR
PRINT "Ich wechsle nun in den Elternordner."
CHDIR ".."
PRINT "Das neue Arbeitsverzeichnis lautet jetzt " & CURDIR
PRINT "Ich wechsle in einen Ordner, der nicht existiert."
CHDIR "../ordner/pfad/der/nicht/vorhanden/ist"
PRINT "Das neue Arbeitsverzeichnis lautet jetzt " & CURDIR
PRINT "Noch ein Versuch, diesmal mit Fehlerkontrolle:"
IF CHDIR("../ordner/pfad/der/nicht/vorhanden/ist") THEN
  PRINT "Wechsel fehlgeschlagen - der Ordner existiert wohl nicht."
ELSE
  PRINT "Das neue Arbeitsverzeichnis lautet jetzt " & CURDIR
END IF
PRINT "Ich wechsle nun in den Ordner /.."
CHDIR "/.."
PRINT "Das neue Arbeitsverzeichnis lautet jetzt " & CURDIR
SLEEP
Ausgabe:
Sie befinden sich aktuell im Verzeichnis D:\\Users\\Ich\\BASIC
Ich wechsle nun in den Elternordner.
Das neue Arbeitsverzeichnis lautet jetzt D:\\Users\\Ich
Ich wechsle in einen Ordner, der nicht existiert.
Das neue Arbeitsverzeichnis lautet jetzt D:\\Users\\Ich
Noch ein Versuch, diesmal mit Fehlerkontrolle:
Wechsel fehlgeschlagen - der Ordner existiert wohl nicht.
Ich wechsle nun in den Ordner /..
Das neue Arbeitsverzeichnis lautet jetzt D:\\

Durch die Verwendung der Trennzeichens / ist das Programm sowohl unter Windows als auch Linux lauffähig, auch wenn Windows bei der Ausgabe selbst \ verwendet. Die angezeigten Pfadnamen werden sich bei Ihnen natürlich unterscheiden. Ein paar wichtige Informationen können wir jedoch aus der Ausgabe herauslesen:

  • Pfade, die mit Slash (oder unter Windows auch mit Backslash) beginnen, werden als absolute Pfade interpretiert. Unter Linux ist der Pfad / das Root-Verzeichnis des Systems. Unter Windows landen Sie im obersten Ordner des aktuellen Laufwerks. Der mit dem Befehl CHDIR "/.." zusätzlich durchgeführte Wechsel in den nächsthöheren Ordner bleibt wirkungslos, da / bereits der oberste Ordner ist (dennoch ist er erlaubt, da auch das Root-Verzeichnis den Ordner .. enthält).

  • Wenn Sie in einen Ordner wechseln wollen, der nicht existiert, wird der Befehl verworfen; es findet auch kein „Teilwechsel“ statt. Sie können aber prüfen, ob der Wechsel erfolgreich war, wenn Sie CURDIR() als Funktion aufrufen. Bei Erfolg liefert sie 0 zurück, bei Misserfolg -1.

  • Die Pfadangabe erfordert keinen abschließenden Slash, und CURDIR() gibt auch keinen abschließenden Slash zurück. Sie können bei CHDIR() den abschließenden Slash setzen, wenn Sie wollen. er wird dann vom Programm ignoriert. Den fehlenden Slash bei der Rückgabe sollten Sie jedoch im Hinterkopf behalten — dazu sehen wir ein Beispiel in Quelltext 17.2.

17.1.3 Programmpfad anzeigen: EXEPATH()

EXEPATH() gibt den Programmpfad aus, also den Pfad, in dem sich das ausgeführte Programm befindet. Wenn Sie, wie z. B. in Quelltext 16.3, auf eine Datei zugreifen wollen, die sich im selben Ordner wie das Programm befindet, gelingt das nur, wenn der Programmpfad mit dem Arbeitsverzeichnis übereinstimmt. Ansonsten wird die Datei im falschen Ordner gesucht und dort nicht gefunden — oder, manchmal noch schlimmer, es wird eine falsche Datei geöffnet, die zufällig denselben Namen hat. Sie können EXEPATH aber beim Öffnen der Datei als Pfadangabe mitgeben, um so sicher zu stellen, dass auf die richtige Datei zugegriffen wird.

Quelltext 17.2: Eine im Programmpfad liegende Datei auslesen
' Oeffne eine Datei, die im selben Ordner liegt wie das Programm
DIM AS INTEGER dateinummer
DIM AS STRING zeile

PRINT "Programmpfad: " & EXEPATH

dateinummer = FREEFILE
IF OPEN(EXEPATH & "/test.bas" FOR INPUT AS #dateinummer) <> 0 THEN
  PRINT "FEHLER: eingabe.txt konnte nicht geoeffnet werden."
ELSE
  DO UNTIL EOF(dateinummer)
    LINE INPUT #dateinummer, zeile
    PRINT zeile
  LOOP
  CLOSE #dateinummer
END IF

Beachten Sie den Slash vor dem Namen der Datei! EXEPATH() liefert, ebenso wie CURDIR(), kein abschließendes Trennzeichen. Wenn Sie also an EXEPATH() einen Pfad oder einen Dateinamen anhängen, müssen Sie dazwischen ein Trennzeichen hinzufügen.

Nun liegt es nahe, mit CHDIR EXEPATH das Arbeitsverzeichnis auf den Programmpfad zu setzen und so weitere Zugriffe zu erleichtern. Das ist jedoch nicht immer von Vorteil, denn dadurch gehen die Informationen über das ursprüngliche Arbeitsverzeichnis verloren. Oft ist es gerade wünschenswert, wenn der Benutzer das Arbeitsverzeichnis wählen kann, um z. B. mit bestimmten Konfigurationsdateien zu arbeiten. Ein Wechsel auf den Programmpfad ist höchstens dann empfehlenswert, wenn Sie ausschließlich auf eigene Programmressourcen zurückgreifen müssen.

17.1.4 Ordner anlegen und löschen: MKDIR() und RMDIR()

MKDIR() (MaKe DIRectory) legt einen neuen Ordner an. Als Funktion aufgerufen gibt der Befehl im Erfolgsfall 0 und ansonsten -1 zurück — das kann z. B. vorkommen, wenn Sie nicht über ausreichende Schreibrechte verfügen, der Ordner bereits existiert oder der gewählte Ordnername ungültige Zeichen enthält.

Um einen vorhandenen Ordner wieder zu löschen, gibt es den Befehl RMDIR(), der, als Funktion eingesetzt, wiederum im Erfolgsfall 0 und bei einem Fehler -1 zurückgibt. Ein Order kann nur gelöscht werden, wenn er leer ist, und er wird auch nicht in den Papierkorb verschoben, sondern wirklich gelöscht.

Mit MKDIR() und RMDIR() lassen sich hervorragend temporäre Ordner anlegen, die bei Programmende wieder gelöscht werden. Quelltext 17.3 legt in der Mitte des Programmes eine kurze Pause ein, sodass Sie den Zwischenstand in Ihrem Dateibrowser kontrollieren können.

Quelltext 17.3: Temporären Ordner anlegen
IF MKDIR("tempOrdner") THEN
  PRINT "tempOrdner kann nicht angelegt werden - existiert der Ordner bereits?"
  END
END IF
IF CHDIR("tempOrdner") THEN
  PRINT "tempOrdner kann nicht geoeffnet werden. ;("
  END
END IF
' Das Programm befindet sich nun in tempOrdner. Nun koennen die Dateien
' angelegt und beschrieben werden.
DIM AS INTEGER dateiNr = FREEFILE
OPEN "test.dat" FOR OUTPUT AS #dateiNr
CLOSE #dateiNr
PRINT "Das Anlegen der temporaeren Dateien ist abgeschlossen."
PRINT "Druecken Sie eine beliebige Taste, um fortzufahren."
GETKEY
KILL "test.dat"
CHDIR ".."
IF RMDIR("tempOrdner") THEN
  PRINT "tempOrdner kann nicht geloescht werden. ;("
ELSE
  PRINT "tempOrdner wurde wieder erfolgreich geloescht."
END IF
SLEEP

17.2 Dateifunktionen

QuickBASIC stellte mit NAME zur Umbenennung von Dateien nur noch einen weiteren Dateien-Befehl zur Verfügung. Inspiriert von Visual Basic wurden in FreeBASIC noch weitere Funktionen eingebaut, etwa um die Existenz einer Datei zu überprüfen oder Dateiattribute auszulesen. Da diese Befehle nicht im QuickBASIC-Sprachschatz vorkommen, müssen sie dem Compiler zunächst bekannt gemacht werden.

Die Deklarationen der genannten Funktionen sind in der Datei file.bi niedergelegt, die sich im Ordner inc/ des FreeBASIC-Installationsordner befindet. Um sie nutzen zu können, muss diese Datei in das Programm eingebunden werden. Dies geschieht über die Befehlszeile

#INCLUDE "file.bi"

file.bi wird wiederum von vbcompat.bi eingebunden, welche letztendlich eine Sammlung VB-kompatibler Funktionen darstellt. Statt file.bi können Sie also auch vbcompat.bi einbinden:

#INCLUDE "vbcompat.bi"

Das Einbinden „externer Befehle“ mag einen Puristen vielleicht abschrecken. Es handelt sich bei diesen Funktionen jedoch um regulären Sprachbestandteil von FreeBASIC, nur dass die Namen im Programmcode nicht von vornherein bekannt sind.2 In vielen anderen Programmiersprachen ist das selektive Einbinden der benötigten Befehle im Übrigen vollkommen üblich.

17.2.1 Dateien umbenennen und verschieben: NAME()

Mit NAME() wird eine Datei umbenannt. Die Funktion nimmt zwei Strings als Argumente entgegen, nämlich den ursprünglichen Dateinamen und den neuen Namen, in den umbenannt werden soll. Als Funktion verwendet, liefert NAME() wie gewohnt 0, wenn kein Fehler auftritt, und -1 bei einem Fehler (z. B. bei fehlenden Schreibrechten oder wenn bereits eine Datei mit dem neuen Namen existiert).

Quelltext 17.4: Dateien umbenennen und verschieben
' Benenne die Datei 'alterName.txt' zu 'neuerName.txt' um
' ohne Erfolgskontrolle
NAME "alterName.txt", "neuerName.txt"

' Verschiebe die Datei TODO.doc vom Unterordner 'zuErledigen' in den
' Unterordner 'erledigt', ohne sie dabei umzubenennen
IF NAME ("zuErledigen/TODO.doc", "erledigt/TODO.doc") THEN
  PRINT "Fehler: Die Datei konnte nicht verschoben werden."
END IF
SLEEP

Im zweiten Teil des Quelltextes sehen Sie, dass sich auch die Pfadangabe im alten und neuen Dateinamen unterscheiden kann. In diesem Fall versucht das Programm, die Datei zu verschieben. Wenn sich zudem auch der Dateiname unterscheidet, wird mit der Verschiebung auch eine Umbenennung durchgeführt. Umgekehrt bedeutet das aber auch: Wenn Sie eine Datei umbenennen wollen, die sich nicht im Arbeitsverzeichnis befindet, müssen Sie sowohl im alten als auch im neuen Namen den Pfad angeben, da die Datei sonst verschoben wird.

17.2.2 Dateien kopieren: FILECOPY()

Mit dem QuickBASIC-Sprachumfang war das Kopieren einer Datei nur möglich, indem sie komplett eingelesen und die Werte in eine neue Datei geschrieben wurden. FreeBASIC greift zu diesem Zweck die Funktion FILECOPY() auf. Auch diesem Befehl wird der Quell- und der Zielname übergeben, und als Funktion aufgerufen gibt er im Erfolgsfall 0 zurück. Etwas ungewöhnlich ist, dass bei einem Fehler 1 zurückgegeben wird und nicht -1.

Quelltext 17.5: Datei kopieren
' Kopiere die Datei 'test.bas' in einen temporaeren Unterordner.
' Dort kann sie bearbeitet werden, ohne die originale Datei zu beeinflussen.

' Notwendige INCLUDE-Datei einbinden
#INCLUDE "vbcompat.bi"

' tempOrdner anlegen, falls er noch nicht existiert. Der Ordner soll am Ende nur
' geloescht werden, wenn er vor Programmstart noch nicht existierte - daher muss
' gespeichert werden, ob das Anlegen erfolgreich war.
DIM AS BOOLEAN tempOrdnerExistierte = MKDIR("tempOrdner")

' Datei kopieren
IF FILECOPY("test.bas", "tempOrdner/test.bas") = 0 THEN
  ' jetzt kann die neue Datei bearbeitet werden
  KILL "tempOrdner/test.bas"
END IF

' ggf. tempOrdner wieder loeschen
IF NOT tempOrdnerExistierte THEN RMDIR "tempOrdner"
SLEEP

17.2.3 Dateiinformationen abrufen: FILEATTR(), FILELEN() und FILEDATETIME()

Über eine geöffnete Datei können mit FILEATTR() verschiedene Informationen abgerufen werden:

Rueckgabe = FILEATTR(Dateinummer[, Ausgabe])

Dateinummer ist dabei die Nummer, unter der die Datei mit OPEN geöffnet wurde. Als Ausgabe kann einer der Werte 1, 2 oder 3 angegeben werden. Statt sich jedoch eine recht nichtssagende Nummernaufteilung merken zu müssen, stellt file.bi Konstanten zur Verfügung, die stattdessen verwendet werden können. Wird Ausgabe nicht angegeben, verwendet FreeBASIC den Wert fbFileAttrMode.

Ausgabe zurückgegebene Information mögliche Rückgabe

fbFileAttrMode

Dateimodus, unter dem die

fbFileModeInput

Datei geöffnet wurde

fbFileModeOutput

fbFileModeRandom

fbFileModeAppend

fbFileModeBinary

fbFileAttrHandle

Handle für den Zugriff

(Handle)

fbFileAttrEncoding

Codierungsvariante, mit der

fbFileEncodASCII

die Datei behandelt wird

fbFileEncodUTF8

fbFileEncodUTF16

fbFileEncodUTF32

Der Rückgabewert für die Ausgabeoption fbFileAttrHandle ist der Handle, über den das System auf die Datei zugreift. Er ist identisch mit dem, den auch die C-Runtime-Library verwendet. Der Handle kann damit an andere C-Funktionen übergeben werden. Dieses Thema führt hier allerdings zu weit. Zu den anderen Optionen folgt ein Beispiel in Quelltext 17.6.
Kann eine Information nicht abgefragt werden, z. B. weil die angegebene Dateinummer nicht belegt ist, so ist der Rückgabewert 0.

Man könnte sich nun fragen, wozu der Dateimodus oder die Codierungsvariante einer geöffneten Datei abgefragt werden muss, wenn man diese Informationen doch selbst über OPEN festgelegt hat. Allerdings kann ein Programm in mehrere Teile gegliedert sein, die unabhängig voneinander agieren. Das einfachste Szenario dazu ist ein Unterprogramm, dem die Dateinummer einer Datei übergeben wird, mit der es arbeiten soll (das Unterprogramm muss dann etwa den Namen der Datei nicht kennen, sondern nur den „Einhängepunkt“). Über FILEATTR() kann es nun beispielsweise die Codierungsvariante ermitteln und so sicherstellen, dass die Daten in korrekter Form gelesen bzw. geschrieben werden.

Zur Veranschaulichung der Syntax hier noch ein (minimal geändertes) Beispiel aus der deutschsprachigen FreeBASIC-Referenz:

Quelltext 17.6: Dateiattribute abfragen
#INCLUDE "vbcompat.bi"
DIM f AS INTEGER = FREEFILE
OPEN "test.bas" FOR INPUT AS #f

SELECT CASE FILEATTR(f, fbFileAttrMode)
CASE fbFileModeInput
   PRINT "Die Datei wurde im INPUT-Modus geoeffnet"
CASE fbFileModeOutput
   PRINT "Die Datei wurde im OUTPUT-Modus geoeffnet"
CASE fbFileModeAppend
   PRINT "Die Datei wurde im APPEND-Modus geoeffnet"
CASE fbFileModeRandom
   PRINT "Die Datei wurde im RANDOM-Modus geoeffnet"
CASE fbFileModeBinary
   PRINT "Die Datei wurde im BINARY-Modus geoeffnet"
END SELECT

PRINT "Das System-Handle zur Datei ist"; FILEATTR(f, fbFileAttrHandle)

SELECT CASE FILEATTR(f, fbFileAttrEncoding)
CASE fbFileEncodASCII
   PRINT "Die Datei ist als ASCII-Datei geoeffnet"
CASE fbFileEncodUTF8
   PRINT "Die Datei ist als UTF-8-Datei geoeffnet"
CASE fbFileEncodUTF16
   PRINT "Die Datei ist als UTF-16-Datei geoeffnet"
CASE fbFileEncodUTF32
   PRINT "Die Datei ist als UTF-32-Datei geoeffnet"
END SELECT

CLOSE #f
SLEEP
Ausgabe:
Die Datei wurde im INPUT-Modus geoeffnet
Das System-Handle zur Datei ist 1971013216
Die Datei ist als ASCII-Datei geoeffnet

Für FILELEN() und FILEDATETIME() ist es dagegen nicht erforderlich, die Datei vorher zu öffnen. FILELEN() gibt die Länge einer Datei in Byte an, FILEDATETIME() liefert den Zeitpunkt, an dem die Datei das letzte Mal geändert wurde. Dieser Zeitpunkt wird als Serial Number zurückgegeben, auf die Kapitel 18 ausführlich eingeht. Auch die formatierte Ausgabe einer Serial Number mittels FORMAT() wird dort ausführlich besprochen.

#INCLUDE "vbcompat.bi"
DIM AS STRING dateiname = "C:\Programme\FreeBASIC\fbc.exe"
PRINT "Die Datei " & dateiname & " ist " & FILELEN(dateiname) & " Byte lang."
PRINT "Zeitpunkt der letzten Aenderung: ";
PRINT FORMAT(FILEDATETIME(dateiname), "dd.mm.yyyy, hh:mm:ss")
SLEEP

Wenn die Datei nicht existiert oder keine Zugriffsberechtigung besteht, liefert FILELEN() den Wert 0 zurück. Achtung: Der Rückgabewert 0 kann auch bedeuten, dass die Datei tatsächlich eine Länge von 0 Byte besitzt!

17.2.4 Auf Existenz prüfen: FILEEXISTS()

Wenn Sie einfach nur überprüfen wollen, ob eine Datei existiert, können Sie FILEEXISTS() verwenden. Der Rückgabewert ist -1, wenn die Datei existiert, oder 0, wenn keine Datei mit diesem Namen existiert. Auch wenn es sich beim angegebenen Namen um einen Ordner statt um eine Datei handelt, wird 0 zurückgegeben.

#INCLUDE "vbcompat.bi"
IF FILEEXISTS("suchmich.bas") THEN
  PRINT "Die Datei existiert!"
ELSE
  PRINT "Die Datei existiert nicht!"
END IF
SLEEP

17.2.5 Dateien suchen: DIR()

Bisher war es immer nötig, den genauen Dateinamen der gewünschten Datei zu kennen. Das ist nicht immer möglich. Vielleicht benötigen Sie einmal eine Liste aller PDF-Dateien in Ihrem Ordner, oder Sie wollen alle Dateien löschen, die mit temp_ beginnen. Für die Suche nach Dateien, die einem bestimmten Muster entsprechen, eignet sich DIR().

DIR([Dateiangabe][, Attributnummer[, Rueckgabeattribut]])
  • Dateiangabe ist der String eines Datei- oder Ordnernamens, der auch Wildcards enthalten kann, nämlich * für eine Folge beliebig vieler Zeichen (auch 0) und ? für kein oder ein beliebiges Zeichen. Beispielsweise würde "bsp*.ba?" unter anderem nach den Dateien bsp.bas, bsp123.bak und bsp_neu.ba suchen.
    Dateiangabe kann auch eine Pfadangabe enthalten, dort sind jedoch keine Wildcards erlaubt.

  • Attributnummer erlaubt die Angabe verschiedener Attribute, welche die Dateien erfüllen müssen — etwa ob sie schreibgeschützt sind oder ob es sich um Ordner handelt.

  • In die Variable Rueckgabeattribut werden die Attribute gespeichert, welche für die gefundene Datei tatsächlich vorliegen.

DIR() gibt den Namen der gefundenen Datei zurück. Der Pfadname entfällt bei der Rückgabe. Eine leere Rückgabe zeigt an, dass keine Datei gefunden wurde, die dem Suchmuster entspricht.

Um den Sinn hinter Rueckgabeattribut zu verstehen, müssen wir etwas in die Funktionsweise von DIR() hineinschauen. Attributnummer setzt sich aus einer Kombination verschiedener Werte zusammen, die in der Datei file.bi mit sprechenden Namen belegt wurden:

  • fbReadOnly: schreibgeschützte Datei

  • fbHidden: versteckte Datei

  • fbSystem: Systemdatei

  • fbDirectory: Ordner

  • fbArchive: archivierbare Datei

Außerdem ist fbNormal = fbReadOnly OR fbArchive. Sie können jede gewünschte Kombination von Attributen über eine OR-Verknüpfung herstellen, also z. B. mit fbNormal OR fbHidden OR fbDirectory nach Einträgen suchen, die auch versteckt und/oder Ordner sein können.

Wird Attributnummer nicht angegeben, wird fbNormal verwendet. DIR sucht also nach allen „normalen“ Dateien einschließlich schreibgeschützter und archivierbarer Dateien, aber keine versteckten Dateien oder Ordner. Der Schalter fbReadOnly bedeutet also, dass auch nach schreibgeschützten Dateien gesucht wird, aber nicht nur schreibgeschützte Dateien. Wenn Sie nun lediglich die schreibgeschützten Dateien ermitteln wollen, jedoch nicht die beschreibbaren, müssen Sie die Rückgabe anhand von Rueckgabeattribut filtern, also nachsehen, ob dort fbReadOnly gesetzt ist oder nicht.

Quelltext 17.7: Suche nach einer Datei
#INCLUDE "vbcompat.bi"
DIM AS INTEGER attribute
DIM AS STRING dateiname = DIR("suchmich.*", fbNormal, attribute)
IF dateiname = "" THEN
  PRINT "Datei nicht gefunden"
ELSE
  PRINT "Gefundene Datei: " & dateiname
  IF attribute AND fbReadOnly THEN
    PRINT "Die Datei ist schreibgeschuetzt."
  ELSE
    PRINT "Die Datei kann beschrieben werden."
  END IF
END IF
SLEEP

DIR() benötigt zwar keine Einbindung von file.bi bzw. vbcompat.bi, die verwendeten Attributvariablen fbNormal und fbReadOnly dagegen schon.
Die Umsetzung der Dateiattribute wird unter den verschiedenen Betriebssystemen unterschiedlich gehandhabt. Beispielsweise markiert Windows versteckte Dateien über ein spezielles Dateienattribut, während Linux alle Dateien als versteckt behandelt, die mit einem Punkt beginnen. Windows stellt eine Option zur Verfügung, Dateien als nicht archivierbar zu kennzeichnen, was dann aber bei einem Schreibzugriff großzügig annulliert wird; Linux dagegen unterstützt dieses Attribut nicht … Im Zweifelsfall müssen Sie mit den Attributen etwas herumexperimentieren.

Nun hat DIR() in Quelltext 17.7 zwar Wildcards unterstützt, aber nur eine einzige Datei zurückgeliefert. Allerdings wird das letzte Suchmuster immer intern gespeichert. Jetzt kann DIR() erneut aufgerufen werden, diesmal jedoch mit einem Leerstring als Dateiangabe. Dadurch liefert es den nächsten passenden Treffer. Diesen „leeren“ Aufruf können Sie auch in eine Schleife packen und damit alle passenden Treffer nacheinander abarbeiten. Wenn Sie Rueckgabeattribut nutzen wollen, müssen Sie das erneut angeben (ansonsten wird der Wert nicht aktualisiert). Dateiangabe und Attributnummer entfällt jedoch und wird aus der letzten Abfrage übernommen.

Quelltext 17.8: Auflisten aller normalen Dateien und Ordner
#INCLUDE "vbcompat.bi"
DIM AS INTEGER attribute
DIM AS STRING dateiname = DIR("*.*", fbNormal OR fbDirectory, attribute)
DO WHILE LEN(dateiname)             ' solange ein Eintrag gefunden wurde
  ' Kennzeichne Ordner durch einen Stern
  IF attribute and fbDirectory THEN
    PRINT "* ";
  ELSE
    PRINT "  ";
  END IF
  ' Gib den Dateinamen (bzw. Ordnernamen) aus
  PRINT dateiname
  ' Suche nach dem naechsten Eintrag
  dateiname = DIR(attribute)
LOOP
SLEEP

Wenn Sie auf die Auswertung von Rueckgabeattribut verzichten, können Sie sich am Ende der Schleife sogar einfach mit einem dateiname = DIR begnügen.

17.3 Externe Programme starten

Externe Dateien können Sie nicht nur zum Lesen und Schreiben öffnen, Sie können sie auch ausführen — jedenfalls wenn es sich um eine ausführbare Datei handelt. Wie in BASIC üblich, gibt es dazu gleich mehrere Befehle, die sich im Detail unterscheiden und die in erster Linie aufgrund der Abwärtskompatibilität beibehalten wurden.

17.3.1 CHAIN(), EXEC() und RUN()

' Drei verschiedene Varianten, ein externes Programm zu starten
Rueckgabewert = CHAIN(Programm)
Rueckgabewert = EXEC(Programm, Argumente)
Rueckgabewert = RUN(Programm[, Argumente])

Die drei Funktionen CHAIN(), EXEC() und RUN() übergeben jeweils die Kontrolle an ein anderes Programm und startet dieses. Programm ist ein String mit dem Dateinamen des zu startenden Programmes (eventuell mit Pfadangabe). Argumente ist ein String, mit den Argumenten, die an das Programm übergeben werden sollen. Während CHAIN() keine Argumentübergabe erlaubt, wird sie von EXEC() erzwungen — Sie können aber auch einen Leerstring "" übergeben. Bei RUN() ist die Übergabe der Argumente optional. Der Rueckgabewert der drei Funktionen ist der Errorlevel, der vom aufgerufenen Programm zurückgegeben wurde, oder -1, falls das Programm nicht aufgerufen werden konnte. (Beachten Sie aber, dass auch der zurückgegebene Errorlevel -1 sein könnte!)

RUN() unterscheidet sich von den beiden anderen Funktionen dadurch, was nach der Ausführung des aufgerufenen Programmes weiter passiert. Während bei CHAIN() und EXEC() anschließend die Kontrolle an das aufrufende Programm zurückgegeben wird und dieses normal weiterläuft, beendet RUN nach erfolgreicher Ausführung und gibt die Kontrolle an das Betriebssystem zurück. Entsprechend können Sie bei RUN() auch nur den Rückgabewert -1 bei missglücktem Aufruf erhalten — bei einem erfolgreichen Aufruf haben Sie ja anschließend keine Möglichkeit mehr, die Rückgabe auszuwerten.

Sie sehen, dass die Unterschiede zwischen den drei Funktionen sehr gering sind. CHAIN() kann problemlos durch EXEC() ersetzt werden, und auch RUN() unterscheidet sich von EXEC() im Prinzip nur durch das Verhalten nach Beendigung des aufgerufenen Programmes.

Natürlich können Sie auch in Ihren eigenen Programmen am Ende ein Errorlevel setzen. Dies geschieht ganz einfach mit der Anweisung END, gefolgt von einem Integerwert. In Kapitel 17.4 wird dazu ein Beispiel präsentiert.

Tip Kompatibilität zu älteren BASIC-Dialekten:
Neben END können auch die Befehle SYSTEM und STOP verwendet werden, um ein Programm zu beenden. Beide Befehle arbeiten identisch zu END und bestehen nur aus Kompatibilitätsgründen zu älteren BASIC-Dialekten. Es wird empfohlen, stattdessen END zu verwenden.

17.3.2 SHELL()

Rueckgabe = SHELL(Kommando)

Mit SHELL() steht noch eine vierte Funktion zur Verfügung. Diese unterscheidet sich jedoch tatsächlich etwas von den anderen, denn zum einen wird ihr statt eines Programmes ein Shell-Befehl übergeben (der seinerseits aber auch ein Programmname sein kann), zum anderen findet eine Parameterübergabe innerhalb dieses Befehls statt. SHELL() ist ungleich mächtiger als die zuvor genannten Befehle, da über den Shell-Befehl z. B. auch Programmaufrufe verknüpft und Ausgaben umgeleitet werden können.

Beachten Sie, dass sich zwei nacheinander ausgeführte SHELL()-Aufrufe nicht gegenseitig beeinflussen! Sie können beispielsweise als Kommando auch einen Ordnerwechsel (cd) durchführen; dieser ist in einem folgenden SHELL()-Aufrufe aber nicht mehr vorhanden. Dazu ein einfaches Beispiel — der Befehl cd wechselt das Verzeichnis und dir listet den Inhalt des Verzeichnisses auf.

Quelltext 17.9: Ordnerinhalt anzeigen mit SHELL()
' Inhalt des uebergeordneten Verzeichnisses anzeigen

' So funktioniert es nicht:
SHELL "cd .."          ' Verzeichniswechsel
SHELL "dir"            ' Ordnerinhalt auflisten
SLEEP

' So klappt es aber:
SHELL "cd .. && dir"   ' beides hintereinander
SLEEP

Natürlich kann hier nicht ansatzweise auf die Möglichkeiten der Shell-Kommandos eingegangen werden. Es ist jedoch lohnenswert, sich näher damit zu beschäftigen, da mit den zur Verfügung stehenden Werkzeugen Programme effizient miteinander verknüpft werden können.

17.4 Kommandozeilenparameter auswerten

In den vorigen Abschnitten wurde gezeigt, wie man ein Programm zusammen mit Argumenten aufrufen kann. Das hat denselben Effekt wie wenn Sie das Programm von Konsole aus starten und dabei die Argumente hinter dem Programmnamen aufführen. Bei dieser Eingabe spricht man von einer Kommandozeile. Der Programmablauf kann dann an die übergebenen Parameter angepasst werden. Ein Beispiel dafür ist der FreeBASIC-Compiler: Beim Aufruf werden die benötigten Informationen — insb. welche Datei(en) kompiliert werden soll(en) — übergeben. Auch wenn Sie Ihre Programme ausschließlich über die IDE compilieren, nutzen Sie damit bereits intensiv den Vorteil der Kommandozeilenparameter, denn die IDE teilt dem Compiler über die Kommandozeile alle nötigen Informationen zum Compiliervorgang mit.

Die Kommandozeilenparameter sind natürlich nur dann sinnvoll, wenn sie vom aufgerufenen Programm auch abgefragt werden können. In FreeBASIC kann das über die Funktion COMMAND() erfolgen. COMMAND() kann mit und ohne Parameter aufgerufen werden, wobei sich die Bedeutung etwas unterscheidet.

  • COMMAND (ohne Parameter) liefert einen String mit allen Kommandozeilenparametern. Dasselbe geschieht bei Übergabe eines negativen Parameters.

  • COMMAND(0) gibt den im Aufruf angegebenen Pfad und Dateinamen zurück.

  • COMMAND(i) gibt für einen positven Wert i das i-te Argument der Kommandozeilenparameter zurück.

Die Anzahl der übergebenen Argumente kann auch mit dem Symbol __FB_ARGC__ abgefragt werden. __FB_ARGC__ beginnt mit der Zählung beim Argument 0 (dem Programmnamen), d. h. wenn Sie neben dem Programmnamen noch weitere drei Argumente übergeben, hat __FB_ARGC__ den Wert 4.

Das folgende Beispiel besteht aus zwei Programmen: das erste ruft das zweite mit einer Argumentenliste auf, die vom zweiten der Reihe nach ausgewertet werden. Beide Programme müssen in compilierter Form im selben Ordner vorliegen, und der Name des zweiten Programmes muss mit dem Namen im SHELL-Aufruf des ersten Programmes übereinstimmen.

Hinweis: Wenn Sie im Dateinamen des übergebenen Programmes die Dateierweiterung weglassen, versucht Windows zunächst, eine Datei dieses Namens ohne Dateiendung zu finden. Scheitert dies, wird nach einer Datei mit passender Endung wie .exe oder .bat gesucht. Unter Linux besitzen Programme in der Regel keine Endung, und auch die von fbc erzeugten Programme sind dort ohne Dateiendung. Quelltext 17.10 verwendet deshalb ebenfalls keine Erweiterung, sodass die Quellcodes auf beiden Systemen funktionieren.
Beachten Sie in Quelltext 17.10 außerdem die Pfadangabe "./" vor dem Namen des aufzurufenden Programmes. In Linux-Systemen können ausführbare Dateien, die nicht in einem der speziellen Systemordner liegen, aus Sicherheitsgründen nur mit Pfadangabe aufgerufen werden (so wird verhindert, dass ein Systemprogramm von einem lokalen Script gleichen Namens überlagert wird). Der Pfad "./" weist explizit darauf hin, dass sich die ausführbare Datei im aktuellen Arbeitsverzeichnis befindet.

Quelltext 17.10: COMMAND() - aufrufendes Programm
' Programm "shell_1.bas"
' ruft das Programm "shell_2" auf
DIM AS INTEGER rueckgabe

rueckgabe = SHELL("./shell_2 shell param1 param2 endParam")
PRINT "Rueckgabe des SHELL-Befehl: " & rueckgabe
PRINT
rueckgabe = EXEC("./shell_2", "exec param1 param2 endParam")
PRINT "Rueckgabe des EXEC-Befehl: " & rueckgabe

SLEEP
Quelltext 17.11: COMMAND() - aufgerufenes Programm
' Programm "shell_2.bas"
' wird von "shell_1" aufgerufen
PRINT "========================="
PRINT "Programmstart von shell_2"
SELECT CASE COMMAND(1)
  CASE "shell"
    PRINT "gestartet durch SHELL"
  CASE "exec"
    PRINT "gestartet durch EXEC"
END SELECT

PRINT "Kommandozeile: " & COMMAND
PRINT "Uebergebene Parameter:"
FOR i AS INTEGER = 0 TO __FB_ARGC__-1
  PRINT i & ". Argument: " & COMMAND(i)
NEXT
PRINT "========================="

END 123      ' mit Errorlevel 123 beenden

Wie Sie sehen, können Sie den Programmablauf an den Inhalt der übergebenen Argumente anpassen. Dies geschieht in Quelltext 17.11 in den Zeilen 5-10, auch wenn die dort verwendete Unterscheidung äußerst einfach ist.

Warning Achtung:
Als Errorlevel, das als Parameter zu END angegeben wird, kann zwar ein INTEGER gewählt werden, unter Linux wird aber nur ein Wert von -1 bis 254 verwendet, da hier nur ein UBYTE weitergereicht wird. Sie sollten daher auf andere Errorlevel verzichten.

17.5 Umgebungsvariablen abfragen und setzen

ENVIRON() erlaubt es, eine Umgebungsvariable abzufragen. Beispiele für solche Umgebungsvariablen sind die Variable PATH, die eine Liste aller Pfade enthält, in denen nach ausführbaren Programmen gesucht wird, oder die Variable HOME (Linux) bzw. HOMEPATH (Windows) mit dem Pfad zum persönlichen Verzeichnis des aktuellen Nutzers. Diese Variablen sind „global“ in dem Sinne, dass sie im gesamten Betriebssystem zur Verfügung stehen. Allerdings wird in der Regel für jeden Prozess eine eigene Kopie dieser Umgebungsvariablen angelegt. Eine Änderung wirkt sich damit nur für den eigenen Prozess und für von diesem Prozess gestartete Unterprozesse aus. Ändern können Sie eine Umgebungsvariable (lokal) mit SETENVIRON. Damit können Sie aber auch eigene Umgebungsvariablen anlegen, die dann z. B. in der Befehlszeile von SHELL() genutzt werden können. Auch FreeBASIC selbst legt innerhalb seiner Programme Umgebungsvariablen an, etwa den verwendeten Grafiktreiber.

SETENVIRON erwartet einen String in der Form:

SETENVIRON "Variable=Wert"

Wenn Sie die Umgebungsvariable Variable in SHELL aufrufen wollen, verwenden Sie unter Linux $Variable und unter Windows %Variable% (dazu gleich noch ein Beispiel). Beachten Sie auch hier, dass die Angaben unter Linux case sensitive sind.

Quelltext 17.12 funktioniert aufgrund der gewählten Variablen- und Pfadnamen nur unter Windows, kann aber problemlos an Linux-Systeme angepasst werden.

Quelltext 17.12: ENVIRON() und SETENVIRON
' Benutzerverzeichnis anzeigen
PRINT "Homeverzeichnis: " & ENVIRON("HOMEPATH")

' Datenordner des Programmes zu PATH hinzufuegen
DIM AS STRING meinOrdner = ENVIRON("APPDATA") & "\meinProgramm"
SETENVIRON "PATH=" & ENVIRON("PATH") & ";" & meinOrdner
PRINT ENVIRON("PATH")

' neue Variable anlegen und mit SHELL nutzen
SETENVIRON("meinPfad=C:\Users")
SHELL "cd %meinPfad% && dir"
SLEEP
Tip Unterschiede zu QuickBASIC:
In QuickBASIC dient ENVIRON sowohl zum Abfragen als auch zum Setzen einer Umgebungsvariablen. In FreeBASIC kann mit ENVIRON() nur abgefragt werden.

17.6 Fragen zum Kapitel

  1. Wodurch kann man eine absolute und eine relative Pfadangabe voneinander unterscheiden?

  2. Sie wollen mit NAME eine Datei verschieben und erhalten den Rückgabewert -1. Welche Gründe kann das haben?

  3. Erweitern Sie Quelltext 17.8, sodass es auch versteckte Dateien anzeigt und entsprechend markiert (z. B. durch Änderung der Schriftfarbe in grau). Außerdem soll zu jeder Datei die Dateigröße angezeigt werden.

  4. Schreiben Sie ein Programm, das einem anderen Programm über die Befehlszeile Parameter übergibt, die dort auf sinnvolle Weise ausgewertet werden. Zu dieser Aufgabe gibt es keine Lösung im Buch; Sie können sich jedoch an Quelltext 17.10 und Quelltext 17.11 orientieren.


Fußnoten:
1) Unter Linux werden auch Ordner als (spezielle) Dateien behandelt. Das Buch verwendet der Einfachheit halber den Begriff Datei nicht für Ordner.

2) Ein Grund für diese Vorgehensweise ist die Abwärtskompatibilität. Beispielsweise enthält file.bi die Deklaration von FILEEXISTS zur Überprüfung der Existenz einer Datei. Hätte nun ein Anwender bereits selbst eine Funktion namens FILEEXISTS definiert, wäre sein Programm wegen des Namenskonflikts nicht mehr lauffähig. Daher versucht FreeBASIC, das von QuickBASIC sowieso bereits eine große Menge an Schlüsselwörtern erbt, neue Schlüsselwörter nur sehr sparsam einzuführen.


Kapitel 16: Datei-Zugriff

Inhaltsverzeichnis

Kapitel 18: Datum und Zeit