Aus Linux-Magazin 02/2005

Dateisystemprogrammierung in C++ mit Boost und More

Die C++-Standardlibrary deckt viele Anwendungsbereiche nicht ab, sodass Programmieraufgaben viele Zusatzbibliotheken erfordern. Die beiden Bibliotheken Boost und More helfen dem Entwickler, denn ihr Funktionsumfang ist riesig. Dateiverarbeitung ist für beide eine Spezialdisziplin.

Die C++-Standardlibrary bringt zwar Dateifunktionen mit, doch deren Funktionalität ist eingeschränkt. Zum Beispiel können sie nicht mit Verzeichnissen umgehen. Das Boost-Projekt[1] bietet mit der Filesystem-Bibliothek[2] eine Erweiterung der C++-Standardlibrary an, die diese Lücke schließt (siehe Kasten “Boost-Projekt”).

Boost::Filesystem ist rund um die Klasse »path« aufgebaut, die Pfade repräsentiert. Die Pfade lassen sich in einer plattformunabhängigen Syntax ausdrücken, um Inkompatibilitäten zwischen den Pfadbezeichnungen verschiedener Betriebssysteme zu umgehen. Die Syntax orientiert sich an der bei Posix-Systemen üblichen, der auch Linux folgt. Eine Beschreibung der Pfadsyntax in EBNF-Grammatik ist unter[7] zu finden. Die Library selbst verwendet den Namespace »boost::filesystem«.

Der Umgang mit der »path«-Klasse ist recht einfach: Man übergibt dem Konstruktor den Pfadstring in der Boost-Syntax. Die Klasse bietet mehrere Funktionen, um mit dem Pfadstring zu arbeiten, und außerdem Funktionen, um ihn in die plattformabhängige Variante umzuwandeln. Alle Funktionen, die eine Pfadangabe benötigen, nehmen diese in Form eines »path«-Objekts an. So bietet »boost::filesystem::basic_fstream« die Funktionalität der C++-Standardklasse »fstream«, erwartet aber statt eines Strings ein »path«-Objekt.

»ls« mit Boost

Wer statt der Boost-Pfadsyntax die native Systemsyntax verwenden will, übergibt dem Konstruktor als zweites Argument das Objekt »boost::filesystem::native«. Neuere Versionen der Library setzen mit der statischen Member-Funktion »boost::filesystem::path::default_name_check« diese Einstellung als Default. Als Beispiel dafür, Informationen aus einem Dateisystem auszulesen, dient hier eine einfache Implementation von »ls« (siehe Listing 1). Es erfordert zusätzlich zur »path«-Klasse noch eine Funktion, die prüft, ob ein Pfad existiert: »exists()«. Sie erwartet ein »path«-Objekt als Argument und liefert einen entsprechenden »bool«-Wert zurück, je nachdem, ob der Pfad existiert oder nicht.

Der Code ist leicht zu verstehen. Das Programm prüft die Anzahl der Argumente (Zeile 13) und bricht gegebenenfalls mit einer Fehlermeldung ab. Für jedes übergebene Argument erzeugt das Beispiel ein Objekt »boost::filesystem:: path«, das einen nativen Pfadstring erwartet (Zeile 15). Damit ruft es die Funktion »ls« auf, die dann mit »boost:: filesystem::exists()« prüft, ob der Pfad existiert (Zeile 6).

Dem Ergebnis dieser Prüfung entsprechend zeigt das Programm entweder an, dass der Pfad nicht existiert, oder es gibt den Pfadnamen aus, siehe Abbildung 1. Dabei wandelt die Member-Funktion »native_file_string()« den Namen in das für die aktuelle Plattform übliche Format um (Zeile 9).

In dieser Version ist das Ls-Tool aber noch wenig hilfreich. Interessanter wäre es zum Beispiel, den Inhalt von Verzeichnissen auszugeben.

Boost-Projekt

Das Boost-Projekt[1] ist eine Sammlung von Open-Source-Libraries, die die C++-Standardlibrary um fehlende Features erweitert. Das Projekt ist in der Community sehr bekannt, Teile des Projekts werden den Weg in den nächsten C++-Standard finden[3]. Die Boost-Libraries sind sehr gut designt und nutzen viele C++-Techniken.

