Michał Wasiak, G1ISI
Biblioteka boost::filesystem wprowadza przenośne udogodnienia w zarządzaniu ścieżkami dostępu do katalogów i plików.
<boost/filesystem.hpp>
wprowadza klasę basic_path i jej pochodne: path i wpath. Poza tym udostępnia wiele pomocnych operacji na plikach i katalogach. Klasa basic_directory_iterator umożliwia niezwykle użytczne iteracje po zawartości katalogów.<boost/filesystem/fstream.hpp>
wprowadza takie same elementy co nagłówek fstream standardowej biblioteki C++, lecz same pliki są identyfikowane przez obiekty basic_path, a nie char *s.Obiekt klasy path może być stworzony w następujący sposób:
path my_path("sciezka/plik.txt");
String podawany jak argument może być ścieżką w ogólnym przenośnym formacie lub w natywnym formacie systemu operacyjnego.
Klasa path posiada konstruktory konwersji z const char* i std::string&, więc pomimo tego, że biblioteka Filesystem używa parametru formalnego path& to i tak dozwolone jest podawanie jako parametrów napisów w stylu C:
remove_all("plik"); create_directory("teczka"); if(!exists("katalog/obraz")) std::cout << "Błędna ścieżka\n";
Ścieżki mogą zawierać odwołanie do bieżącego katalogu używając notacji „.”, a także do nadkatalogu, używając zapisu „..”.
Klasa basic_directory_iterator jest ważnym elementem biblioteki Filesystem. Wprowadza iterator wejściowy po zawartości katalogu z wartością typu basic_path. Jako przykład użycia tego iteratora przedstawiona zostanie funkcja, która rekursywnie wyszukuje plik o podanej nazwie w podanym katalogu i jego podkatalogach. Zwraca wartość bool i ewentualnie ścieżkę do odnalezionego pliku:
namespace bf = boost::filesystem; bool find_file( const bf::path& dir_path, const std::string& file_name, path& path_found ) { if(!exists(dir_path) || !is_directory(dir_path)) return false; directory_iterator iter(dir_path), end_iter; for(; iter != end_iterl ++iter) { if(bf::is_directory(*iter)) { if(find_file(*iter, file_name, path_found)) return true; } else if(iter->leaf() == file_name) { path_found = *iter; return true; } } return false; }
Funkcja najpierw sprawdza czy podana ścieżka w path_dir istnieje i jest katalogiem. Później tworzy obiekt directory_iterator podając ściężkę do konstruktora. Pętla iteruje po wszystkich elementach katalogu i dla każdego napotkanego podkatalogu wywołuje rekursywnie funkcję szukającą. Każdy rozpoznany plik jest porównywany co do nazwy z paramatrem file_name i jeśli znaleziony zostanie plik o podanej nazwie to do path_found kopiowana jest scieżka do tego pliku i zwracana jest wartość 'true'. Jeśli nic nie zostanie znalezione to zwrcana jest wartość 'false'.
Są dwa formaty łańcuchów opisujących ścieżkę dostępu:
„SYS1::DISK1:[DANE.FILMY]”
„c:\dane\filmy”
void copy_file(const Path1& from_fp, const Path2& to_fp);
Funkcja kopiuje zawartość pliku from_fp do pliku to_fp.
void rename(const Path1& from_p, const Path2& to_p);
Funkcja zmienia nazwę pliku from_fp na nową to_p.
bool remove(const Path& p);
Funkcja usuwa plik p, jeśli plik taki istnieje. Zwraca wartość wyrażenia exists(p)
.
bool create_directory(const Path& dp);
Funkcja tworzy katalog dp. Zwraca wartość true, jeśli katalog został utworzony, w przeciwnym razie zwraca wartość false.
uintmax_t file_size(const Path& p);
Funkcja zwraca rozmiar pliku p w bajtach.
std::time_t last_write_time(const Path& p);
Funkcja zwraca czas ostatniej modyfikacji elementu p.
void last_write_time(const Path& p, const std::time_t new_time);
Funkcja ustawia czas ostatniej modyfikacji pliku p na new_time.
template <class Path> typename Path::string_type extension(const Path & p);
Funkcja zwracająca rozszerzenie pliku. Jeśli p.leaf()
zawiera kropkę, to funkcja zwraca podciąg z p.leaf()
od ostatniej kropki aż do ostatniego znaku nazwy. W przeciwnym razie zwraca pusty łańcuch.
template <class Path> typename Path::string_type basename(const Path & p);
Funkcja zwracająca nazwę pliku bez rozszerzenia. Jeśli p.leaf()
zawiera kropkę, to funkcja zwraca podciąg z p.leaf()
od początku nazwy do ostatniej kropki (kropka nie jest zawarta). W przeciwnym wypadku zwraca p.leaf()
.
template <class Path> Path replace_extension(const Path & p, const typename Path::string_type & new_extension);
Funkcja zmienia rozszerzenie pliku p na nowe new_extension.
Prosty program wypisujący zawartość katalogu:
namespace bf = boost::filesystem; void listing(const bf::path& p) { unsigned long dir_count = 0; unsigned long file_count = 0; unsigned long other_count = 0; unsigned long err_count = 0; if(!bf::exists(p)) { std::cout << "Nieprawidlowa sciezka: " << p.native_file_string() << std::endl; return; } if(bf::is_directory(p)) { std::cout << "W katalogu: " << p.native_file_string() << "\n\n"; bf::directory_iterator iter(p), end_iter; for(; iter != end_iter; ++iter) { try { if(bf::is_directory(*iter)) { ++dir_count; std::cout << iter->leaf() << " [katalog]\n"; } else if(fs::is_regular(iter->status())) { ++file_count; std::cout << iter->leaf() << " (" << bf::file_size(*iter) <<" B)\n"; } else { ++other_count; std::cout << iter->leaf() << " [inny]\n"; } } catch(const std::exception& ex) { ++err_count; std::cout << iter->leaf() << ": " << ex.what() << std::endl; } } std::cout << std::endl << file_count << " plikow\n" << dir_count << " katalogow\n" << other << " innych\n" << err_count << " błedów\n"; } else { // musi byc plikiem std::cout << "Plik: " << p.native_file_string() << std::endl; } }
Przy całym dobrodziejstwie niesionym przez bibliotekę Filesystem trzeba także pamiętać o pewnych pułapkach czyhających na programistę. Otóż podczas wykonywania funkcji może wystąpić wyścig, a wtedy wartość zwracana przez funkcję może ulec zmianie do czasu powrotu do funkcji wywołującej.
Pliki lub katalogi są często współdzielone i przez to ich stan może zostać zmieniony prze inny wątek, proces, a nawet inny komputer posiadający sieciowy dostęp do systemu plików. Oto kilka przykładów pokazujących istotę problemu:
assert(exists("foo") == exists("foo"));
W tym przypadku może się zdarzyć, iż nieistniejący plik bądź katalog zostanie utworzony lub istniejący zostanie usunięty pomiędzy pierwszy a drugim wywołaniem exists(). Taka sytuacja może nastąpić, jeśli podczas wykonania przykładowego kodu inny wątek, proces lub komputer także operuje w tym samym folderze.
remove_all("foo"); assert(!exists("foo"));
Taka próba nie powiedzie się, jeśli między wywołaniem remove_all() a wywołaniem exists() inny wątek, proces lub komputer stworzy plik bądź katalog o nazwie „foo”.
assert(is_directory("foo") == is_directory("foo"));
Zawiedzie, w przypadku gdy inny wątek, proces badź też komputer pomiędzy dwoma wywołaniami is_directory() usunie istniejący plik „foo” i stworzy katalog o nazwie „foo”.