創作內容

4 GP

【程式】檔案操作—Linux篇

作者:Shark│2017-12-26 22:38:03│巴幣:56│人氣:1993
繼Windows篇之後,這次介紹Linux的系統原生檔案函式。
Windows篇在此

先介紹一個網站,筆者常在這裡查Linux的系統呼叫。
https://linux.die.net/
在左邊Site Search打函式名稱,按enter就能找到說明文件,本篇介紹的函式都可以在這裡查到用法。

有個地方要說明,查下面的open()可以查到兩筆資料:open(2)open(3),解釋一下括號裡的數字是什麼。
那是Linux的man page裡的章節,這裡有解釋:https://linux.die.net/man/
1命令列指令應該是各篇Linux教學最常提到的,在命令列打「man 指令名稱」就是顯示這個。寫程式常用的是2和3,是C語言的函式說明,2是Linux核心,3是核心以外的函式庫。
不過核心函式經常section 2和section 3都有一篇,會有部分內容一邊有寫,另一邊沒寫的情況,最好兩個都看一下。而像XOpenDisplay()不屬於Linux的核心,就只有(3)。

2022/04/03:增加scandir()和opendir()的介紹。



開檔案用open()。
本篇每個函式要include的header不一定相同,所以會註明。
#include<fcntl.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
有兩種型式,但它是宣告成C的可變參數,不是C++的function overloading。

參數如下
1:檔名,編碼是UTF-8,Linux檔名用UTF-8編碼。

2:bit flag,包括很多東西。
首先要有O_RDONLY、O_WRONLY、O_RDWR的其中一個,分別是把檔案開成讀、寫、或讀+寫模式。
然後設定檔案已存在或不存在時要怎麼做,列個表比較清楚。
檔案已存在 檔案不存在
O_CREAT|O_TRUNC 覆蓋舊檔 建新檔
O_CREAT|O_EXCL 失敗 建新檔
O_CREAT 開檔 建新檔
0 開檔 失敗
O_TRUNC 覆蓋舊檔 失敗
以上是必須要有的,查說明文件可以看到還有很多其他flag,是在特殊情況才會用。

3:第二參數如果有包含O_CREAT則要給第三參數,建新檔時要把它設成什麼權限,如果檔案已存在則不會修改權限。
用的是8進位權限,即把使用者分成擁有者、群組、其他三類,規定誰可讀、可寫、可執行,詳細去查Linux的使用說明吧。
雖然有定義常數,但是直接打8進位數字比較快。
例如一般的可執行檔或script,擁有者可以執行和修改,其他人可以執行,就用0755。
又如資料檔,所有人都可以讀取和寫入,就用0666。

傳回的int稱為file descriptor(以下簡稱FD),之後要操作這個檔案就把file descriptor傳入函式。
失敗時傳回-1,會把原因記錄在全域變數errno裡。(嚴格來說是每個thread各有一個errno變數,一個thread不會影響其他thread的errno)

即使程式是64位元file descriptor也是32位元,Windows的handle則是跟指標相同大小,在64位元程式裡是64位元。

例如Cyber Sprite儲存遊戲設定,有檔案時開現有檔案,沒檔案時要建新的,權限要設成所有人可讀可寫,就像這樣用。
int file=open("settingl", O_WRONLY|O_CREAT, 0666);

關檔案用close()。
#include<unistd.h>
int close(int fd);
成功傳回0,失敗傳回非0。



再來介紹其他檔案操作函式

一、取得檔案資訊(大小、修改時間、擁有者ID等等)

#include<sys/stat.h>
struct stat buf;
//從FD取得
int fstat(int fd, struct stat* buf);
//從檔名取得
int stat(const char* path, struct stat* buf);
由於struct和函式都一樣叫stat,宣告stat結構的時候要寫成struct stat buf而不是stat buf,否則不能編譯。
成功時傳回0,會把檔案資訊存入stat結構,例如stat.st_size是大小,stat.st_mtime是修改時間。
stat.st_mode除了權限以外也包含檔案種類,最低的9 bit是權限,高位bit是檔案種類。