Alle Bibliotheken laufen unter Posix-kompatiblen Systemen (Unix, Linux) und Windows. Die Firma Boost Consulting[4] bietet kommerziellen Support für die Bibliotheken. Eine genaue Liste der Libraries findet sich unter[5], eine ausführliche Installationsanleitung ist unter[6] nachzulesen.

Abbildung 1: Das Boost-Programm aus Listing 1 implementiert ein einfaches »ls«.

Abbildung 1: Das Boost-Programm aus Listing 1 implementiert ein einfaches »ls«.

Abbildung 2: Das More-Beispiel aus Listing 3 gibt Details einzelner Dateien aus.

Abbildung 2: Das More-Beispiel aus Listing 3 gibt Details einzelner Dateien aus.

Iteratoren eingebaut

Dafür kennt Boost die Klasse »directory _iterator«, die einen Iterator implementiert, der kompatibel zu den Iteratoren aus der C++-Standardlibrary ist. Dem Konstruktor kann man ein existierendes »path«-Objekt übergeben und erhält so den Anfangs-Iterator. Ohne Argument liefert die Funktion den End-Iterator. Wie üblich schaltet der Inkrement-Operator »++« den Iterator auf den nächsten Pfad weiter. Der Dereferenzier-Operator »*« gibt in diesem Fall ein »path«-Objekt zurück.

Damit lässt sich der Code des Ls-Tools relativ leicht erweitern, siehe Listing 2. Zusätzlich prüft die folgende neue Version, ob der Pfad ein Verzeichnis ist, da sonst eine Iteration sinnlos ist. Dazu dient die einfache Funktion »boost::filesystem::is_directory()«. Die Pfade, die die Iteratoren liefern, übergibt das Programm an einen rekursiven Aufruf der Funktion »ls()«. Das kann, je nachdem, wie tief die Pfade verschachtelt sind, zu einer riesigen Ausgabe führen. In diesem Fall sollte man besser den rekursiven Aufruf durch eine einfache Ausgabe des Pfads ersetzen.

Exceptions gegen Fehler

Erfolgt der Aufruf des Programms mit dem Pfad ».«, bricht es mit der Meldung »Abgebrochen« ab, was auf eine nicht gefangene Exception hinweist. Die Boost::Filesystem-Library wirft Exceptions, die von dem Standard-Exception-Typ »std::exception« abgeleitet sind, was das Fangen extrem vereinfacht. So lässt sich die Funktion »ls()« leicht mit folgendem Code absichern:

void ls(path &pfad) {
  try {
    // hier der Code der ls-Funktion
  }
  catch(std::exception &e) {
    std::cerr << "Error: " << e.what()
     << 'n';
  }
}

Das Standard-Ls unter Linux gibt im Gegensatz zum selbst programmierten Beispieltool ohne weitere Parameter den Inhalt des aktuellen Pfads aus. Wer das erreichen will, tauscht die Ausgabe der Fehlermeldung in der »main()«-Funktion aus und ruft die Funktion »ls()« mit dem Rückgabewert der Funktion »boost::filesystem::current_path()« auf, die den aktuellen Pfad zurückgibt:

int main(int argc,char **argv) {
  if(argc > 1)
    for(int i=1;i<argc;++i)
      ls(argv[i]);
  else
    ls(current_path());
}

In der letzten Zeile steht nur »current_ path()«, da im Programm der Namespace »boost::filesystem« festgelegt ist.

Dateisystem manipulieren

Boost::Filesystem bietet auch Funktionen, um das Dateisystem zu verändern. So legt »void create_directory(path const &directory_path)« ein Verzeichnis an. Die Funktion »bool remove(path const &ph)« löscht den übergebenen Pfad und behandelt dabei Symlinks korrekt. Sie entfernt also nicht den Pfad, auf den der Symlink zeigt, sondern nur den Link selbst. Der Rückgabewert vom Typ »bool« gibt an, ob das Löschen erfolgreich war. Jene Verzeichnisse, die noch Dateien enthalten, lassen sich nicht mit »remove()« löschen. Dazu dient »unsigned long remove_all(path const &ph)«. Der Rückgabewert gibt die Anzahl gelöschter Pfade an.

Listing 1: »ls« mit
Boost (1)

