大綱

 

(0) 可攜性問題

(1) 絕對 / 相對路徑 < too easy >

(2) 取得當前路徑

(3) 設定當前路徑

(4) 建立資料夾

(5) 刪除資料夾

(6) system call 誤失

(7) 列舉資料夾下所有檔案 < Q&A 等級 >

(8) 取得特殊資料夾路徑 < 我的文件、最近開啟檔案等>

(9) 檔案管理 * 


* 本文所提及之 system 指令,部份可以 popen / _popen 方式處理。

* 對於字集編碼問題,不為本文探討內容。

* 撰完文後才發現,基於 VC 太深 < 筆者非願如此 > ,於此告知 。

* 外部程式執行之方式 (system, WinExec, CreateProcess 等) 雖有點相關,但不在本文探討內容 。

* 目錄管理與檔案管理常放在一起探討,但篇幅有限,直接連到  以前寫過的檔案管理  做探討。

 

0. 可攜性問題

 

標準函式庫中,並沒強制要求目錄管理之相關函式該放於何處,

也沒強制要求這些函式名稱該如何定義,

但妙的是從 Turbo C 開始就提供了部份相關函式,

至今對任何 compiler 而言,

header file、function name、function prototype 等,

還是沒有一致性的結論,目前自制、宣稱能達到可攜性的作法,

是研究各環境、compiler 之相關函式使用方式,