檔案管理器裡「8進位權限」的欄位即是把stat.st_mode用8進位表示。

後3個數字是權限,前面的數字4代表資料夾,10是普通檔案。除此之外Linux有其他檔案種類代表特殊物件,有named pipe、socket、character device、block device和符號連結。

從檔名檢查檔案是否存在也是用stat(),如果函式傳回非0,errno=ENOENT就代表找不到檔案。

如果程式是32位元(在64位元OS執行32位元程式也算),stat只能用在小於2GB的檔案,要用stat64。
#define __USE_LARGEFILE64
#include<sys/stat.h>

struct stat64 buf;
int fstat64(int fd, struct stat64* buf);
int stat64(const char* path, struct stat64* buf);
但不知為何用C compiler(gcc或clang)不能編譯,要用C++ compiler(g++或clang++)。
64位元程式裡stat本身就是64位元,不用改用stat64。



二、讀寫檔
#include<unistd.h>
ssize_t read(int fd, void* buf, size_t count);
count是要讀的byte數,會把資料填入buf,函式傳回實際讀取的byte數。
如果已到檔案結尾,沒有更多資料可讀則會有傳回值<count的情況,發生錯誤時傳回-1。

ssize_t和size_t的大小依程式位元數而定,32 bit時是32 bit整數,64 bit時是64 bit整數。
在32位元程式裡似乎不能一次讀2^31 bytes以上。

移動檔案指標要用lseek()。
#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
offset是移動距離,可正可負,whence是要以哪裡為基準,可填以下值
SEEK_SET:檔案開頭。
SEEK_CUR:目前位置。
SEEK_END:檔案結尾。此時距離填正數就會超過檔案長度,所以通常填負數。
傳回移動後的位置,失敗時傳回-1。off_t跟size_t一樣是隨程式位元數而定的整數。

在32位元程式處理2GB以上的檔案要用lseek64()
#define _LARGEFILE64_SOURCE
#include<unistd.h>

off64_t lseek64(int fd, off64_t offset, int whence);

例:讀某個檔案的前16 bytes,跳過之後128 bytes,再讀1024bytes(file是已經開好的FD)
char buffer1[16];
char buffer2[1024];
read(file, buffer1, 16);
lseek(file, 128, SEEK_CUR);
read(file, buffer2, 1024);

寫檔用write()
#include<unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
參數和傳回值跟read()差不多,buf是要寫入的資料,count是byte數。



三、搜尋檔案或列出資料夾裡的檔案

據筆者所知有三種方法。第一種是glob()。
#include<glob.h>
int glob(
  const char* pattern,
  int flags,
  int (*errfunc)(const char* epath, int eerrno),
  glob_t* pglob
);

void globfree(glob_t* pglob);
glob(3) - Linux man page
pattern是檔名,可包含萬用字元?和*,?代表任何值的「一個」字元,*則是不限內容不限字數的字元。
但萬用字元不會包含檔名開頭的點.(Linux檔名開頭是.代表隱藏檔),除非flags有指定GLOB_PERIOD。
flags是bit flag,詳細請查說明文件。

errfunc是發生錯誤時的callback,不需要的話可填NULL。發生錯誤時glob內部會呼叫這個函式,例如資料夾沒有可讀權限的時候。函式return後再繼續工作。
預設glob在遇到error時不會停止,會繼續尋找其他檔案,如果此函式return非0會讓glob()停止,把目前找到的結果傳回。

搜尋結果會存入pglob,宣告一個glob_t struct再把它的指標傳入。
成功時傳回0。
搜尋結果是放在動態分配記憶體,用完後要呼叫globfree()釋放。