01 #include <boost/filesystem/path.hpp>
02 #include <boost/filesystem/operations.hpp>
03 #include <iostream>
04 
05 void ls(boost::filesystem::path const &pfad) {
06   if(!boost::filesystem::exists(pfad))
07     std::cerr << "Pfad '" << pfad.native_file_string() << "' existiert nichtn";
08   else
09     std::cout << pfad.native_file_string()  << 'n';
10 }
11 
12 int main(int argc,char **argv) {
13   if(argc > 1)
14     for(int i=1;i<argc;++i)
15       ls(boost::filesystem::path(argv[i], boost::filesystem::native));
16   else {
17     std::cerr << "usage: " << *argv << "  <pfad> ...n";
18     return 1;
19   }
20 }

Listing 2: »ls« mit
Boost (2)

01 using namespace boost::filesystem;
02 void ls(path &pfad) {
03   if(!exists(pfad))
04     std::cerr << "Pfad '" << pfad.native_file_string() 
05       << "' existiert nicht" << std::endl;
06   else {
07     std::cout << pfad.native_file_string() << 'n';
08     if(is_directory(pfad))
09     {
10       directory_iterator end;
11       for(directory_iterator i(pfad);
12           i!=end;
13           ++i)
14         ls(*i);
15     }
16   }
17 }

Die Funktion »void rename(path const &from_path, path const &to_path)« benennt Dateien und Verzeichnisse um. Das erste Argument ist der Quell-, das zweite der Zielpfad. Kopieren funktioniert nach dem gleichen Muster mit der Funktion »void copy_file(path const &source_file, path const &target_file)«. Die Rechte- und Benutzer-Verwaltung von Unix-Dateisystemen deckt Boost:: Filesystem leider nicht ab.

Tabelle 1:
More-Funktionen

 

Funktion

Beschreibung

bool rename_node(std::string const& oldpath,std::string
const& newpath)

Benennt die Datei oder das Verzeichnis »oldpath« in
»newpath« um; diese Funktion besitzt kein rekursives
Gegenstück

bool remove_node(std::string const& path)

Entfernt den Pfad »path«

bool copy_node(std::string const& from,std::string
const& to)

Kopiert einen Pfad »from« (1. Parameter) nach
»to« (2. Parameter)

bool create_dir(std::string path)

Erzeugt ein Verzeichnis

More bietet mehr

Die More-Bibliothek [8] gibt es nur für Posix-Systeme (siehe Kasten “More- Library”). Dafür entstehen aber auch keine Portabilitätsprobleme bei der Rechteverwaltung. Die Library bietet die Klasse »more::io::file_status«, die Informationen über eine Datei, ein Verzeichnis und deren Rechte liefert. More bietet ähnliche Funktionen wie Boost, zusätzlich aber Features wie Dateisperren, temporäre Dateien, Suchfunktionen, die ähnlich wie Grep arbeiten, und noch einiges mehr. Die Installation beschreibt der Kasten “More-Library”.

Im Gegensatz zur Boost::Filesystem-Library behandelt More Pfade einfach als String, weil es ohnehin auf Portabilität verzichtet. Die Klasse »more::io::file_status« liefert Informationen über eine Datei, wie in Listing 3 nachzulesen ist. Abbildung 2 zeigt ein Beispiel der Programmausgabe.

More enthält natürlich auch einen Verzeichnis-Iterator, mit dem sich das Ls-Beispiel nachvollziehen lässt, siehe Listing 4. Die Main-Funktion entspricht dabei ungefähr der des Boost-Beispiels. Die Klasse »more::io::directory« (Zeile 14) verhält sich wie ein STL-Container und stellt die Funktionen »begin()« und »end()« zur Verfügung, die einen Iterator vom Typ »more::io::directory::iterator« zurückgeben (Zeile 15). Zu initialisieren ist die Klasse einfach mit dem Pfadnamen. Der Rest des Code ist leicht verständlich. Zeile 16 prüft noch die speziellen Verzeichnisse »..« und ».«, da die Funktion sonst in eine endlose Rekursionsschleife gelangt. Außerdem sind die Pfad- und Dateinamen relativ zum angegebenen Pfad.

Listing 3: Datei-Infos mit
More

