Linux系統(tǒng)編程(一) ------ 文件操作函數(shù)

文件操作

打開文件

1.使用open()函數(shù)打開和創(chuàng)建文件

  • 手冊文件 man 2 open

函數(shù)頭文件及函數(shù)原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

函數(shù)參數(shù):

  pathname:待打開文件的絕對路徑和文件名。

  flags:打開的旗標類型,或稱模式,
  O_RDONLY      只讀模式打開文件
  O_WRONLY      只寫模式打開文件
  O_RDWR        讀寫模式打開文件
  O_CREAT       若欲打開的文件不存在則自動建立該文件
  O_TRUNC       若文件存在并且以可寫的方式打開時, 此旗標會令文件長度清為0, 
                而原來存于該文件的資料也會消失。
  O_EXCL        如果O_CREAT 也被設(shè)置, 此指令會去檢查文件是否存在。 
                文件若不存在則建立該文件,否則將導致打開文件錯誤. 
                此外, 若O_CREAT 與O_EXCL 同時設(shè)置,并且欲打開的文件為符號連接, 
                則會打開文件失敗。

  參數(shù)mode僅在flags中含有O_CREAT時有效,設(shè)定新建文文件的打開權(quán)限,有下列數(shù)種組合,
  S_IRWXU             00700 權(quán)限,代表該文件所有者具有可讀、可寫及可執(zhí)行的權(quán)限。
  S_IRUSR 或S_IREAD,  00400 權(quán)限,代表該文件所有者具有可讀取的權(quán)限。
  S_IWUSR 或S_IWRITE, 00200 權(quán)限,代表該文件所有者具有可寫入的權(quán)限。
  S_IXUSR 或S_IEXEC,  00100 權(quán)限,代表該文件所有者具有可執(zhí)行的權(quán)限。
  S_IRWXG             00070 權(quán)限,代表該文件用戶組具有可讀、可寫及可執(zhí)行的權(quán)限。
  S_IRGRP             00040 權(quán)限,代表該文件用戶組具有可讀的權(quán)限。
  S_IWGRP             00020 權(quán)限,代表該文件用戶組具有可寫入的權(quán)限。
  S_IXGRP             00010 權(quán)限,代表該文件用戶組具有可執(zhí)行的權(quán)限。
  S_IRWXO             00007 權(quán)限,代表其他用戶具有可讀、可寫及可執(zhí)行的權(quán)限。
  S_IROTH             00004 權(quán)限,代表其他用戶具有可讀的權(quán)限。
  S_IWOTH             00002 權(quán)限,代表其他用戶具有可寫入的權(quán)限。
  S_IXOTH             00001 權(quán)限,xit代表其他用戶具有可執(zhí)行的權(quán)限。

函數(shù)返回值: 打開文件成功,返回一個文件描述符 >2;打開失敗,返回-1。

提示:使用 access()作用戶認證方面的判斷要特別小心, 例如在access()后再作open()空文件可能會造成系統(tǒng)安全上的問題。

2.使用create()函數(shù)創(chuàng)建并打開文件

函數(shù)原型

     int creat(const char *pathname, mode_t mode);
     相當于使用調(diào)用方式,
     open(const char *pathname, (O_CREAT|O_WRONLY|O_TRUNC));

函數(shù)參數(shù):

  pathname   待打開文件的絕對路徑和文件名。

  mode       新創(chuàng)建文件的權(quán)限,見上面open()

函數(shù)返回值:若成功會返回新的文件描述符,若有錯誤發(fā)生則會返回-1。

提示:creat()無法建立特別的裝置文件,如果需要請使用mknod()。

讀寫文件

1.使用read()函數(shù)從文件中讀取數(shù)據(jù)

  • 手冊文件 man 2 read

函數(shù)頭文件及函數(shù)原型
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

函數(shù)參數(shù):

 fd      文件指針,提供數(shù)據(jù)的文件的文件描述符,讀取的數(shù)據(jù)的來源。

 buf     讀到的數(shù)據(jù)所存放的內(nèi)存空間的起始地址,同時文件的當前讀寫位置向后移。

 count   想要讀取的數(shù)據(jù)的字節(jié)數(shù),也是提供的存儲空間字節(jié)數(shù)。

函數(shù)說明及返回值: read()會把參數(shù)fd 所指的文件傳送count 個字節(jié)到buf 指針所指的內(nèi)存中(暨在[0,count]區(qū)間變化)。

1.若參數(shù)count 為0,則read()不會有作用并返回0。
2.成功時,返回值為實際讀取到的字節(jié)數(shù)。
3.如果返回0,表示已到達文件尾,暨碰到了EOF或是無可讀取的數(shù)據(jù)。
4.此外文件讀寫位置會隨讀取到的字節(jié)移動。
5.有錯誤發(fā)生時則返回-1,而文件讀寫位置則無法預(yù)測。
提示:

