Linux檔案操作(二)

來源:互聯網
上載者:User
 標準I/O庫
標準I/O庫以及他的標頭檔,提供了一個到底層I/O系統調用的一個萬能介面.這個庫並不是ANSI標準C的一部分,而我們在前面所談到的系統調用也不是,但是這個庫卻提供了許多複雜的函數用來處理格式化輸出以及描述輸入.他同時也會小心的處理裝置所要求的緩衝區.
在許多方式上,我們可以用使用低層檔案描述符的方式來使用這個庫.我們需要開啟檔案建立訪問路徑.這會返回一個值,並會作為一個調用其他I/O庫函數的參數.這個與低層檔案描述符等同的被稱之類流(stream),並且是作為一個指向結構的指標,FILE*,來實現的.
當一個程式啟動時會自動開啟三個檔案流.他們是stdin,stdout,stderr.這些是在stdio.h中定義,分別代表標準輸入.標準輸出和標準錯誤輸出,相對的,他們分別與低層的檔案描述符0,1,2相對應.
在下一部分中,我們將會看到下面的一些內容:
fopen, fclose
fread, fwrite
fflush
fseek
fgetc, getc, getchar
fputc, putc, putchar
fgets, gets
printf, fprintf, sprintf
scanf, fscanf, sscanf
fopen
fopen庫函數是低層的open系統調用的類比.我們主要將他用於檔案或是終端輸入與輸出.然而在我們需要顯示的控制裝置的地方,我們最好是使用低層的系統調用,因為他們可以削除由庫所造成的潛在的不良因素,如輸入/輸出緩衝區.
其文法格式如下:
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
fopen開啟由filename參數所指定的檔案,並建立一個與其相關的流.mode參數指出如何來開啟這個檔案.他可以是下列字串中的一個:
"r"或"rb":以唯讀方式開啟
"w"或"wb":以唯寫方式開啟
"a"或"ab":以讀方式開啟,添加到檔案的結尾處
"r+"或"rb+"或"r+b":開啟更新(讀和寫)
"w+"或"wb+"或"w+b":開啟更新,將其長度變為零
"a+"或"ab+"或"a+b":開啟更新,添加到檔案結尾處
b表明這個檔案是二進位檔案而不是文字檔.
在 這裡我們要注意,與MS-DOS不同,Unix和Linux並不會在文字檔與二進位檔案之間進行區別.Unix與Linux將所有檔案看成是一樣的,尤 其是二進位檔案.另外要注意的一點就是mode參數必須是一個字串,而不是一個字元.我們要總是使用"r",而絕不可以是'r'.
如果函數調用成功,fopen會返回一個非空的檔案指標.如果失敗,他會返回NULL,這是在stdio.h中定義的.
fread
fread 庫函數可以用來從一個檔案流中讀取資料.由stream流中讀取的資料將會放在由prt所指定的資料緩衝區中.fread和fwrite都處理資料記錄. 這些是由塊的尺寸size,讀取的次數nitems來指定要傳送的記錄塊的.如果成功則傳回值為實際讀入到資料緩衝區中的塊數,而不是位元組數.在檔案的結 尾處,也許會返回少於nitems的值,包括零.
其文法格式如下:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
與所有要寫入到緩衝區中的標準I/O函數一樣,程式員要負責分配資料空間以及檢查錯誤.
fwrite
fwrite調用一個與fread相類似的函數介面.他將會從指定的資料區讀取資料記錄並寫入到輸出資料流中.他的傳回值為成功寫入的記錄數.
其文法格式如下:
#include <stdio.h>
size_t fwrite (const void *ptr, size_t size, size_t nitems, FILE *stream);
在這裡我們要注意就是我們並不推薦在使用結構資料使用fread與fwrite.一部分的原因就是因為用fwrite寫入的檔案潛在的存在著在不同的機器間不相容的問題.
fclose
fclose 庫函數關閉指定的檔案流,並將所有未寫入的資料寫入檔案中.使用fclose是相當重要的,因為stdio庫會快取資料.如果程式需要確定已經完整的寫入 了所有的資料,這時就應調用fclose.然而,當一個程式正常結束時,fclose就自動調用,從而關閉所有仍然開啟的檔案流.當然,在這樣的情況下, 我們就沒有機會來檢查由fclose報告的錯誤.與檔案描述符所有的限制一樣,可用的流數目也是有限制的.實際的限制是FOPEN_MAX,這是在 stdio.h中定義,而且至少為8個.
其文法格式如下:
#include <stdio.h>
int fclose(FILE *stream);
fflush
fflush 庫函數可以使得所有未寫入檔案流中的資料立刻寫入檔案流中.例如,我們可以使用這個函數來保證在試圖讀取一個輸入之前已經將互動提示發送到了終端.這個函 數對於保證在繼續操作之前已經將所有重要的資料寫入了磁碟檔案.在偵錯工具時我們有時也可以用這個函數來保證程式正在寫入檔案而不是在進行空操作.另外我 們要注意的一點就是當我們調用fclose時會隱含地調用flush操作,所以我們在fclose之前並不需要調用fflush.
其文法格式如下:
#include <stdio.h>
int fflush(FILE *stream);
fseek
fseek 函數是與lstat系統調用作用等同的一個函數操作.他會設定在這個檔案流中下一次要讀或是寫的位置.其中offset與whence的含義與用法與我們 在前面所提到的lseek的用法相同.然而lseek返回的是off_t,而fseek則會返回一個整數:如果成功,則返回0,失敗則返回-1,並且使用 errno來表明錯誤.所以這就會更為的標準.
其文法格式如下:
#include <stdio.h>
int fseek(FILE *stream, long int offset, int whence);
fgetc,getc,getchar
fgets函數會從一個檔案流中作為一個字元返回下一個位元組.當他到達檔案結尾處或是有錯誤發生時,則會返回EOF.我們必須使用ferror或是feof來辨別這兩種情況.
其文法格式如下:
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();
getc 函數與fgetc函數作用相同,所不同的只是前者是作為一個宏來實現的,在這種情況下,stream參數必須沒有邊界影響(side effects)(例如:他不可以影響局部變數或是作為參數傳遞給函數的變數).同時,我們也不可以使用getc的地址作為一個函數指標.
getchar函數與getc(stdin)的作用相同並且從標準輸入讀取下一個字元.
fputc,putc,putchar
fputc函數向輸出檔案流中寫入一個字元.他會返回他寫入的值,如果失敗則為EOF.
其文法格式如下:
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
與fgetc/getc相類似,putc函數的作用與fputc相同,但是也許他會作為一個宏來實現.GNU C編譯器就是這樣做,而且我們可以在stdio.h標頭檔中看到他的定義.
putchar 函數與putc(c,stdout)的作用相同,是向標準輸出寫入一個單獨的字元.在這裡我們要注意的putchar是將字元作為整數而不是字元進行處 理,這與getchar的返回字元的結果相同.這樣就可以允許檔案結束標記符(EOF)在超出字元數目邊界時使用-1進行標記.
fgets,gets
fgets 函數從輸入檔案流中讀取一串字元.他會將所讀到的字元放入由s所指向的位置,直到碰到一個新行,其傳輸了n-1個字元,或者是遇到檔案結尾時返回,而這是 會首先發生的情況.任何遇到的分行符號都會傳送到接收字串,並且會添加一個結束位元組/0.在任何一次調用中最多隻可以傳輸n-1個字元,因為必須要添加一 個空位元組來結束字元從而構成n個字元.
其文法格式如下:
#include <stdio.h>
char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);
如果函數調用成功,則會返回一個指向s的指標.如果這個流到達了檔案結尾,他會為流設定EOF標記符並且會返回一個null 指標.如果遇到讀錯誤發生,fgets會返回一個null 指標並且設定errno來表明錯誤類型.
gets函數與fgets相類似,所不同的只是前者是從標準輸入讀取字串並且會忽略任何分行符號.他會在接收到的字元中添加一個結束符.
在這裡我們要注意的是gets函數並不會限制所傳輸的字元數,所以他會超出他們的傳輸緩衝區.所以,我們要避免使用這個函數並且要使用fgets函數進行替換.所以我們要小心使用這個函數.
格式化輸入與輸出
有許多的庫函數可以按我們所希望的方式產生輸出,而如果我們有過一些C語言編程的經驗,我們就會對於這些格式感到熟悉.這些函數包括prinf以及其他的一些向檔案流中寫入資料的函數以及scanf和其他的一些函數從檔案流中讀取資料的函數.
printf,fprintf,sprintf
printf函數家族可以格式化並輸出不同類型的變數參數.在輸出資料流中所代表的每一個函數的工作方式是由format參數來控制的,這個參數包含要列印的普通的字串和代碼,也就是稱之為逸出字元的部分,這些用來表明要如何和在哪裡列印其餘的參數.
其文法格式如下:
#include <stdio.h>
int printf(const char *format, ...);
int sprintf(char *s, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
printf 函數在標準輸出上產生他的輸出.fprintf函數在一個指定的流上產生他的輸出,而sprintf函數會將他的輸出與一個結束Null 字元寫入字串s,而s 是作為參數來進行傳遞的.這個字元必須足夠的大來包含所有的輸出.另外還有一些printf函數家族中其他的函數可以用來以不同的方式來處理不同的參數. 我們可以通過查看printf手冊頁得到更為詳細的內容.
普通的字元在傳遞到輸出後並不會發生改變.逸出字元會使得printf取回並格式化傳遞的其餘參數.他們能通常是以%字元開頭的.如下面的一些例子:
printf(“Some numbers: %d, %d, and %d/n”, 1, 2, 3);
他在標準輸出的結果如下:
Some numbers: 1, 2, and 3
如果我們要列印一個%字元,我們必須使用%%,這樣就不會與一個逸出字元發生混淆了.
下面是一些最常的逸出字元:
%d,%i:以十進位數列印一個整數
%o,%x:以八進位,十六進位數列印
%c:列印一個字元
%s:列印一個字串
%f:列印一個浮點數(單精確度數)
%e:以定點數的格式列印一個雙精確度數
%g:以普通格式列印一個雙精確度數
在format 字串傳遞給printf函數與逸出字元相匹配的參數類型和個數是非常重要的.一個可選的尺寸標識可以用來表明整數參數的類型.這個可以是h,例如,% hd用來表明short int,或是可以是l,例如,%ld用來表明long int.一些編譯器可以檢查printf的這些參數,但是他們並不是絕對可靠的.如果我們正在使用GNU編譯器gcc,我們可以使用-Wformat來做 到這一點.
如下面的例子:
char initial = ‘A’;
char *surname = “Matthew”;
double age = 14.5;
printf(“Hello Miss %c %s, aged %g/n”, initial, surname, age);
這個例子的結果如下:
Hello Miss A Matthew, aged 14.5
如果我們使用域標識,我們就可以更多的控制列印的方式.這些擴充了逸出字元從而可以控制輸出中的空格.一個常用的用法是可以用來為浮點數的列印指定一個十進位數的空間或是為一個字串指定一個列印的空間.
域標識是在逸出字元的%字元後面以數位方式來指定的.下面的這個表中包含了一些逸出字元的例子以及他們的輸出結果.
Format Argument       |     Output     |
%10s   “Hello”        |            Hello|
%-10s  “Hello”        |Hello             |
%10d   1234            |             1234|
%-10d  1234              |1234              |
%010d  1234            |0000001234        |
%10.4f 12.34           |         12.3400|
%*s    10,”Hello”      |            Hello|
所 有的這些例子以10個字元的寬度進行列印.在這裡我們要注意就是在域寬度中負數用來表明列印的內容要靠左對齊.一個變化的地區寬度可以用一個萬用字元*來指 定.在這樣的情況下,下一個參數用來指定寬度.開頭的0用來要列印的內容以0開頭的.根據POSIX的說明,printf函數並不會截斷一個要列印的域, 而是進行擴充來進行填充.所以,如果我們要列印一個比我們所指定的域長的內容,那麼這個域會進行增長.
如下面的表格所示:
Format Argument              |     Output   |
%10s   “HelloTherePeeps”    |HelloTherePeeps|
printf會返回一個整數,用來表明寫入的字元個數.這在sprintf函數中並不包括結束字元null.如果出現錯誤,則會返回一個負數並且會設定errno.
scanf,fscanf,sscanf
scanf函數家族的工作方式也printf組的工作方式相類似,所不同的只是這些函數是從一個流中讀取內容或者是在作為參數傳遞的指標地址處放置變數值.他們以同樣的方式使用格式字串來控制輸入轉換,而這些逸出字元中的許多都是相同的.
其文法格式如下:
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *s, const char *format, ...);
在這裡很重要的一點就是用來存放由scanf函數所讀入值的變數必須是正確的類型而且必須與格式字串進行精確的匹配.如果不是這樣,我們的記憶體就會泄漏而我們的程式就可能崩潰.這些並不會出現編譯錯誤,如果我們幸運的話,有可能會得到警告資訊.
scanf及其相關的函數的格式字串包含有一般字元和逸出字元,而這些與printf相類似.然而一般字元是用來指定必須在輸入中出現的字元.
如下面的一個簡單的例子:
int num;
scanf(“Hello %d”, &num);
這 個scanf調用只有當在標準輸入中的下五個字元與Hello匹配才會成功.然後,如果下一個字元形成一個可以識別的十進位數,這個數就會被讀入而他的值 將會賦給變數num.在格式字串中的空格是用來忽略輸入中逸出字元之間的任何空格符(空格,Tab,或是新行).這就意味著如果我們指定下面的輸入形式 中的任何一個都會成功並會將1234存入變數num中:
Hello     1234
Hello1234
在通常情況下,當轉義開始時,空格符也會被忽略掉.這就意味著格式字串%d將會一直從輸入中讀入,跳過任何空格以及新行直到發現一個數字充列.如果沒有出現所希望的字元,轉義就會失敗,scanf函數返回.
如果我們不小心這樣就會導致問題,如果在我們的程式中讀入整數而輸入中並沒有數字字元,這樣就會導致一個死迴圈.
其他的一些逸出字元如下:
%d:讀入一個十進位整數
%o,%x:讀入一個八進位,十六進位整數
%f,%e,%g:讀入一個浮點數
%c:讀入一個字元(並不會跳過空格)
%s:讀入一個字串
%[]:讀入一個字元集
%%:讀入一個%字元
與printf 相類似,scanf逸出字元也有一個寬度域來限制輸入數量.一個尺寸標識(h代表shor,而l代表long)一個正接收的參數是否短於或是長於預設的情 況.這就意味著%hd表示short int,而%ld代表long int,%lg代表前面所說的雙精確度浮點數.
一個標識符如果以*開始則表明所有的內容都會被忽略掉.這就意味著所輸入的資訊並不會被儲存,所以我們也就並不需要一個變數來進行接收.
我們使用%c來從輸入中讀取一個單一的字元,這並不會跳過初始的空格符.
我 們使用%s來讀取一個字串,但是我們必須小心.他會跳過開頭的空格符,但是卻會停在字串中的第一個空格符處.所以我們最好使用他來讀取一個單詞而不是 通常的字串.同時沒有指定地區寬度標識符,所以他可能讀取的字串的長度並沒有限制,所以這個接收字串必足夠的大來存放輸入資料流中最長的字串.我們最 好使用地區寬度標識,或者是組合使用fgets和sscanf來讀入一行輸入.這樣就可以盡量防止懷有惡意的使用者所造成的緩衝區溢位.
我們使用% []標識可以讀入由一個字元集合所組成的字串.格式串%[A-Z]可以讀入有大寫字母組成的字串.如果在這個集合中的第一個字元為^,那麼則會讀入由 不在集合中的字元所組成的字串.所以如果要讀入含有空格但是卻在第一個逗號處結束的字串,我們可以格式串%[^,].
我們可以輸入下面的輸入行:
Hello, 1234, 5.678, X, string to the end of the line
這個scanf調用會正確的讀入四個內容:
char s[256];
int n;
float f;
char c;
scanf(“Hello,%d,%g, %c, %[^/n]”, &n,&f,&c,s);
scanf函數會返回他所成功讀取的內容數,如果第一個內容失敗則會返回零值.如果與第一個內容匹配之前已經到達輸入的結尾,則會返回EOF.如果在檔案流上發生讀錯誤,則會設定檔案流錯誤標記,而錯誤變數將errno將會進行設定來表明錯誤類型.
在通常的情況下,scanf以及一些相關的函數並不會推薦使用,這是由於下面的三個原因:
傳統的原因是因為這些函數的實現存在一些bug
他們的使用並不靈活
他們會使得正是分析的程式難於理解.
我們可以試著使用一些其他的函數,如fread或是fgets.
其他的一些流函數
還有許多其他的stdio的庫函數,這些函數或者使用流參數,或者是使用標準的stdin,stdout,stderr參數:
fgetpos:得到在檔案流中的當前地址
fsetpos:在檔案流中設定當前的地址
ftell:返回一個流中當前檔案的位移量
rewind:在一個流中重新設定檔案地址
frepoen:重用一個檔案流
setvbuf:為一個流設定緩衝方案
remove:與unlink等價,所不同的只是其參數為一個目錄,在這種情況下,他與rmdir的作用相同
這些庫函數都在手冊頁中的第三部分有詳細的說明.
我們現在可以使用檔案流函數來重新實現一個檔案複製的程式,在這裡我們要使用庫函數.我們來看一下下面的例子程式copy_stdio.c.
這個程式與前面的一個版本的程式相類似,但是現在這裡的一個字元一個字元的拷貝是由stdio.h中的函數引用來實現的:
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int c;
    FILE *in, *out;
    in = fopen(“file.in”,”r”);
    out = fopen(“file.out”,”w”);
    while((c = fgetc(in)) != EOF)
      fputc(c,out);
    exit(0);
}
如果我們要像前面一樣來運行這個程式,我們可以得到下面的輸出資訊:
$ TIMEFORMAT=”” time copy_stdio
0.29user 0.02system 0:00.35elapsed 87%CPU
...
這 一次,這個程式的已耗用時間為0.35秒,並不如底層塊版本的運行速度快,但是這要比一次拷貝一個字元的版本有了很大的改進.這是因為stdio庫使用 FILE結構來維護一個內部的緩衝區,而且只有這個緩衝區滿時才會調用底層的系統調用.我們可以使用一行一行的拷貝與塊拷貝來實現,從而可以與我們這裡運 行的幾個版本進行相應的比較.
流錯誤
要標識一個錯誤,許多的stdio庫函數會返回一個越界的值,例如null 指標或者是定值EOF.在這些情況下,這些錯誤是由外部的變數errno來標識的:
#include <errno.h>
extern int errno;
在這裡我們要注意的,許多的函數會改變errno的值.只有當一個函數的調用失敗時,他的值才是可用的.我們應在一個函數標識失敗後立刻檢測errno的值.我們應在使用他之前要將他的值拷貝到另一個變數中,因為一些列印函數,如fprintf也許會修改他的值.
我們也可以通過檢測檔案流的狀態來決定是否發生了錯誤,或者是已經達到檔案結尾.
#include <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);
ferror函數會為一個流檢測錯誤標識符,如果進行了設定則會返回零值,否則返回非零值.我們可以像下面的樣子來使用這個函數:
if(feof(some_stream))
    /* We’re at the end */