01 #include <iostream>
02 #include <more/io/filesys.h>
03 
04 int main(int argc,char **argv) {
05   if(argc != 2)
06     return 1;
07   more::io::file_status status (argv[1]);
08   if(status.exists())
09     std::cout << argv[1]
10     << "nUid: " << status.uid() 
11     << "nGid: " << status.gid() 
12     << "nAtime: " 
13     << status.atime() 
14     << "nMtime: " 
15     << status.mtime() 
16     << "nFile Type: " 
17     << (status.is_regular_file() 
18         ? "regular file" : 
19         status.is_directory() 
20         ? "directory" : 
21         status.is_symbolic_link() 
22         ? "symbolischer link" : 
23         "unbekannt") 
24     << "nPermissions: " 
25     << std::oct 
26     << status.permissions() 
27     << "nUser Permissions: " 
28     << std::oct 
29     << status.user_permissions() 
30     << std::endl;
31   else
32     return 1;
33 }

Listing 4:

01 #include <more/io/filesys.h>
02 #include <more/io/directory.h>
03 #include <iostream>
04 
05 void ls(std::string const &pfad) {
06   more::io::file_status status(pfad);
07   if(!status.exists())
08     std::cerr << "Pfad '" << pfad
09     << "' existiert nichtn";
10   else
11     std::cout << pfad << std::endl;
12 
13   if(status.is_directory()) {
14     more::io::directory dir(pfad);
15     for(more::io::directory::iterator i = dir.begin();
16        i! = dir.end(); ++i)
17       if(*i! = ".." && *i! = ".") {
18         if(pfad[pfad.size()-1] !='/')
19           ls(pfad+'/' + *i);
20         else
21           ls(pfad+*i);
22       }
23   }
24 }

Dateien und Zeichenketten suchen

More bietet Features, mit denen sich zum Beispiel eine Suche im Dateisystem realisieren lässt. Die Methode »more:: io::file_glob_list()« erwartet als Parameter ein Suchmuster, das auch reguläre Ausdrücke enthalten darf. Ein den STL-Containern ähnliches Interface bietet die Möglichkeit, über die gefundenen Pfade zu iterieren: »more::io::file_glob_list::begin()« liefert einen Iterator für den Anfang der gefundenen Dateien, »more:: io::file_glob_list::end()« einen Iterator für das Ende.

Vor dem Iterieren empfiehlt es sich jedoch zu prüfen, ob das Programm überhaupt Dateien gefunden hat und das Ergebnisobjekt gültig ist. Dabei hilft die Member-Funktion »more::io::file_glob_list::good()«, die prüft, ob ein Fehler aufgetreten ist. Allerdings gibt »good()« unter Umständen auch »true« zurück, wenn nichts gefunden wurde. Um das zu entscheiden, hilft ein Test, ob der erste Iterator auf »end« zeigt. Ein Dereferenzieren des Iterators liefert den Dateinamen als Typ »char const *const«.

Noch einfacher lassen sich mit More Dateien durchsuchen. Die Funktion »more:: io::grep_string()« dient als Basis für ein kleines Grep-Tool (Listing 6). Der erste Parameter ist der Pfad, der zweite ein Suchmuster. Bei Erfolg gibt die Funktion »true« zurück. Sie wendet das Suchmuster allerdings zeilenweise an, weshalb der Anwender auf mehrzeilige Muster verzichten muss.

More-Library

Die Open-Source-Library More[8] erweitert die C++-Standardlibrary um fehlende Features. Dabei überschneidet sie sich zum Teil mit dem Boost-Projekt. More läuft aber nur auf Posix-kompatiblen Systemen (Linux, Unix) und es gibt auch keinen kommerziellen Support.

Installation

Die More-C++-Library lässt sich mit den üblichen Routinen installieren. Folgende Pakete erweitern die Funktionalität der Bibliothek zur Compile-Zeit:

  • Boehm-Demers-Weisers Conservative Garbage Collector[9]: Seinen
    Pfad gibt man explizit bei »configure« mit
    »–with-bdwgc= Dir« an.
  • Foreign Function Interface[10]: Fehlt FFI, ist das erste
    Auslesen des Function Interface langsamer als bei
    On-the-fly-Kompilierung, das weitere Lesen aber schneller. Für
    die hier behandelten Beispiele bringt das Foreign Function
    Interface aber keinen Vorteil.
  • Curl-Library[11]: Sie holt ENSDF-Daten (Evaluated Nuclear
    Structure Data File) aus den entsprechenden Datenbanken im
    Internet. Das ist aber für hier besprochene Anwendungen
    uninteressant.