read()函數(shù)負責從文件句柄中讀取指定數(shù)量的字節(jié),并將這些字節(jié)放在標量型變量中。read()函數(shù)和標準I/O函數(shù)fread()相同的方式處理I/O緩沖的。為了提高效率,read()函數(shù)并不是一次讀取一個字節(jié),而是讀取一塊數(shù)據(jù)并保存到臨時存儲區(qū)中。然后,C的fread函數(shù)與Perl的read函數(shù)會從臨時緩沖區(qū)將數(shù)據(jù)一次一個字節(jié)地傳送給程序。print()函數(shù)(而不是write()函數(shù)負責輸出read()函數(shù)返回的實際字節(jié)。print()函數(shù)類似于C中的fwrite()函數(shù)。

附加:如果順利 read()會返回實際讀到的字節(jié)數(shù),最好能將返回值與參數(shù)count 作比較,若返回的字節(jié)數(shù)比要求讀取的字節(jié)數(shù)少,則
1. 讀取普通文件時,讀到文件末尾還不夠 nbytes 字節(jié)。例如:如果文件只有 30 字節(jié),
而我們想讀取 100字節(jié),那么實際讀到的只有 30 字節(jié),read 函數(shù)返回 30 。
此時再使用 read 函數(shù)作用于這個文件會導致 read 返回 0 。
2. 從終端設(shè)備(terminal device)讀取時,一般情況下每次只能讀取一行。
3. 從網(wǎng)絡(luò)讀取時,網(wǎng)絡(luò)緩存可能導致讀取的字節(jié)數(shù)小于 nbytes 字節(jié)。
4. 讀取 pipe 或者 FIFO 時,pipe 或 FIFO 里的字節(jié)數(shù)可能小于 nbytes 。
5. 從面向記錄的設(shè)備讀取時,某些面向記錄的設(shè)備(如磁帶)每次最多只能返回一個記錄。
6. 在讀取了部分數(shù)據(jù)時被信號中斷。讀操作始于 cfo 。在成功返回之前,cfo 增加,
增量為實際讀取到的字節(jié)數(shù)。

2.使用write()函數(shù)向指定文件中寫入數(shù)據(jù)
  • 手冊文件 man 2 write

函數(shù)頭文件及函數(shù)原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

函數(shù)參數(shù):

fd      待寫入數(shù)據(jù)的文件的描述符

buf     寫入數(shù)據(jù)的起始地址

count   待寫入的數(shù)據(jù)的字節(jié)數(shù)

函數(shù)說明及返回值: write()會把參數(shù)buf 所指的內(nèi)存寫入count 個字節(jié)到參數(shù)fd 所指的文件內(nèi)。當然,文件讀寫位置也會隨之移動。
如果順利會返回實際寫入數(shù)據(jù)的字節(jié)數(shù),表示寫了部分或者全部的數(shù)據(jù)。
當有錯誤發(fā)生時,返回-1,我們要根據(jù)錯誤的類型來處理。如果錯誤為EINTR表示在寫時出現(xiàn)了中斷錯誤。如果為EPIPE表示網(wǎng)絡(luò)連接出現(xiàn)了問題。

提示:對于普通文件,寫操作始于 cfo 。如果打開文件時使用了 O_APPEND,則每次寫操作都將數(shù)據(jù)寫入文件末尾。成功寫入后,cfo 增加,增量為實際寫入的字節(jié)數(shù)。

定位文件

預(yù)概念: 所有打開的文件都有一個當前文件偏移量(current file offset),以下簡稱為 cfo。cfo 通常是一個非負整數(shù),用于表明文件開始處到文件當前位置的字節(jié)數(shù)。讀寫操作通常開始于 cfo,并且使 cfo 增大,增量為讀寫的字節(jié)數(shù)。文件被打開時,cfo 會被初始化為 0,除非使用了 O_APPEND 。

使用lseek()函數(shù)定位指定已打開文件的讀寫指針

  • 手冊文件 man lseek

函數(shù)頭文件及函數(shù)原型
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

函數(shù)參數(shù):

fd 待重新定位讀寫指針位置的文件的描述符

offset 讀寫指針的偏移量(可正可負可為0)

whence 讀寫指針的偏移位置
    SEEK_SET   相對文件首部偏移,文件偏移量將被設(shè)置為 offset。
    SEEK_CUR   相對文件當前讀寫位置偏移,文件偏移量將被設(shè)置為 cfo 加上 offset,
               offset 可以為正也可以為負。
    SEEK_END   相對文件尾部偏移,文件偏移量將被設(shè)置為文件長度加上 offset,
               offset 可以為正也可以為負。

函數(shù)說明及返回值: 每一個已打開的文件都有一個讀寫位置,當打開文件時通常其讀寫位置是指向文件開頭,若是以附加的方式打開文件(如O_APPEND),則讀寫位置會指向文件尾。當read()或write()時,讀寫位置會隨之增加,lseek()便是用來控制該文件的讀寫位置。參數(shù)fildes 為已打開的文件描述詞,參數(shù)offset 為根據(jù)參數(shù)whence來移動讀寫位置的位移數(shù)。當調(diào)用成功時則返回目前的讀寫位置,也就是距離文件多少個字節(jié)數(shù)。若有錯誤則返回-1。

例:

 將讀寫位置移到文件開頭時: lseek(int fildes, 0, SEEK_SET);
 將讀寫位置移到文件尾時:   lseek(int fildes, 0, SEEK_END);
 想要取得目前文件位置時:   lseek(int fildes, 0, SEEK_CUR);
提示:

1.Linux 系統(tǒng)不允許lseek()對tty 裝置作用,此項動作會令lseek()返回ESPIPE。
2.如果參數(shù) fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 為 ESPIPE。 對于普通文件(regular file),cfo 是一個非負整數(shù)。但對于特殊設(shè)備,cfo 有可能是負數(shù)。因此,我們不能簡單地測試 lseek 的返回值是否小于 0 來判斷 lseek 成功與否,而應(yīng)該測試 lseek 的返回值是否等于 -1 來判斷 lseek 成功與否。
3.lseek 僅將 cfo 保存于內(nèi)核中,不會導致任何 I/O 操作。這個 cfo 將被用于之后的讀寫操作。
4.如果 offset 比文件的當前長度更大,下一個寫操作就會把文件“撐大(extend)”。這就是所謂的在文件里創(chuàng)造"空洞(hole)”。沒有被實際寫入文件的所有字節(jié)由重復(fù)的 0 表示。空洞是否占用硬盤空間是由文件系統(tǒng)(file system)決定的。

關(guān)閉文件

使用close函數(shù)關(guān)閉指定文件

  • 手冊文件 man close

函數(shù)頭文件及函數(shù)原型

 #include <unistd.h>
 int close(int fd);

函數(shù)參數(shù):

fd    為open()或creat()打開的文件描述符。

函數(shù)說明及返回值: 當使用完已打開的文件后若已不再需要則可使用 close()關(guān)閉該文件, 而close()會讓數(shù)據(jù)寫回磁盤, 并釋放該文件所占用的資源. 參數(shù)fd 為先前由open()或creat()所返回的文件描述詞.**返回值:若文件順利關(guān)閉則返回0, 發(fā)生錯誤時返回-1.

提示:雖然在進程結(jié)束時,系統(tǒng)會自動關(guān)閉已打開的文件,但仍建議自行關(guān)閉文件,并確實檢查返回值。

綜合案例

// ./my-cp <src_file> <dst_file>

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_SIZE 100

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        printf("usage : %s <src_file> <dst_file>\n",
            argv[0]);
        return 1;
    }
    
    int src_fd = 0;
    int dst_fd = 0;
    int n = 0;
    char buf[BUFFER_SIZE] = {'\0'};
    char *src_file = argv[1];
    char *dst_file = argv[2];
    
    // 1.open
    // 1.1 以只讀方式打開源文件
    if((src_fd = open(src_file, O_RDONLY)) == -1)
    {
        perror("open src error");
        return 1;
    }
    // 1.2 以只寫方式打開目的文件
    if((dst_fd = open(dst_file, 
            O_WRONLY | O_CREAT | O_TRUNC,
            S_IRUSR | S_IWUSR)) == -1)
    {
        perror("open dst error");
        return 1;
    }
    
    // 2. 循環(huán)從源文件中讀取數(shù)據(jù)寫入到目的文件中
    // 直到讀到源文件的尾部為止
    // 2.1 read data from src_file
    // 2.2 write data to dst_file
    while((n = read(src_fd, buf, BUFFER_SIZE)) > 0)
    {
        write(dst_fd, buf, n);
    }
    
    // 3.close
    close(src_fd);
    close(dst_fd);

    return 0;
}

// 練習:
// 實現(xiàn)一個相對完整版的cp程序,要求能夠判斷出目標文件是否存在。
//  如果存在,給出提示是否覆蓋。
// 思路:
// 1.打開源文件
// 2.判斷目的文件是否存在
// 3.如果目的文件存在,提示是否覆蓋
// 4.如果選擇覆蓋,則以只寫的方式打開文件,并截短文件內(nèi)容(O_TRUNC)
// 5.如果選擇不覆蓋,則提醒輸入新的保存文件名,并已只寫方式打開
// 6.循環(huán)讀取源文件內(nèi)容,寫入到目的文件中
// 7.關(guān)閉已打開的文件

// 思考題1:能否關(guān)閉標準輸入文件、標準輸出文件、標準出錯文件?

參考資料

劉老師上課資料及網(wǎng)上前輩資料
計算機操作系統(tǒng)教程:介紹現(xiàn)代操作系統(tǒng)原理及應(yīng)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容