原帖:https://tokyo.zxproxy.com/browse.php?u=uG7kXsFlW1ZmaxKEvCzu8HrCJ0bXIAddA1s5dtIUZ%2FYzM1u9JI7jjKLTXvXJlIqeavUo1Ak%3D&b=6
如果要在 C++ 裡對特定的檔案做存取,其實透過 STL 的 fstream(參考)來做,一般是不會有什麼問題的;相對的,問題比較大的部分,可能會是在於對於資料夾(folder、directory)的處理,以及對於路徑的操作上。像是以路徑來說,Windows 用的「\」、而且有磁碟代號(X:),而 Linux 則是用「/」、也沒有磁碟代號的概念;如果再加上一些其他檔案系統設計上的不同,所以其實要寫一個跨平台的檔案操作程式,其實有滿多繁瑣的事情要做的。而在路徑的部分,就 Heresy 的認知,STL 裡面好像還沒有類似 dir 或 ls 功能的函式、可以用來列出目錄下的檔案?
Boost C++ Libraries 裡的 FileSystem 這個函式庫(官方網頁,以下簡稱 Filesystem),就是為了讓程式開發者可以快速、簡單地對系統的檔案、資料夾、路徑進行操作,而發展出來的函式庫;他不但和 C++ 的標準函式庫可以非常好地相融在一起,更可以讓程式開發者寫的程式能在不同的作業系統下運作。而同時,FileSystem 也已經被 C++ Standards Committee 接受決定當作 TR2 的一部分了~
雖然目前 Boost 的 1.44 還是以第二版的 FileSystem 為預設使用的版本,但是實際上 FileSystem 已經有第三版(網頁)了~而在下一版的 Boost(1.45),也會改以 FileSystem v3 來當作預設的版本,所以 Heresy 這邊的介紹也就以 FileSystem v3 為主了。
使用 FileSystem v3
由於 FileSystem 有牽涉到系統的操作,所以他也是 Boost C++ Libraries 裡,少數需要先編譯、建置的函式庫之一,而建置的方法,就請參考之前的《Boost C++ Libraries 簡介》,這邊就不多提了。
而前面也有提到,目前的 Boost 1.44 預設還是使用 FileSystem v2,所以如果要使用 FileSystem v3 的話,除了要 include FileSystem 的主要 header 檔:boost/filesystem.hpp 外,還要再 include 他之前,加上一行:
#define BOOST_FILESYSTEM_VERSION 3
來指定要使用的 FileSystem 版本。理論上等到 Boost 1.45 後,應該就不必加這個了。
而由於 FileSystem 這個函式庫是需要是先編譯的,所以在建置有使用到 FileSystem 的程式時,也需要 link FileSystem 的 library 檔。在 VC 的環境下的話,理論上 Boost 會自動去 link,使用者只要設定好路徑就可以了;gcc 的話,則是要加上「-l boost_filesystem」來做 library 的 linking。
在操作上,FileSystem 主要是定義了一個 path 的類別,用來處理路徑的操作;像是透過 operator / 可以輕鬆地來加上子目路的路徑、透過 pareant_path() 則可以簡單地取得上一層路徑。同時,Path 也可以快速地和 std::string 做轉換,所以在使用上相當地方便~
下面就是一些簡單的 Path 操作範例:
#define BOOST_FILESYSTEM_VERSION 3 #include <stdlib.h> #include <string> #include <iostream> #include <boost/filesystem.hpp> namespace BFS = boost::filesystem; int main( ) { std::cout << "Windows style:\n"; BFS::path p1( "c:\\windows" ); BFS::path p2 = p1 / "system32"; std::cout << "p1=" << p1 << ", p2=" << p2 << "\n" << std::endl; std::cout << "Linux style:\n"; p1 = "/usr/local"; p2 = p1.parent_path(); std::cout << "p1=" << p1 << ", p2=" << p2 << "\n" << std::endl; }
執行的結果應該會向下面這樣:
Windows style: p1="c:\windows", p2="c:\windows\system32" Linux style: p1="/usr/local", p2="/usr"
在這個範例裡,可以發現 FileSystem 的 Path 在操作上相當簡單,除了可以直接透過 std::string 來建立 Path 的物件外,Boost 也提供了轉換的函式,在大部分情況下都可以把 std::string 自動轉換成 Path 的型別。而如果要進到子資料夾,只要透過 operator / 就可以了(上方黃底的部分)~在操作上相當地直覺。
而如果想把 Path 轉換為 std::string 的話,也只要呼叫他提供的 Path::string() 這個函式(或者也有提供 wstring() 的版本)就可以了;下面就是一個簡單的例子:
std::string str = p2.string();
Path 相關函式細節
當然,FileSystem 提供的 Path 這個類別,還有許多成員函式可以使用;在 FileSystem 官方的 tutorial 文件裡,就有列出一些他的函式,reference 裡則有完整的列表。
第一部分是路徑分解的函示(path decomposition、參考),主要目的就是用來分解路徑的各項目。下面 Windows 是以「C:\Windows\System32\xcopy.exe」、Linux 則是以「/Build/heresy/a.out」來當作範例的資料:
函式 |
說明 |
Windows 範例 |
Linux 範例 |
---|---|---|---|
root_name() |
根目錄名稱 | C: | |
root_directory() |
根目錄資料夾 | \ | / |
root_path() |
根目錄路徑 | C:\ | / |
relative_path() |
相對路徑 | Windows\System32\xcopy.exe | Build/heresy/a.out |
parent_path() |
上一層目錄路徑 | C:\Windows\System32 | /Build/heresy |
filename() |
檔案名稱 | xcopy.exe | a.out |
stem() |
不包含副檔名的檔名 | xcopy | a |
extension() |
副檔名 | .exe | .out |
基本上,對於要解析一個路徑來說,這邊提供的函式應該算是相當充分了~
而除了路徑的分解外,他也還有提供不少判斷用的函式(官方稱為 path query,參考),例如:empty()、has_filename()、has_extension()、is_absolution() 等等;這些都是簡單地回傳一 true 或 false,可以幫助快速地進行路徑的條件判斷,在這邊就不一一解釋了。
而除了 path 本身的函式之外,在 boost::filesystem 這個 namespace 下,也還提供了相當多的函式,可以用來取得檔案的相關資料、或是對檔案、目錄進行操作;Boost 把這些函式稱為「Operational functions」,下面 Heresy 列了一些自己覺得比較重要的:
確認檔案性質、相關資料的函式:
函式 |
說明 |
---|---|
bool exists( const path& ) |
判斷所指定的路徑是否存在 |
bool is_directory( const path& ) |
判斷指定的路徑是否是目錄 |
bool is_regular_file( const path& ) |
判斷指定的路徑是否是一般檔案 |
bool is_symlink( const path& ) |
判斷指定的路徑是否是 symbolic link |
bool is_other( const path& ) |
判斷指定的路徑是否不是路徑、一般檔案或 symbolic link |
bool is_empty( const path& ) |
判斷指定路徑是否是空目錄、或是大小為零的檔案 |
uintmax_t file_size( const path& ) |
取得指定路徑檔案的大小,只對 regular file 有用 |
註:這些函式實際上都是用 file_status 來進行操作的,傳入 path 當參數的只是 inline 函式 |
檔案、目錄操作:
函式 |
說明 |
---|---|
void copy_file( const path& from, const path& to) |
複製檔案。 也可以再加上額外的 copy_option,來指定檔案已存在時的處理方法(fail_if_exists、overwrite_if_exists)。 |
void rename( const path& old, const path& new ) |
修改檔案、目錄的名稱。 |
bool create_directories( const path& ) bool create_directory( const path& ) |
建立新目錄。 create_directory() 在上層目錄不存在的情況下,會建立失敗;而 create_directories() 則會一層一層地建立下來。 |
bool remove( const path& ) uintmax_t remove_all(const path& p) |
刪除檔案或目錄。 remove() 只會刪除單一檔案和目錄,如果目錄內還有東西,會刪除失敗;remove_all() 則是會把目錄內的東西也一起刪除掉。 |
其他:
函式 |
說明 |
---|---|
path current_path() void current_path( const path& ) |
取得、設定目前的工作路徑 |
bool equivalent(const path& , const path& ) |
確認兩個路徑是否相同 |
space_info space( const path& ) |
取得路徑的容量、可用空間資訊 |
path unique_path(); |
產生一個獨一無二的路徑名稱。形式可以指定,預設的形式會式「%%%%-%%%%-%%%%-%%%%」,每一個「%」都會被填入隨機產生的值,適合用在產生暫存性的路徑名稱。 |
基本上,有了這些函式,應該就足以應付絕大部份檔案、路徑操作上的需求了!當然,Heresy 這邊沒有全列,而實際上 boost 的 FileSystem 對於某些檔案系統的特殊功能,也沒有完全支援;而如果要用到 FileSystem 沒有支援的功能,可能就得用系統提供的功能了。
目錄的處理
這部分的「目錄的處理」,指的主要是列出一個目錄下所有的檔案、目錄的部分;FileSystem 在這部分,主要是透過「directory_iterator」這個類別(參考)來做的。透過 path 和 directory_iterator,程式開發者可以輕鬆地透過和 STL iterator 相同的操作方法,來對一個目錄下的項目作處理,算是相當方便的~
下面是一個簡單的例子,他會列出 C:\windows 下所有的檔案與資料夾:
namespace BFS = boost::filesystem; BFS::path p1( "c:\\windows" ); std::cout << "Files in " << p1 << std::endl; for( BFS::directory_iterator it = BFS::directory_iterator(p1); it != BFS::directory_iterator(); ++ it ) { BFS::path px = it->path(); std::cout << px.filename() << "\n"; }
在這個例子裡,基本上就是透過一個 for 迴圈、以及透過 p1 建立出來的 directory_iterator it,來掃整個 p1 目錄下的項目。其中,directory_iterator( p1 ) 就是建立一個指向 p1 目錄下第一個項目的 iterator、directory_iterator() 則是建立一個通用的、代表結尾的 end iterator,用來做中斷條件的判斷參考。而在使用上,由於 it 的型別實際上是 directory_iterator,所以還是需要轉型成 path,才方便做一般性的操作;或者,也可以透過 status() 這個函式,來取得檔案狀態進行操作。
而前面也提過,由於他是採用 iterator 的概念來做的,所以可以和 STL 做很好的整合;這邊就用官方 tut4.cpp 的例子(網頁),稍微修改一下來做示範了~
#define BOOST_FILESYSTEM_VERSION 3 #include <stdlib.h> #include <vector> #include <iostream> #include <boost/filesystem.hpp> using namespace std; using namespace boost::filesystem; int main( int argc, char* argv[] ) { path p( argv[1] ); if( exists( p ) && is_directory( p ) ) { cout << p << " is a directory containing:\n"; vector<path> v; copy( directory_iterator( p ), directory_iterator(), back_inserter( v ) ); sort( v.begin(), v.end() ); for( vector<path>::const_iterator it( v.begin() ); it != v.end(); ++it ) cout << "\t" << *it << '\n'; } return 0; }
在這個範例裡,主要就是多了先將目錄的 path 都先複製到 vector<path> v 裡面,進行 sort 的動作;如此一來,就可以確保輸出的結果是排序過的了~而目前這樣寫的排序順序,就是直接用 path 預設的大小比較方法來做,如果要自己控制排序順序的話,STL 的 sort() 也可以簡單地透過自訂 comparsion 來做到;比如說如果搭配C++0x 的 lambda expression 的話,可以簡單寫成:
sort( v.begin(), v.end(), [](const path& p1, const path& p2 ){return p1.extension()<p2.extension();} );
如此就可以根據副檔名來做排序了~不過這個寫法非常簡略,所以其實還是有不少改善的空間的。 ^^"
範例
最後,則是來貼一個 Heresy 自己寫的程式當作範例。這個程式會去遞迴地去掃描所給的資料夾下的所有的資料夾以及檔案,並且根據檔案大小來做排序(非 regular file 會放到最後);而輸出的部分,如果是檔案的話,他除了印出檔名外,也會印出檔案的大小。
#define BOOST_FILESYSTEM_VERSION 3 #include <stdlib.h> #include <vector> #include <string> #include <iostream> #include <boost/filesystem.hpp> using namespace std; using namespace boost::filesystem; bool CompareBySize( const path& rP1, const path& rP2 ) { if( !is_regular_file( rP1 ) && !is_regular_file( rP2 ) ) return false; else if( !is_regular_file( rP1 ) ) return false; else if( !is_regular_file( rP2 ) ) return true; return file_size( rP1 ) < file_size( rP2 ); } void outputFileInfo( const path& rPath ) { cout << " File: " << rPath.filename(); if( is_regular_file( rPath ) ) cout << " (size:" << file_size( rPath ) / 1024 << "kb)"; cout << endl; } void ScanDirectory( const path& rPath ) { cout << " Directory: " << rPath << endl; vector<path> vList; copy( directory_iterator(rPath), directory_iterator(), back_inserter( vList ) ); sort( vList.begin(), vList.end(), CompareBySize ); for( vector<path>::const_iterator it = vList.begin(); it != vList.end(); ++ it ) { if( is_directory( *it ) ) ScanDirectory( *it ); else outputFileInfo( *it ); } } int main( int argc, char* argv[] ) { path rootPath( argv[1] ); if( exists( rootPath ) ) { if( is_directory( rootPath ) ) ScanDirectory( rootPath ); else outputFileInfo( rootPath ); } }
基本上,這也就只是個簡單的範例程式了~實用價值不高,而且應該也還有許多改善的空間。 ^^"