- 論壇徽章:
- 0
|
翻譯自——GNULinux應(yīng)用程序編程(GNU/Linux Application Programming) by M.Tim Jones
第十章:在GNU/Linux當中的文件操作
本章內(nèi)容:
理解在GNU/Linux當中的文件操作APIs
探討字符獲取機制
探討字符串獲取機制
研究有序和無序(隨機獲。┓椒
回顧文件獲取的交替APIs和方法
簡介
在這一章當中,我們將會學習GNU/Linux的文件操作APIs以及研究一些能夠舉例說明文件操作APIs的正確用法的應(yīng)用程序。我們將會學習一些不同方式的文件操作函數(shù),包括字符接口、字符串接口以及ASC||模式和二進制接口。本章將著重于APIs的討論并通過它們在應(yīng)用程序中的使用來舉例說明它們的用法。
GNU/Linux中的文件操作
GNU/Linux中的文件操作是通過標準C庫來完成的。我們可以通過使用相同的API函數(shù)來創(chuàng)建并操作ASC||文本文件或者二進制文件。我們可以對文件進行添加或者在文件當中進行查找。
在本章當中,我們將學習函數(shù)調(diào)用fopen(打開或者創(chuàng)建一個文件)、fwrite和fread(對文件進行讀寫操作)、fseek(在一個已經(jīng)存在的文件當中定位于一個指定的位置)、feof(在讀文件的時候測試文件指針是否處于文件的末尾)已經(jīng)其它的低級別的調(diào)用(如:open、write以及read)。
文件操作API探秘
現(xiàn)在讓我們親自動手來完成GNU/Linux流文件I/O編程的一些例子。
創(chuàng)建一個文件句柄
要寫一個執(zhí)行文件操作的應(yīng)用程序,我們首先必須使文件I/OAPIs可見。通過簡單地包含頭文件stdio.h我們就可以做到這一點,比如:
#include
不這么做的話將會產(chǎn)生編譯錯誤(未經(jīng)聲明的符號)。第二步則是聲明我們的將用于進行文件I/O操作中的句柄。這個句柄一般為一個文件指針,文件指針是一個不應(yīng)該能夠被開發(fā)者獲取的透明的結(jié)構(gòu)體。
FILE *my_fp;
在下面的部分當中我們將基于這一點來舉例說明ASC||以及二進制應(yīng)用程序。
打開一個文件
現(xiàn)在讓我們來打開一個文件來舉例說明不同的可用的模式;仡櫸覀兦懊嫠f的,打開一個文件的機制和創(chuàng)建一個文件的機制是一樣的。我們將首先來研究這一點。
函數(shù)fopen是十分簡單的,它提供了如下的API:
FILE *fopen( const chat *filename, const char *mode);
通過第一個參數(shù)filename我們指定了我們想要獲取或者創(chuàng)建的文件名,然后通過mode指定了我們想要使用的模式。操作fopen產(chǎn)生的結(jié)果是一個FILE指針(它可以為NULL——意味著操作失。。
操作fopen的關(guān)鍵點在于我們所提供的讀寫模式。表格10.1提供了一個初始化的獲取模式的表單。
表格10.1:簡單的文件讀寫模式
模式 描述
r 為讀取文件打開一個已經(jīng)存在的文件
w 為寫文件打開一個文件(如果文件不存在就創(chuàng)建一個新的)
a 為添加文件而打開一個文件(如果文件不存在就創(chuàng)建一個新的)
aw 為讀寫文件而打開一個文件(如果文件不存在就創(chuàng)建一個新的)
讀寫模式僅僅是fopen函數(shù)用來決定怎么打開或者創(chuàng)建文件的一個字符串。如果我們想要創(chuàng)建一個新的文件,我們只需要簡單地按如下方式來使用函數(shù)fopen:
my_fp = fopen( “myfile.txt”, “w”);
產(chǎn)生的結(jié)果將是一個為寫操作做好了準備的新創(chuàng)建(同時也覆蓋掉了原有的文件)的文件。相反,如果我們想要對一個業(yè)已存在的文件進行讀操作,我們可以按如下方式來打開:
my_fp = fopen( “myfile.txt”, “r”);
注意:在這里我們只是簡單地使用了一個不同的讀寫模式。讀模式假定了文件已經(jīng)存在,如果文件不存在的話,函數(shù)將會返回一個NULL。
在兩種情況之下都是這么假定的:我們的文件myfile.txt不是業(yè)已在當前目錄當中就是將會創(chuàng)建于當前目錄之中。當前目錄是我們調(diào)用我們所寫的應(yīng)用程序的所在之地。
檢查I/O操作的結(jié)果是否成功是十分重要的。對于函數(shù)fopen來說,我們簡單地測試了它的返回是否為NULL。對錯誤進行什么樣的處理是由程序決定的(由你個人決定的)。清單10.1提供了一種錯誤處理的機制的例子:
清單10.1: 在一個fopen調(diào)用當中獲取一個錯誤
1: #include
2: #include
3: #include
4:
5: #define MYFILE "missing.txt"
6:
7: main()
8: {
9:
10: FILE *fin;
11:
12: /* Try to open the file for read */
13: fin = fopen( MYFILE, "r" );
14:
15: /* Check for failure to open */
16: if (fin == (FILE *)NULL) {
17:
18: /* Emit an error message and exit */
19: printf("%s: %s\n", MYFILE, strerror( errno ) );
20: exit(-1);
21:
22: }
23:
24: /* All was well, close the file */
25: fclose( fin );
26:
27: }
28:
在清單10.1,我們使用一對新的沒有介紹過的函數(shù)調(diào)用。在13行試圖打開一個文件之后,我們通過檢查來看一看新的文件句柄是否為NULL(0)。如果為NULL的話,我們就可以知道文件不存在或者我們不能夠獲取文件(我們沒有采用合適的方式來獲取文件)。在這樣的情況之下,我們顯示了一個由我們想要進行讀操作而嘗試打開的文件已經(jīng)隨后產(chǎn)生的錯誤消息組成的錯誤信息。我們通過變量errno捕獲錯誤代碼。變量errno是一個系統(tǒng)調(diào)用設(shè)置的暗示最后一個出現(xiàn)的錯誤的特殊變量。我們將這個值傳遞給一個能夠?qū)⒄麛?shù)錯誤數(shù)字轉(zhuǎn)換為一個適合在標準輸出上打印的字符串的函數(shù)——strerror。執(zhí)行我們的程序樣例將會得到如下結(jié)果:
$ ./app
Missing.txt: No such file or directory
$
現(xiàn)在讓我們轉(zhuǎn)到從一個文件當中讀寫數(shù)據(jù)上來。
讀寫數(shù)據(jù)
對一個文件進行讀寫有幾種方法。擁有多個選擇是很不錯的,但是在什么樣的對方使用什么樣的機制也是十分重要的。例如,我們可以以字符為單位來進行讀寫,亦可以以字符串為單位來進行讀寫(只對于ASC||文本文件可行)。我們還可以使用一個更為普遍的方式(同時支持ASC||以及二進制文件)來允許讀寫數(shù)據(jù)。在這里我們將會學習每一種方法,但是我們的著重點將會落在后一個機制上。
標準輸入輸出庫有一個緩沖接口。它有兩個十分重要的特點。第一,系統(tǒng)以塊為單位進行讀寫(塊的大小一般為8KB)。字符I/O只是簡單地被寫到FILE緩沖器(當緩沖器滿時,其內(nèi)容將會自動地寫到媒介上)上。第二,fflush是必要的,或者如果數(shù)據(jù)被發(fā)送到一個交互式設(shè)備(比如控制臺終端)上時必須設(shè)置為無緩沖I/O。
字符接口
我們將于清單10.2和10.3中舉例說明字符接口。在清單10.2當中,我們舉例說明了如何使用fputc來進行字符輸出。而在清單10.3當中,我們則介紹了如何使用fgetc來進行字符輸入。這些函數(shù)擁有如下原型:
int fputc( int c, FILE *stream);
int fgetc( FILE * stream);
在這個例子當中我們產(chǎn)生了一個使用fputc的輸出文件然后將這個文件用作fgetc的輸入。在清單10.2當中,我們在11行打開我們的輸出文件然后向文件輸出我們的樣例字符串。我們的簡單的循環(huán)將會遍歷整個字符串直到發(fā)現(xiàn)NULL,此時我們將退出以及關(guān)閉文件(21行)。在16行,我們使用fputc來顯示我們的字符(作為一個int整數(shù),根據(jù)fputc原型)同時指定我們的輸出流(fout)。
清單10.2: fpuc字符接口樣例
1: #include
2:
3: int main()
4: {
5: int i;
6: FILE *fout;
7: const char string[]={"This\r\nis a test\r\nfile.\r\n\0"};
8:
9: fout = fopen("inpfile.txt", "w");
10:
11: if (fout == (FILE *)NULL) exit(-1);
12:
13: i = 0;
14: while (string != NULL) {
15:
16: fputc( (int)string, fout );
17: i++;
18:
19: }
20:
21: fclose( fout );
22:
23: return 0;
24: }
25:
使用字符接口來讀取這個文件的函數(shù)將會在清單10.3當中展示。這個函數(shù)和我們的文件創(chuàng)建的樣例是十分地類似的。我們在第八行打開文件來進行讀操作,在這之后的第十行有一個測試。然后我們進入一個循環(huán)從文件中來獲取字符(12—22行)。循環(huán)只不過是使用了fgetc來從文件當中獲取字符,然后當遇到特殊字符EOF的時候?qū)V。EOF暗示了我們——這是文件的末尾了。對于所有的不是EOF的字符(16行),我們將會使用函數(shù)printf來顯示字符到標準輸出當中去。當文件內(nèi)指針到了文件的末尾時,我們將會在24行使用fclose來關(guān)閉文件。
清單10.3: fgetc字符接口樣例
1: #include
2:
3: int main()
4: {
5: int c;
6: FILE *fin;
7:
8: fin = fopen("inpfile.txt", "r");
9:
10: if (fin == (FILE *)0) exit(-1);
11:
12: do {
13:
14: c = fgetc( fin );
15:
16: if (c != EOF) {
17:
18: printf("%c", (char)c);
19:
20: }
21:
22: } while (c != EOF);
23:
24: fclose( fin );
25:
26: return 0;
27: }
28:
執(zhí)行了我們這個程序之后將會得到如下結(jié)果:
$ ./charout
$ ./charin
This
is a test
file.
$
很明顯,字符接口十分地簡單,同時它們的效率亦十分低。我們應(yīng)該在應(yīng)該基于字符串的方法不能夠使用的時候才來使用它們。下一次我們再來看一看字符接口。
字符串接口
在這一部分當中,我們將來學習用于提供讀寫字符串的四個庫函數(shù)。前兩個(fputs和fgets)是簡單的字符串接口,而后面兩個(fprintf和fscanf)是更為復雜的提供了額外功能的字符串接口。
接口fputs和fgets借鑒了我們前面討論的fputc以及fgetc這兩個函數(shù)。它們提供了以應(yīng)該十分簡單的方式來進行讀寫字符串(variable-length)的方法。它們的原型如下:
int fputs( int c, FILE *stream );
char *fgets( char *s, int size, FILE *stream );
讓我們首先來看一看一個從用戶(從標準輸入)接受字符串的樣例程序(清單10.4),然后將它們寫入一個文件。一旦程序接收了一個空行,我們將停止輸入進程。
清單10.4: 將字符串寫入文件
1: #include
2:
3: #define LEN 80
4:
5: int main()
6: {
7: char line[LEN+1];
8: FILE *fout, *fin;
9:
10: fout = fopen( "testfile.txt", "w" );
11: if ( fout == (FILE *)0 ) exit(-1);
12:
13: fin = fdopen( 0, "r" );
14:
15: while ( (fgets( line, LEN, fin )) != NULL ) {
16:
17: fputs( line, fout );
18:
19: }
20:
21: fclose( fout );
22: fclose( fin );
23:
24: return 0;
25: }
在清單10.4當中的程序有一點小聰明。讓我們一行一行地預(yù)排這個程序解釋每一個要點。最奇怪的是我們在第七行聲明了我們的字符串line(用來讀取用戶的輸入)。然后我們聲明了兩個FILE指針,一個用于輸入(fin),一個用于輸出(fout)。
在第十行,我們使用函數(shù)fopen打開了我們的輸出文件——一個新的文件(testfile.txt)。我們在第十一行檢查了這一行的錯誤狀態(tài),如果出現(xiàn)錯誤而失敗的話就退出。在第十三行,我們使用了一個特別的函數(shù)fdopen來關(guān)聯(lián)一個存在的帶有一個流的文件描述符。在這樣的情況之下,我們將標準輸入描述符in和帶有一個新的流的fin(由fdopen返回)關(guān)聯(lián)了起來。現(xiàn)在無論我們在標準輸入當中輸入了什么,我們的輸入將會發(fā)送到這個文件流fin當中去。接下來,我們進入了一個嘗試要從流fin(標準輸入)當中讀取并將其所讀取寫到輸出流(fout)當中去的循環(huán)。在第十五行,我們使用fgets來進行讀取并用NULL來檢查fgets的返回。當我們關(guān)閉描述符(在鍵盤上按下Ctrl+D就可以搞定)的時候,NULL就會出現(xiàn)。然后提供使用fputs就可以將line讀入的顯示到輸出流當中去。最后,當輸入流被關(guān)閉時,我們退出我們的循環(huán)并且在第二十一行以及第二十二行關(guān)閉兩個流。
現(xiàn)在讓我們來看看另一個讀取的樣例,fgets。在這個樣例當中(清單10.5),我們使用fgets讀取了我們的test文件的內(nèi)容,然后將它打印到本章輸出當中去。
清單10.5: 從一個文件當中讀取字符串 &n
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u/6957/showart_349618.html |
|