clearerr函數會清除stream指標所指的檔案流的檔案結束或是錯誤標識符.這個函數並沒有傳回值也沒有定義的錯誤.我們可以使用這個函數來在流上由錯誤條件進行恢複.這個函數應的一個例子也許就是當發生磁碟滿時會將資料重新寫入檔案流中.
流與檔案描述符
每一個檔案流都是與底層的檔案描述符相對應的.我們可以混合使用底層的輸入和輸出與高層的檔案流操作,但是通常而言這是不明智的,因為緩衝區的影響是不可預知的.
#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fildes, const char *mode);
我們可以通過調用fileno函數來得知一個檔案流正在使用哪一個底層的檔案描述符.他會為指定的檔案流返回一個檔案描述符,如果失敗則會返回-1.如果我們需要底層的訪問一個開啟的流,我們可以使用這個函數,如使用fstat.
我們可以通過調用fdopen函數來在一個已經開啟的檔案描述符的基礎上建立一個新的檔案流.實質上,這個函數會為一個已經開啟的檔案描述符提供一個stdio的緩衝區,這也許會是一個用來進行解釋的一個較為簡單的方式.
fdopen 函數與fopen的操作方式相類似,所不同的只是他所使用的為一個底層的檔案描述符.如果我們需要使用open來建立一個檔案,也許是為了更好的許可權控 制,但是卻希望使用檔案流進行寫操作時,這個函數就會顯得尤為有用.mode參數與fopen函數的參數相同,而且必須與這個檔案最初開啟時所建立的檔案 訪問方式相相容.fdopen會返回一個新的檔案流,如果失敗則會返回NULL.
檔案與目錄維護
標準庫與系統調用對於檔案的建立與維護提供了完全的控制.
chmod
我們可以使用chmod系統調用改變一個檔案或是目錄的許可權.這構成了Shell編程的基本內容.
其文法如下:
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
由path所指定的檔案將會具有由mode所指定的許可權.在這裡所指定的mode與open系統調用中的相同,是一個要求的權限的位或.除非是這個程式被指定了合適的許可權,否則只有這個檔案的所有者或是超級使用者才可以改變他的許可權.
chown
超級使用者可以使用chown系統調用來改變一個檔案的所有者.
其文法如下:
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
這個調用使用使用者ID或是組ID的數值(可以由getuid和getgid調用得到)和一個常量來誰可以來改變檔案的所有者.如果設定了合適的許可權我們就可以改變一個檔案的所用者和所屬的組.
unlink,link,symlink
我們可以使用unlink來移除一個檔案.
unlink可以為一個檔案移除目錄實體並減少他的串連數量.如果函數調用成功則返回0,失敗則會返回-1.我們必須在所要執行命令的目錄中有寫和執行的許可權,因為檔案對於這個函數調用有他自己的目錄實體.
其文法如如下:
int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);
如 果串連數量達到0而沒有進程開啟檔案,這個檔案則會被刪除.事實上,一個目錄實體總是會被刪除,但是這個檔案的空間並不會被回收,直到關閉最後一個相關的 進程.rm程式使用這個調用.在通常情況下我們可以使用ln程式來為一個檔案建立一個連結.我們可以使用link系統為一個檔案有計劃的建立連結.
link系統調用為一個已存在的檔案path1建立一個新的連結.新的目錄實體是由path2來指定的.我們可以用相類似的方式使用symlink來建立一個符號連結.在這裡我們要注意的就是一個檔案的符號連結不會像永久連結那樣阻止一個檔案的刪除.
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.