C++ 檔案、資料夾、路徑處理函式庫:boost::filesystem

原帖:https://tokyo.zxproxy.com/browse.php?u=uG7kXsFlW1ZmaxKEvCzu8HrCJ0bXIAddA1s5dtIUZ%2FYzM1u9JI7jjKLTXvXJlIqeavUo1Ak%3D&b=6php

 

若是要在 C++ 裡對特定的檔案作存取,其實透過 STL 的 fstream(參考)來作,通常是不會有什麼問題的;相對的,問題比較大的部分,可能會是在於對於資料夾(folder、directory)的處理,以及對於路徑的操做上。像是以路徑來說,Windows 用的「\」、並且有磁碟代號(X:),而 Linux 則是用「/」、也沒有磁碟代號的概念;若是再加上一些其餘檔案系統設計上的不一樣,因此其實要寫一個跨平臺的檔案操做程式,其實有滿多繁瑣的事情要作的。而在路徑的部分,就 Heresy 的認知,STL 裡面好像還沒有類似 dir 或 ls 功能的函式、能夠用來列出目錄下的檔案?ios

Boost C++ Libraries 裡的 FileSystem 這個函式庫(官方網頁,如下簡稱 Filesystem),就是為了讓程式開發者能夠快速、簡單地對系統的檔案、資料夾、路徑進行操做,而發展出來的函式庫;他不但和 C++ 的標準函式庫能夠很是好地相融在一塊兒,更能夠讓程式開發者寫的程式能在不一樣的做業系統下運做。而同時,FileSystem 也已經被 C++ Standards Committee 接受決定當做 TR2 的一部分了~express

雖然目前 Boost 的 1.44 還是以第二版的 FileSystem 為預設使用的版本,可是實際上 FileSystem 已經有第三版(網頁)了~而在下一版的 Boost(1.45),也會改以 FileSystem v3 來當做預設的版本,因此  Heresy 這邊的介紹也就以 FileSystem v3 為主了。windows

 

使用 FileSystem v3

由於 FileSystem 有牽涉到系統的操做,因此他也是 Boost C++ Libraries 裡,少數須要先編譯、建置的函式庫之一,而建置的方法,就請參考以前的《Boost C++ Libraries 簡介》,這邊就很少提了。ui

而前面也有提到,目前的 Boost 1.44 預設還是使用 FileSystem v2,因此若是要使用 FileSystem v3 的話,除了要 include FileSystem 的主要 header 檔:boost/filesystem.hpp 外,還要再 include 他以前,加上一行:spa

#define BOOST_FILESYSTEM_VERSION 3

來指定要使用的 FileSystem 版本。理論上等到 Boost 1.45 後,應該就沒必要加這個了。排序

而由於 FileSystem 這個函式庫是須要是先編譯的,因此在建置有使用到 FileSystem 的程式時,也須要 link FileSystem 的 library 檔。在 VC 的環境下的話,理論上 Boost 會自動去 link,使用者只要設定好路徑就能夠了;gcc 的話,則是要加上「-l boost_filesystem」來作 library 的 linking。rem

在操做上,FileSystem 主要是定義了一個 path 的類別,用來處理路徑的操做;像是透過 operator / 能夠輕鬆地來加上子目路的路徑、透過 pareant_path() 則能夠簡單地取得上一層路徑。同時,Path 也能夠快速地和 std::string 作轉換,因此在使用上相當地方便~get

下面就是一些簡單的 Path 操做範例:string

#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 );
  }
}

基本上,這也就只是個簡單的範例程式了~實用價值不高,並且應該也還有許多改善的空間。 ^^"

相關文章
相關標籤/搜索