大綱

 

(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. 其他檔案管理 *


 

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

arrow
arrow
    全站熱搜

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