Herkese merhabalar, bir aradan sonra (umarım çok da uzun olmayan), yeni bir haftalık C++ yazımız ile birlikteyiz. Bu yazımda, sizler ile birlikte uzun bir süre beklenen (aslında Java ve .Net gibi bir çok platform ile sunulan ama C++ da standart bir şekilde sunulmayan) STL kabiliyeti olan, dosya sistemi kütüphanesine bir göz atacağız.
Bu kütüphane, aslında uzunca bir süredir (yaklaşık 2003) kullanılmakta olan boost::filesystem kütüphanesini baz alıyor diyebiliriz. O kütüphane de oldukça yaygın ve birçok kişi tarafından kullanılıyor, ayrıca merak edenler için de 😉 boost ve standart dosya sistemi kütüphanelerini karşılaştıran bir bağlantı veriyorum. Elbette standart dosya sistemi kütüphanesinin en önemli amacı sizin de tahmin edebileceğiniz üzere bir çok platform için standart kod yazabilmek. Bu yazımda da, kütüphanenin öne çıkan özellikleri üzerinden geçip, bunları örnek kullanımlarla sizlere aktarmaya çalışacağım. Bunun ile birlikte daha detaylı bilgi edinmek için de sizlere ilgili kaynakları sunacağım. Bu arada eğer fırsat bulabilirsem, hazırlayacağım bazı haftalık C++ yazılarında bu kütüphanenin örnek kullanımlarını verebilirim.
İçerik
Giriş:
Stackoverflow yazısında ifade edildiği üzere, bu kütüphaneye yapılan değişikliklere ilişkin bir çok yayın mevcut. Bunlara aşağıdaki bağlantılardan ulaşabilirsiniz:
- P0219R1, göreceli yol desteği eklenmesi,
- P0317R1, directory_entry’ye ön bellek (cache) eklenmesi,
- P0492R2, gelen görüşler ışığında yapılan bir çok hata ve güncellemeler,
- P0430R2, bazı Non-POSIX sistemler için destek,
- P0218R1, nihai yayın.
Kütüphaneye dair güncellemeleri “LWG issue list” listesinden takip edebilirsiniz. Ayrıca C++ standardının diğer parçalarına/kabiliyetlere dair hususları da buradan takip edebilirsiniz.
Ayrıca C++ standart dokümanı içerisindeki 30.10 başlığından da dosya sistemine dair detaylara ulaşabilirsiniz. Fakat dokümanın, oldukça büyük olduğunu ifade etmeliyim 🙂
Yukarıda da bahsettiğim üzere, kütüphane boost::filesystem’e dayanıyor ve kütüphane tarafından sunulan bazı bileşenler de opsiyonel. Bir diğer deyişle her platform ya da derleyici ile kullanılmayabilir (ör. sembolik kısayollar FAT-32 de desteklenmemektedir). Bunların yanında, aşağıda bazı platform (işletim sistemi) ile ilintili hususları da göz önüne almanızda fayda var:
- Dosya karakter duyarlılığı. Ör. “file.txt”, “File.txt” ve “filet.TXT” her ne kadar Windows tabanlı işletim sistemlerinde aynı dosyaya karşılık gelse de Linux gibi POSIX tabanlı işletim sistemlerinde ise üç farklı dosyaya karşılık gelmektedir,
- Göreceli/tam dosya yolu yaklaşımları. POSIX benzeri sistemlerde “/bin” dizini tam dosya dizinine karşılık gelse de, Windows tabanlı işletim sistemlerinde böyle bir yaklaşım hiç bulunmamaktadır,
Kütüphanedeki en önemli kavram dosya/dizin yolu (“path”). Bu aslında herhangi bir dosyanın (aynı zamanda dizinin) konumunu ifade eden etiket olarak nitelendirebiliriz. Tabi bu sadece dosya değil, aynı zamanda dizin, kısa yol, katı bağlantı ya da sembolik bağlantıları da ifade edebilir. Yol kavramının bu anlamda ifade edebileceği bütün değerleri görmek için dosya sistemi kütüphanesi sayfasına bakabilirsiniz. Bu yollar göreceli ya da bütün yolu ifade edebilir. Bazı platformların jenerik ya da kendilerine özel yol formatları da olabiliyor. Ör, Windows üzerinde “/tmp/test.txt” geçerli bir format olarak kabul ediliyor, “\tmp\test.txt” yolu da destekleniyor. Bu ikisi aynı yolu ifade eden platforma özgü gösterimler. Bu tarz gösterimler, platformlardan platforma değişebilmekte. Kütüphane bu tarz platform bağımlı dosya yolu ile daha jenerik gösterimler arasında dönüşüm için de API sunmakta.
Bu doğrultuda bakacak olursak genel yol formatı aşağıdaki gibi özetlenebilir:
The generic path format can be summarized as:
[rootname] [rootdir] [relativepath]
where:
- Burada opsiyonel olan kök ismi (“rootname”), platforma özgüdür (ör. POSIX sistemlerde “//host”, Windows tabanlı sistemlerde “C:” olabilmektedir.),
- Bir diğer opsiyonel öğe kök dizini de dizin ayıracı olarak kullanılıyor,
- Son olarak göreceli yol ise dosya ve dizin isimlerini içeren kısımdır.
Sizin de tahmin edebileceğiniz üzere dosya sistemi kütüphanesi oldukça büyük bir kütüphane ve bütün detaylarını bir yazıda vermek oldukça zor. Burada olabildiğince önemli olan kabiliyetlerin üzerinde durup, daha çok pratik kullanım alanı olan özelliklerini örnek kodlar ile vermeye çalışacağım. Daha fazla detay öğrenmek isterseniz, sizlere kaynaklar kısmındaki Nicolai. M. Jouttis ve diğer C++ 17 kitaplarını önerebilirim.
Dosya sistemi kütüphanesini kullanmak için <filesystem> başlık dosyasını eklemeli ve filesystem:: alanını kullanmalısınız. Kütüphane içerisinde hem sınıflar hem de bağımsız metot şeklinde API’ler bulunmakta. Varsayılan olarak, kütüphane hataları istisna mekanizması (filesystem_error istisna nesnesi) ile kotarıyor (diğer kütüphanelere göre doğası gereği bu tarz durumların çok olabileceğini tahmin edersiniz diye düşünüyorum). İstisna mekanizması yanında isterseniz, ilgili hata kodlarını da parametre olarak dönen API’leri kullanabilirsiniz (std::error_code).
İlgili alan uzayı ve başlık dosyası derleyici ve IDE’nize göre değişiklik gösterebilir, bu konuya son başlıkta (derleyici desteği) değineceğim.
Bu kadar açıklama yeter sanırım, hemen bir örnek ile kütüphaneyi kullanmaya başlayalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <fstream> #include <iostream> #include <string> #include <experimental/filesystem> namespace fs = std::experimental::filesystem; int main() { /// current_path metodu uygulamanın çalışdığı dizin std::cout << "Current path: " << fs::current_path() << std::endl; /// "sandbox/a/b" dizinlerini olustur std::string dir = "sandbox/a/b"; fs::create_directories(dir); std::ofstream("sandbox/file1.txt"); // Sembolik bağlantılar Windows'ta olmadığı için aşağıdaki gibi koruma altına alıyoruz. #ifndef WIN32 fs::path symPath = fs::current_path() /= "sandbox"; symPath /= "syma"; fs::create_symlink("a", "symPath"); std::cout << "fs::symlink(symPath): " << fs::is_symlink(symPath) << std::endl; std::cout << "fs::exists(symPath): " << fs::exists(symPath) << std::endl; #endif std::cout << "fs::is_directory(dir): " << fs::is_directory(dir) << std::endl; // Dizin içeriğini özyineli bir şekilde gösterelim for (auto& p : fs::recursive_directory_iterator("sandbox")) std::cout << p << std::endl; // kök dizin ve altındaki bütün her şeyi silelim fs::remove_all("sandbox"); return 0; } |
Yukarıda görebileceğiniz üzere dizin işlemlerini sunulan bu API’ler ile kolay bir şekilde gerçekleştirebilirsiniz. Peki daha başka neler yapabiliriz:
- Dizin oluşturma/içeriğini izleme,
- Dizin düzenlemeleri ve karşılaştırma,
- Dizin isimlerinin platform özgü ve jenerik dönüşümleri
- Dizin ve dosyalar arasında dolaşma,
- Dosya niteliklerini, durumunu ve yetkileri izleme/değiştirme,
- Dosya işlemleri (oluşturma, silme, kopyalama, taşıma),
- Çok daha fazlası için https://en.cppreference.com/w/cpp/filesystem sayfasına bir göz atabilirsiniz
Yol Nesneleri:
Şimdi en çok kullanılan nesnelerden birisi olan yol nesnelerine göz atalım. Aslında bakarsanız aşağıdaki örnek bu sınıfa ilişkin çoğu örnek kullanımı göstermekte. Bunların bir çoğu günlük kullanımda ihtiyaç duyabileceğiniz kabiliyetler. Hemen bakalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
#include <fstream> #include <iostream> #include <string> #include <time.h> #include <filesystem> using namespace std; namespace fs = std::experimental::filesystem; // İlgili yolu yukarıda bahsettiğim şekilde parça parça göstermek için kullanılan metot. // Görebileceğiniz üzere aslında yol nesnesinin kendisi de bir konteyner void iteratePathContent(const fs::path& initialPath) { int i = 0; for (const auto& part : initialPath) cout << "Path part: " << i++ << " = " << part << '\n'; } // Verilen dosyanın boyutunu hesaplayan metot // Hata durumunda -1 döner uintmax_t calculateFileSize(const fs::path& filePath) { // uintmax_t => en geniş işaretsiz tam sayı uintmax_t totalSize = static_cast<uintmax_t>(-1); if (fs::exists(filePath) && fs::is_regular_file(filePath)) { auto err = error_code{}; auto filesize = fs::file_size(filePath, err); if (filesize != static_cast<uintmax_t>(-1)) { totalSize = filesize; } } return totalSize; } // Verilen dizinin boyutunu hesaplayan metot // Hata durumunda -1 döner uintmax_t calculateDirectorySize(const fs::path& pathToCheck) { auto size = static_cast<uintmax_t>(-1); if (fs::exists(pathToCheck) && fs::is_directory(pathToCheck)) { for (auto const & entry : fs::recursive_directory_iterator(pathToCheck)) { if (fs::is_regular_file(entry.status()) || fs::is_symlink(entry.status())) { auto err = error_code{}; auto filesize = fs::file_size(entry, err); if (filesize != static_cast<uintmax_t>(-1) && err != error_code()) size += filesize; } } } return size; } // Verilen yol nesnesine ilişkin bilgileri sunan metot void displayPathInfo(const fs::path& pathToShow) { int i = 0; cout << "Displaying path info for: " << pathToShow << '\n'; for (const auto& part : pathToShow) { cout << "path part: " << i++ << " = " << part << '\n'; } try { cout << "exists() = " << fs::exists(pathToShow) << '\n' << "root_name() = " << pathToShow.root_name() << '\n' << "root_path() = " << pathToShow.root_path() << '\n' << "relative_path() = " << pathToShow.relative_path() << '\n' << "parent_path() = " << pathToShow.parent_path() << '\n' << "filename() = " << pathToShow.filename() << '\n' << "stem() = " << pathToShow.stem() << '\n' << "extension() = " << pathToShow.extension() << '\n'; cout << "canonical() = " << fs::canonical(pathToShow) << '\n'; } catch (fs::filesystem_error err) { cout << "exception: " << err.what() << '\n'; } } // Dosya ismi, yolu, boyutu ve en son düzenlenme tarihini gösteren metot void displayFileInfo(const fs::directory_entry & entry, string &lead, fs::path &filename) { time_t cftime = chrono::system_clock::to_time_t(fs::last_write_time(entry)); // _s siz asctime API sini de kullanabilirsiniz char timeStr[26]; asctime_s(timeStr, sizeof timeStr, localtime(&cftime)); cout << lead << " " << filename << ", " << calculateFileSize(filename.c_str()) << ", time: " << timeStr; } // Verilen yol içeriğinin özyineli bir şekilde gösterilmesi void traverseAndDisplayFolderTree(const fs::path& pathToShow, int level) { if (fs::exists(pathToShow) && fs::is_directory(pathToShow)) { auto lead = string(level * 3, ' '); for (const auto& entry : fs::directory_iterator(pathToShow)) { auto filename = entry.path().filename(); if (fs::is_directory(entry.status())) { cout << lead << "[+] " << filename << '\n'; traverseAndDisplayFolderTree(entry, level + 1); cout << '\n'; } else if (fs::is_regular_file(entry.status())) displayFileInfo(entry, lead, filename); else cout << lead << " [?]" << filename << '\n'; } } } // Yukarıdaki dizinlerin özyineli bir şekilde gösterilmesinin ilklendirilmesinde kullanılan metot void traverseAndDisplayFolderTree(const fs::path& initialPath) { traverseAndDisplayFolderTree(initialPath, 0); } int main(int argc, char* argv[]) { // yol nesnesi ile /= and += operatörlerini kullanabilirsiniz. İlki yol formatına uygun bir şekilde ilgili metni ekler, ikincisi ise direk ekler fs::path examplePath("C:\\"); examplePath /= "Windows"; examplePath /= "System"; cout << examplePath << '\n'; examplePath += "32"; cout << examplePath << '\n'; // Verilen yola ilişkin bilgileri döner displayPathInfo("C:\\windows\\system.ini"); // Verilen yol içeriğini özyineli bir şekilde göster iteratePathContent("C:\\windows\\system.ini"); // Verilen dizin içeriğini öz yineli bir şekilde göster const fs::path pathToShow{ argc >= 2 ? argv[1] : fs::current_path() }; traverseAndDisplayFolderTree(pathToShow); return 0; } |
Boş Disk Alanı Bilgisi:
Bir diğer kullanışlı sınıf ise std::filesystem::space_info. Bu sınıf sayesinde dosya sistemine ilişkin boş ve kullanılabilir alan bilgileri aşağıda gösterildiği şekilde sorgulanabilir:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> #include <filesystem> using namespace std; namespace fs = std::experimental::filesystem; int main() { fs::space_info devi = fs::space("/windows/system32"); fs::space_info tmpi = fs::space("C:\\"); cout << ". Capacity Free Available\n" << "/windows/system32: " << devi.capacity << " " << devi.free << " " << devi.available << '\n' << "/tmp: " << tmpi.capacity << " " << tmpi.free << " " << tmpi.available << '\n'; } |
İzinler:
Aşağıda gösterildiği şekilde dosyaların izin nitelikleri ile de oynayabilirsiniz. Bu anlamda BitmaskTypetipinde olan std::filesystem::perms maskeleri kullanılmakta (detaylar için https://en.cppreference.com/w/cpp/filesystem/perms)’a göz atabilirsiniz). Aşağıda ilgili izinleri nasıl görüntüleyip, değiştirebileceğinizi bulabilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include <fstream> #include <bitset> #include <iostream> #include <filesystem> namespace fs = std::experimental::filesystem; using namespace std; void displayPermissions(fs::perms p) { cout << ((p & fs::perms::owner_read) != fs::perms::none ? "r" : "-") << ((p & fs::perms::owner_write) != fs::perms::none ? "w" : "-") << ((p & fs::perms::owner_exec) != fs::perms::none ? "x" : "-") << ((p & fs::perms::group_read) != fs::perms::none ? "r" : "-") << ((p & fs::perms::group_write) != fs::perms::none ? "w" : "-") << ((p & fs::perms::group_exec) != fs::perms::none ? "x" : "-") << ((p & fs::perms::others_read) != fs::perms::none ? "r" : "-") << ((p & fs::perms::others_write) != fs::perms::none ? "w" : "-") << ((p & fs::perms::others_exec) != fs::perms::none ? "x" : "-") << '\n'; } int main() { // Dosya oluştur ofstream("test.txt"); cout << "Created file with permissions: "; displayPermissions(fs::status("test.txt").permissions()); // İzinleri değiştir fs::permissions("test.txt", fs::perms::owner_all | fs::perms::group_all | fs::perms::add_perms); cout << "After adding o+rwx and g+rwx: "; displayPermissions(fs::status("test.txt").permissions()); // Dosyayı sil fs::remove("test.txt"); } |
Son Düzenleme Zamanı:
Bu kütüphane ve std::chrono kütüphanesini de kullanarak herhangi bir dosyanın son düzenlenme tarihini görebilirsiniz. Aşağıdaki örnek kod, bu kullanımı göstermekte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include <iostream> #include <chrono> #include <fstream> #include <filesystem> namespace fs = std::experimental::filesystem; using namespace std::chrono_literals; using namespace std; int main() { // Bir yol tanımlayalım fs::path path = fs::current_path() / "example.txt"; // Dosya oluştur ofstream exampleFile(path.c_str()); exampleFile.close(); // Son yazma zamanını oku auto ftime = fs::last_write_time(path); time_t cftime = chrono::system_clock::to_time_t(ftime); cout << "File write time is " << asctime(localtime(&cftime)) << '\n'; // Dosya zamanını 3 saat ileri alalım fs::last_write_time(path, ftime + 3h); // Dosya zamanını tekrar okuyalım ftime = fs::last_write_time(path); cftime = chrono::system_clock::to_time_t(ftime); // TR zamanı cout << "File write time is " << asctime(localtime(&cftime)) << endl; // Dosyayı silelim fs::remove(path); return 0; } |
Derleyici/Kütüphane Desteği:
Kullandığınız derleyicinin bu kabiliyeti destekleyip desteklemediğini, diğer kabiliyetler ile birlikte https://en.cppreference.com/w/cpp/compiler_support adresinden kontrol edebilirsiniz.
Derleyicinizin sürümüne göre std::experimental::filesystem isim alanı kullanmanız gerekebilir.
GCC: “-lstdc++fs” opsyionu eklenmelidir. Ayrıca “<experimental/filesystem>” başlığını eklemeniz gerekiyor.
Clang: Clang 5.0 ile destek sunuluyor. https://libcxx.llvm.org/cxx1z_status.html
Visual Studio: Visual studio 2017 için ise <filesystem> başlığını ekleyip, std::experimental::filesystem isim alanını kullanmalısınız (yukarıdaki örnekler de bu şekilde).
Kaynaklar:
- http://www.modernescpp.com/index.php/c-17-more-details-about-the-library
- https://en.cppreference.com/w/cpp/experimental/fs
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4100.pdf
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf
- https://stackoverflow.com/questions/48347642/differences-between-c14-stdexperimentalfilesystemv1-and-c17-stdfiles
- https://www.packtpub.com/application-development/c17-stl-cookbook
- https://www.amazon.com/Modern-Programming-Cookbook-multithreading-networking/dp/1786465183
- http://www.cppstd17.com/
- https://stackoverflow.com/questions/40899267/how-similar-are-boost-filesystem-and-the-standard-c-filesystem-libraries
- https://www.boost.org/doc/libs/1_68_0/libs/filesystem/doc/index.htm
- https://en.cppreference.com/w/cpp/compiler_support