如下會找出目前資料夾裡所有附檔名為txt的檔案。
int errorFunc(const char* path, int eerrno){
  printf("errorFunc path=%s\n", path);
  return 0;
}

……

glob_t result;
glob("*.txt", 0, errorFunc, &result);
printf("file number %d\n", result.gl_pathc);
for(int i=0; i<result.gl_pathc; i++){
  printf("%s\n", result.gl_pathv[i]);
}
globfree(&result);

第二個方法是scandir()。
#include<dirent.h>

//函式宣告
int scandir(const char* dirp,
  struct dirent*** namelist,
  int (*filter)(const struct dirent*),
  int (*compar)(const struct dirent**, const struct dirent**));

//struct dirent定義
//只有d_name比較會用到

struct dirent {
  ino_t          d_ino;    /* inode number */
  off_t          d_off;    /* offset to the next dirent */
  unsigned short d_reclen; /* length of this record */
  unsigned char  d_type;   /* type of file; not supported
                              by all file system types */

  char           d_name[256]; /* filename */
};

//----
//用法

int filter(const struct dirent* file){
  char* subStr=strstr(file->d_name, ".txt");
  //若結尾是.txt才return非0
  return subStr && subStr[4]==0;
}

struct dirent** dirEntryList;
int fileNum = scandir(".", &dirEntryList, filter, alphasort);
for(int i=0; i<fileNum; i++){
  printf("%s\n", dirEntryList[i]->d_name);
}
free(dirEntryList);
scandir(3) - Linux man page
這個方法不能搜尋子資料夾,且不能用萬用字元,篩選檔案要自己寫一個函式。

第一參數是資料夾路徑。要給兩個callback,第三參數填篩選函式,傳回非0代表要在結果中包含這個檔案,傳回0則不會放入搜尋結果;第四參數是排序函式,系統內建了兩個函式可以直接利用:alphasort()和versionsort()。
搜尋結果是dirent*的陣列,會存入第二參數,用完要用free()釋放空間。
若所有檔案filter()都傳回0則namelist=NULL,不過free()的規則是填NULL不會發生任何事。

如果只是要列出資料夾裡所有檔案,還有另一個方法:opendir()與readdir()。用法大致如下:opendir()產生一個DIR*型態的handle,readdir()跑迴圈,用完用closedir()釋放資源。
#include<dirent.h>

DIR* dir=opendir(".");
struct dirent* dirEntry;
while(dirEntry = readdir(dir)){
  printf("%s\n", dirEntry->d_name);
}
closedir(dir);
opendir(3) - Linux man page
readdir(3) - Linux man page
closedir(3) - Linux man page

有兩個檔案「.」和「..」,是在路徑裡代表目前資料夾和上一層而不是真的檔案,scandir()和opendir()的方法,以及glob()的flag有GLOB_PERIOD的時候也會把這兩個列出來,要自己設法處理。



或許有聽過「Unix-like系統把周邊裝置視為檔案」的說法,這說法其來有自,取得某些系統資訊的方法就是讀取特定的檔案。
例:
開/proc資料夾可取得目前所有process的資訊,讀取/proc/self/cmdline可得知目前process的命令列。
讀取/proc/cpuinfo會顯示CPU的資訊,可以在命令列打「cat /proc/cpuinfo」看看。
開啟/dev/input/js0可取得手把輸入,寫遊戲引擎會用到。
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=3833622
All rights reserved. 版權所有,保留一切權利

相關創作

同標籤作品搜尋:Linux程式設計|Linux|C語言|程式

留言共 0 篇留言

我要留言提醒:您尚未登入,請先登入再留言

4喜歡★shark0r 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:【程式】檔案操作—Win... 後一篇:【進度】完成一個關卡的t...

追蹤私訊切換新版閱覽

作品資料夾

shane8124各位帥哥美女們
新聞學院的戀愛預報 112#開學前夕 更新看更多我要大聲說昨天20:00


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】