再以 pre-process (#ifdef) 方式進行封裝。

唯此文並不以可攜性為前提,所撰之程式碼以 VC 10.0 為主,

MinGW gcc 應大多執行 ( 筆者只有測試列舉所有檔案是相容的 ),

目前筆者所知大概有幾個 header 可以查

dir.h 或 direct.h , io.h 。

 

另外 VC 對於這類函式,幾乎全都是以底線開頭當函式名稱。

 

* 一個引戰點:既然沒有一個非常標準的作法,筆者認為為了省事,

   適當(當必要時)調用 system 相關指令也不是一個太壞的選擇。

   如同有人強調不要用 system("pause"); 但實際上就是沒有可攜性作法可完全達到相同目的...


 

 

1. 絕對 / 相對路徑

 

一般程式在執行時之預設路徑,為該執行程式所在位置。

假設於 D:\F1\FF1 存在一個 main.exe (console),而 F1 底下又存在這些資料夾

 

D:\F1\FF1 (含 a.txt)
D:\F1\FF2 (含 b.txt)
D:\F1\FF3 (含 c.txt)

 

當 main.exe 快點兩下執行時,目前之目錄設定即為 D:\F1\FF1

這便是程式預設之「當前目錄」。所有的 fopen、open 等相關函式,

若只寫檔名時,便是搜尋當前目錄,如 fopen("a.txt", "r");

為執行開啟 D:\F1\FF1\a.txt 檔案之動作,這屬相對路徑之指定,

實際在指定時只指定檔名,由當前目錄進行搜尋。

 

另一種是絕對路徑,假設要開啟的檔案是 D:\F1\FF2\b.txt

可以直接指定,如 fopen("D:\\F1\\FF2\\b.txt", "r");

即使當前目錄並不在 FF2 裡,但指定完整路徑後,便可跳到該目錄下搜尋。

一般而言若 資料夾/檔案 結構 已被拿來當作是小型資料庫使用時 ,

筆者較習慣使用絕對路徑方式處理,在 debugging / trace 顯得較方便。

 

相對路徑裡還有一種表示,常見於較多層資料夾情況一次複製給 user。

假設目前位於 D:\F1\FF1,要搜尋「上一層目錄」(也就是 D:\F1 ) 

裡面之 FF3 資料夾,裡面之 c.txt 時,可以使用這種表示法

 

fopen("..\\FF3\\c.txt");

 

拆解一下

 

..\\  回到上一層  的

FF3\\  FF3 資料夾 的

c.txt 

 

2. 取得當前路徑


較常見之作法為 getcwd ( vs 2003 後開頭加底線為佳 )


#include <direct.h>
#include <stdio.h>

char *getcwd( 
    char *buffer,
    int maxlen 
    );

int main()
{
    char buf[2000];
    getcwd(buf, sizeof(buf)-1);
    puts(buf);
    getchar();
    return 0;
}

 

Win32 API 可見 GetCurrentDirectory

 

3. 設定當前路徑

 

較常見之作法為 chdir ( vs 2003 後開頭加底線為佳 )

 

#include <direct.h>
#include <stdio.h>

int chdir(const char* Path);

int main()
{
    char buf[2000];
    if(!chdir("C:\\")){
        getcwd(buf, sizeof(buf)-1);
        puts(buf);
    } else {
        puts("chdir fail");
    }    
    getchar();
    return 0;
}

 

Win32 API 可見 SetCurrentDirectory。 

 

 

4. 建立資料夾


較常見之作法為 mkdir ( vs 2003 後開頭加底線為佳 )


#include <direct.h>
#include <stdio.h>

int main()
{
    if(mkdir("C:\\folder")==0) 
        puts("create folder success");
    else
        puts("create folder success");

    getchar();
    return 0;
}


Win32 API  可見 CreateDirectory 相關函式。


大多 mkdir 之設計在成功時會返回0,失敗時會返回 -1。

注意到的是大多 compiler 給予 mkdir 之特性:

一般而言不能用在建立多層資料夾之情形,如

 

mkdir("C:\\F1\\SUB1\\SUB2\\SUB3");

 

必須一層一層建立起來,即使是 CreateDirectory 也如此。

mkdir 即使這種情形建立失敗後,返回值仍為 0 (個人是覺得有寫錯之嫌),這是該注意之處

  

要建立深入資料夾有幾種作法

 

 ˇ 直接進行 strtok (... ,"\\", ...) 作分析  < 這也是較常用的 >

ˇ 調用 Win32 DbgHelp API : MakeSureDirectoryPathExists < 不建議, 沒 unicode 版本 >

ˇ 調用 SHCreateDirectoryEx : 放在 <Shlobj.h> / <Shell32.lib>

 

通常較建議直接做 strtok 分析 (或 stringstream 取得) 即可。

 

 

5. 刪除資料夾


較常見之作法為 rmdir ( vs 2003 後開頭加底線為佳 ) 


#include <direct.h>
#include <stdio.h>

int main()
{
    if(rmdir("C:\\folder")==0) 
        puts("remove folder success");
    else
        puts("remove folder success");

    getchar();
    return 0;
}


成功時傳回0,失敗時傳回-1。但若本來要移除的資料夾就不存在時,它會傳回 0 <  成功 >。

Win32 API 請見 RemoveDirectory

 

rmdir 算是一個非常基本款、不非常方便的指令。要移除一個資料夾之前,

必須先將裡面的檔案與其他資料夾全都清空 (也就是刪除) 才可以完成,

但大多使用時是希望,不論這資料夾裡面有沒有東西,

直接把它移掉就對了 < 裡面有東西的話就順便移掉 >。

這作法就麻煩了,遞迴列舉資料夾底下所有檔案/資料夾,逐一刪除;

再來是使用 SHFileOperator ( Shell32.lib, Shellapi.h ),

使用起來並不容易。


6. system call 誤失


 

由於使用 system 呼叫 cd、dir 等指令時,由於環境變數不會保存下來,故

 

system("cd /D C:\\F1\\FF2");

 

這是無效的。但一些指令確實非常好用,像是剛剛所提到的,

要直接刪除某個目錄時,直接調用 system("del C:\\F1 /f /s /q"); 

便可深度刪除。而 system("rm C:\\1\\2\\3\\4"); 

也可深度建立資料夾。

 

7. 列舉資料夾下所有檔案


這問題可以講很久,這裡點到為止。

列舉資料夾底下所有檔案筆者保守推估至少存在 3 ~ 5 種作法。


(7.1) GUI 拉子控制出來


這是蠻偷懶的方式,一些別人已經包好了 combox、list 等子控制,裡面包含了 function member - Dir,直接拉一個子控制出來,顯示設隱藏,需要的時候再調用即可。


(7.2) system("dir")


假設要查 C:\ 底下所有 .txt 檔案,先做 system("C:\\*.txt /b > tmp.rec") ; 之動作,最後 tmp.rec 裡便是所有檔名,所以只要讀 tmp.rec 便可。

 

(7.3) direct.h / io.h

 

#include <stdio.h>
#include <string.h>
#include <io.h>
#include <direct.h>
#include <time.h>

#define _MAX_FNAME 2000
void EnumAllFileTree(char *path);

int main()
{
    const char* path = "D:\\Couse";
    const char* filter = "*.*";
    char attrib[_MAX_FNAME];
    char filename[_MAX_FNAME];

    struct _finddata_t file;
    long hFile;
    struct tm *tmCreate, *tmWrite, *tmAccess;
    
    chdir(path);

    hFile = _findfirst(filter, &file);    
    if(hFile!=-1){
        do{
            // get filename
            sprintf(filename, "%s\\%s", path, file.name);
            printf("%s\n", filename);
            memset((void*)attrib, 0, _MAX_FNAME);
            
            // file attribute
            strcpy(attrib, "\tattrib:[ ");
            if(file.attrib & _A_ARCH) strcat(attrib, "檔案 ");
            if(file.attrib & _A_HIDDEN) strcat(attrib, "隱藏 ");
            if(file.attrib & _A_NORMAL) strcat(attrib, "普通 ");
            if(file.attrib & _A_RDONLY) strcat(attrib, "唯讀 ");
            if(file.attrib & _A_SUBDIR) strcat(attrib, "資料夾 ");
            if(file.attrib & _A_SYSTEM) strcat(attrib, "系統 ");
            strcat(attrib, "]");
            printf("%s\n", attrib);
            printf("\tfilesize:%u\n",file.size);
            
            // get access, create, write time, and ouput 
            // printf("\tcreate:%s", ctime(&file.time_create));
            // printf("\twrite:%s", ctime(&file.time_write));
            // printf("\taccess:%s", ctime(&file.time_access));

            // trans to struct tm, tmCreate as sample.
            // 
            tmCreate = localtime(&file.time_create);
            printf("\t%d/%d/%d,%d:%d:%d:, (%d)\n",
                tmCreate->tm_year+1900,
                tmCreate->tm_mon+1,
                tmCreate->tm_mday,
                tmCreate->tm_hour,
                tmCreate->tm_min,
                tmCreate->tm_sec,
                tmCreate->tm_wday);
        }while(_findnext(hFile, &file)==0);
        _findclose(hFile);
    }
    getchar();
    return 0;
}

 

 (7.4) 其他方式

 

筆者說過,這問題還是必須看 compiler 與環境為主,所以可能找到的是 opendir、readdir 這組;

而 Win32 API ,可參考 FindFirstFileFindNextFilestruct WIN32_FIND_DATA 等函式;

另外 boost 裡面也有,在 boost / filesystem.hpp 裡面,這裡筆者不熟,就不贅述。


(7.5) 深度掃描問題


像是防毒軟體,可能會掃描整個資料夾、整個 C 槽,或整顆硬碟,基於上述之程式碼,這裡提供另一組。

 

#include <stdio.h>
#include <string.h>
#include <io.h>
#include <direct.h>
#define _MAX_FNAME 2000
void EnumFiles(char *path);

int main()
{
    EnumFiles("C:\\");
    getchar();
    return 0;
}
void EnumFiles(const char *path)
{
    const char *filter = "*.*";
    char  filename[_MAX_FNAME];
    char attrib[_MAX_FNAME];
    struct _finddata_t file;
    long hFile;

    chdir(path);
    hFile = _findfirst(filter, &file);

    if(hFile!=-1){
        do{
            // important (!)
            if(!strcmp(file.name, ".") || !strcmp(file.name,"..")){
                continue;
            }
            sprintf(filename, "%s\\%s", path, file.name);
            printf("%s\n", filename);

            if(file.attrib & _A_SUBDIR) {
                printf("%s <folder>\n", filename);
                EnumFiles(filename);
            }
        }while(_findnext(hFile, &file)==0);
        _findclose(hFile);
    }
}

 

有興趣可跑跑看,但這會跑很久。另外筆者不確定用這種方式最後會不會因為 recursive 太深層關係,最後造成 stack overflow。

 

(7.6) 只窮舉特定之檔案

 

ˇ  有沒有辦法只列舉「資料夾」即可?

ˇ  有沒有辦法只列舉「唯讀之 *.txt 」即可?

ˇ  有沒有辦法依「建立時間」之順序拿到?

 

類似的問題還很多。目前筆者不知道除了一一窮舉、再判斷檔案/資料夾屬性外,

能有其他辦法可一次就拿到上面這些目的。

 

「依 xxx 時間」之順序拿應是較令人感興趣的問題,目前實作很多人是全都塞到記憶體裡面去,

直接呼叫 qsort 出來做排序。另一派 < 也就是筆者,犯懶 > ,

直接用 system("dir") 之相關指令,與 io  redirection 完成,這部份有興趣可研究 dir 指令。

 

(7.7) 筆者的建議..


由於窮舉資料夾底下之特定檔案,幾乎是必遇之問題,在 VBA 裡更是頻繁,

任何程式語言,較建議包成 callback function 處理 (如果它可以的話...),一勞永逸。

< 代價可能是 function call 之時間 ,但若處理之引數複雜時,可能就不適合用 callback function>



8. 取得特殊資料夾路徑 < 我的文件、最近開啟檔案等>


通常這是用 Win32 API 完成,參考 SHGetSpecialFolderPath

而特殊資料夾路徑之代號,參考 CSIDL ,裡面的參數並非在所有電腦裡都可順利取得。

這裡拿取得「我的最愛」路徑為例。

 

#include <windows.h>
#include <stdio.h>
#include <shlobj.h>

int main()
{
    char path[2000];
    SHGetSpecialFolderPath(NULL,path,CSIDL_COMMON_FAVORITES,FALSE);
    puts(path);
    getchar();
    return 0;
}

 

取得特殊資料夾路徑後,要進行窮舉,方法與上述 (section 7 ) 相同。

 


9. 其他檔案管理 *


 

檔案管理和目錄管理幾乎都是一起提的,唯篇幅限制,且筆者之前也寫過一份,有興趣請連至 此篇文章

edisonx 發表在 痞客邦 PIXNET 留言(3) 人氣()


留言列表 (3)

發表留言
  • damody
  • 請問若要加上unicode的處理,有什麼方法或程式庫可以順便跨平台嗎= =?因為我對 vc 的 wchar_t 以外的 unicode 檔案系統實在不熟。
  • 若你說的「跨平台」指的是「可攜性」,那沒有,vc 對於 big5/unicode 處理比其他 compiler 多了一些步驟,所以即使沒做特別處理,"許功蓋"問題也沒浮現過,但這問題於 gcc 下明顯要再額外處理。

    我猜這篇文章可能是你要的
    http://www.vckbase.com/document/viewdoc/?id=1733
    msdn 上應有 < 沒再仔細搜尋過 >
    但如你所見,其他函式要再查 compiler 是否支援。

    edisonx 於 2012/04/24 10:41 回覆

  • novus
  • 處理檔案系統目前最好用的程式庫應該還是 boost filesystem v3,在 Windows 和 POSIX 環境可以作到無痛轉移。我建議除非要使用非常依賴平台的功能(例如取得檔案的icon),否則全面換到 boost filesystem 會省下很多時間。

    字元編碼確實是一個令人痛苦的事情,試試 boost locale 吧
  • novus 摸的東西實在是太多了,相較我所知實在是過於狹獈,再次感謝指導。

    edisonx 於 2012/04/25 18:35 回覆

  • ywchen1994
  • 請問一下我寫了一隻程式要刪除程式所在的資料夾下的另一個資料夾裡的所有的jpg
    假設另一個資料夾叫做A
    我想要用system("del A//*.jpg")這樣對嗎?
  • try it !! 不過我猜你遇到的問題會是要先做 chdir ( Windows 的話可用 SetCurrectDirectory,搭配 GetModuleFileName 使用)。

    edisonx 於 2016/08/24 00:47 回覆

您尚未登入,將以訪客身份留言。亦可以上方服務帳號登入留言

請輸入暱稱 ( 最多顯示 6 個中文字元 )

請輸入標題 ( 最多顯示 9 個中文字元 )

請輸入內容 ( 最多 140 個中文字元 )

請輸入左方認證碼:

看不懂,換張圖

請輸入驗證碼