Der Compile-Prozess der More-C++-Library läuft nach folgendem Schema ab:

$ tar xjf more-0.7.5<$>.tar.bz2
$ cd more-0.7.5<$>
$ sh ./configure
$ make
$ make check
$ su -c 'make install'

Natürlich kennt More auch zahlreiche Funktionen zum Löschen, Kopieren, Umbenennen und Erzeugen von Dateien und Verzeichnissen. Dabei gibt es alle Funktionen als normale und als rekursive Variante, bis auf »rename_node()«. Deren Funktionsnamen enden auf »_rec«, sie geben »bool«-Werte zurück, wobei »true« für Erfolg steht.

Von »remove_node()« und der rekursiven Schwester gibt es zwei Ableger, die das Entfernen des Pfads auf das Programmende verschieben. Die Funktionen heißen »remove_node_at_exit()« und »remove_node_rec_at_exit()«.

Zeitgemäße Helfer

Die vorgestellten Bibliotheken vereinfachen das Arbeiten mit dem Filesystem und bieten ein zeitgemäßes C++-API. Boost::Filesystem besticht zwar durch Plattformunabhängigkeit und ein raffinierteres API , unterstützt aber keine Dateirechte und Suchfunktionen. More bietet jene Features, die man bei Boost vermisst. Das Suchen von Dateien und Strings gestaltet sich problemlos. Selbst komplexe Aufgaben sind damit leicht zu lösen. (ofr)

Listing 5: Dateien suchen mit
More

01 #include <more/io/filesys.h>
02 #include <iostream>
03 
04 int main(int argc,char **argv) {
05   if(argc!=2) {
06     std::cerr << "usage: "
07     << argv[0] << " <file-pattern>"
08     << std::endl;
09     return 1;
10   }
11   more::io::file_glob_list files(argv[1]);
12   if (files.good() && files.begin() != files.end())
13     for(more::io::file_glob_list::const_iterator i = files.begin(); i!=files.end(); ++i)
14       std::cout << *i << 'n';
15   else {
16     std::cerr << "not foundn";
17     return 1;
18   }
19 }

Listing 6: Strings suchen mit
More

01 int main(int argc,char **argv) {
02   if(argc < 3) {
03     std::cerr << "usage: " << argv[0] 
04             << " <search-pattern>" 
05                " <file-name> ...n"; 
06     return 1;
07   }
08   for(int i=2; i < argc; ++i)
09     //erster Parameter ist das Suchmuster
10     //zweiter Parameter die zu durchsuchende Datei
11     if (more::io::grep_string(argv[i], argv[1]))
12       std::cout << argv[i] << 'n';
13 }

Infos

[1] Boost-Projekt: [http://www.boost.org]

[2] Boost::Filesystem-Library: [http://www.boost.org/libs/ filesystem/doc]

[3] C++-Standard Library Technical Report: [http://std.dkuug.dk/jtc1/sc22/wg21/docs/library_technical_report.html]

[4] Boost Consulting: [http://www.boost-consulting.com]

[5] Liste der Boost-Libraries: [http://www.boost.org/libs/libraries.htm]

[6] Boost-Installationsanweisungen: [http://www.boost.org/more/getting_started.html]

[7] Boost-Pfadsyntax in EBNF: [http://www.boost.org/libs/filesystem/doc/path.htm#Grammar]

[8] More-Library: [http://more.sourceforge.net]

[9] Boehm-Demers-Weiser Conservative Garbage Collector: [http://www.hpl.hp.com/personal/Hans_Boehm/gc]

[10] Foreign Function Interface: [http://sources.redhat.com/libffi]

[11] Curl-Library: [http://curl.haxx.se/]

[12] Listings online: [https://www.linux-magazin.de/Service/Listings/2005/02/C++/]

Der Autor

Rüdiger Sonderfeld ist Entwickler freier Software, beschäftigt sich mit verschiedensten Programmiersprachen und moderiert das Forum der Community-Website [http://c-plusplus.de].

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben