本文的編寫主要是在了解,Linux系統調用和C語言庫函數的基礎上進行的編寫代碼。
這篇文章將講解Linux以下的系統調用:open()、read()、write()、close()、lseek()。涉及到的c語言庫函數:fopen()、fread()、fwrite()、fclose()、flseek()。
用Linux系統調用和C語言庫函數 兩種方式實現文件拷貝
采用Linux系統調用實現文件拷貝
1.open()
用open函數可以打開或創建一個文件

1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 int open(const char *pathname , int oflag,.../*, mode_t mode * / ) ; 5 6 // 返回:若成功為文件描述符,若出錯為- 1
pathname 是要打開或創建的文件的名字。oflag參數可用來說明此函數的多個選擇項。用下列一個或多個常數進行或運算構成oflag參數(這些常數定義在<fcntl.h>頭文件中):
•O_RDONLY 只讀打開。
•O_WRONLY 只寫打開。
•O_RDWR 讀、寫打開。
在這三個常數中應當只指定一個。下列常數則是可選擇的:
•O_APPEND 每次寫時都加到文件的尾端。
•O_CREAT 若此文件不存在則創建它。使用此選擇項時,需同時說明第三個參數mode,用其說明該新文件的存取許可權位。
•O_EXCL 如果同時指定了O_CREAT,而文件已經存在,則出錯。這可測試一個文件是否存在,如果不存在則創建此文件成為一個原子操作。
•O_TRUNC 如果此文件存在,而且為只讀或只寫成功打開,則將其長度截短為0。
•O_NOCTTY 如果pathname指的是終端設備,則不將此設備分配作為此進程的控制終端。
•O_NONBLOCK 如果pathname指的是一個FIFO、一個塊特殊文件或一個字符特殊文件,則此選擇項為此文件的本次打開操作和后續的I/O操作設置非阻塞方式。
•O_SYNC 使每次write都等到物理I/O操作完成。
2.read()
用read函數從打開文件中讀數據。

#include <unistd.h> ssize_t read(int filedes, void *buff, size_t nbytes) ; //返回:讀到的字節數,若已到文件尾為0,若出錯為-1
如read成功,則返回讀到的字節數。如已到達文件的尾端,則返回0。
有多種情況可使實際讀到的字節數少於要求讀字節數:
• 讀普通文件時,在讀到要求字節數之前已到達了文件尾端。例如,若在到達文件尾端之前還有30個字節,而要求讀100個字節,則read返回30,下一次再調用read時,它將返回0(文件尾端)。
• 當從終端設備讀時,通常一次最多讀一行(第11章將介紹如何改變這一點)。
• 當從網絡讀時,網絡中的緩沖機構可能造成返回值小於所要求讀的字節數。
• 某些面向記錄的設備,例如磁帶,一次最多返回一個記錄。
讀操作從文件的當前位移量處開始,在成功返回之前,該位移量增加實際讀得的字節數。
3.write()
用write函數向打開文件寫數據

1 #include <unistd.h> 2 ssize_t write(int f i l e d e s, const void * buff, size_t nbytes) ; 3 4 //返回:若成功為已寫的字節數,若出錯為- 1
其返回值通常與參數nbytes的值不同,否則表示出錯。write出錯的一個常見原因是:磁盤已寫滿,或者超過了對一個給定進程的文件長度限制。
對於普通文件,寫操作從文件的當前位移量處開始。如果在打開該文件時,指定了O_APPEND選擇項,則在每次寫操作之前,將文件位移量設置在文件的當前結尾處。在一次成功寫之后,該文件位移量增加實際寫的字節數。
4.close()
可用close函數關閉一個打開文件:

1 #include <unistd.h> 2 int close (int filedes); 3 4 //返回:若成功為0,若出錯為-1
5.lseek()
可以調用lseek顯式地定位一個打開文件。

1 #include <sys/types.h> 2 #include <unistd.h> 3 off_t lseek(int filedes, off_t offset, int whence) ; 4 5 //返回:若成功為新的文件位移,若出錯為- 1
對參數offset的解釋與參數whence的值有關。
•若whence是SEEK_SET,則將該文件的位移量設置為距文件開始處offset個字節。
•若whence是SEEK_CUR,則將該文件的位移量設置為其當前值加offset,offset可為正或負。
•若whence是SEEK_END,則將該文件的位移量設置為文件長度加offset,offset可為正或負。
經過上面的簡單學習我們可以采用Linux系統調用實現文件拷貝

1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<unistd.h> 6 #include<stdlib.h> 7 #include<errno.h> 8 #include <string.h> 9 10 #define BUFF_SIZE 8192 11 12 int main(int argc,char **argv){ 13 14 int from_fd;//源文件的文件描述符 15 int to_fd;//目標文件的文件描述符 16 off_t file_size=0; 17 char buffer[BUFF_SIZE]; 18 int nread; 19 20 //判斷參數個數是否正確 21 if(argc != 3) 22 { 23 printf("Usage:%s fromfile tofile\n",argv[0]); 24 exit(1); 25 } 26 27 //打開源文件 28 if((from_fd=open(argv[1],O_RDONLY))==-1) 29 { 30 printf("Open %s Erron\n",argv[1]); 31 exit(1); 32 } 33 34 //創建目標文件 35 if((to_fd=open(argv[2],O_CREAT|O_WRONLY|O_TRUNC,S_IRUSR|S_IWUSR))==-1) 36 { 37 printf("Open %s Erron\n",argv[2]); 38 exit(1); 39 } 40 41 //測得文件的大小 42 file_size=lseek(from_fd,0L,SEEK_END); 43 lseek(from_fd,0L,SEEK_SET); 44 printf("from file size is =%ld\n",file_size); 45 46 //進行文件拷貝 47 while((nread=read(from_fd,buffer,BUFF_SIZE)) > 0) 48 { 49 if((write(to_fd,buffer,nread)) !=nread) //將buffer中的數據寫到目的文件 50 printf("write error"); 51 bzero(buffer,BUFF_SIZE); 52 } 53 close(from_fd); 54 close(to_fd); 55 exit(0); 56 return 0; 57 } 58
結果可以用diff命令進行文件的比對
采用C語言庫函數實現文件拷貝
1.fopen()
fopen庫函數類似於底層的open系統調用。它主要用於文件和終端的輸入輸出。如果你需要對設備進行明確的控制,那最好使用底層系統調用,因為這可以避免用庫函數帶來的一些潛在問題,如輸入/輸出緩沖。

1 #include <stdio.h> 2 FILE *fopen(const char *pathname, const char * type) ; 3 4 //返回:若成功則為文件指針,若出錯則為 N U L L
(1)fopen打開路徑名由pathname指示的一個文件。
(2)type參數指定對該I/O流的讀、寫方式,ANSIC規定type參數可以有15種不同的值
2.fread()、
fread庫函數用於從一個文件流里讀取數據。數據從文件流stream讀到由ptr指向的數據緩沖區里。fread和 fwrite都是對數據記錄進行操作,size參數指定每個數據記錄的長度,計數器nitems給出要傳輸的記錄個數。它的返回值是成功讀到數據緩沖區里的記錄個數(而不是字節數)。當到達文件尾時,它的返回值可能會小於nitems,甚至可以是零。

1 #include <stdio.h> 2 size_t fread(void *ptr, size_t size, size_t nitems, FILE * stream) ;
3.fwrite()、
fwrite庫函數與fread有相似的接口。它從指定的數據緩沖區里取出數據記錄,並把它們寫到輸出流中。它的返回值是成功寫入的記錄個數。

1 #include <stdio .h> 2 size_ t fwrite(const void *ptr, size_ t size, size_t nitems, FILE * stream) ;
4.fclose()、
fclose庫函數關閉指定的文件流stream,使所有尚未寫出的數據都寫出。因為stdio庫會對數據進行緩沖,所以使用fclose是很重要的。如果程序需要確保數據已經全部寫出,就應該調用fclose函數。雖然當程序正常結束時,會自動對所有還打開的文件流調用fclose函數,但這樣做你就沒有機會檢查由fclose報告的錯誤了。

1 #include <stdio.h> 2 int fclose(FILE * stream) ;
5.flseek()、
fseek函數是與lseek系統調用對應的文件流函數。它在文件流里為下一次讀寫操作指定位置。
offset和whence參數的含義和取值與前面的lseek系統調用完全一樣。 但lseek返回的是一個off_t數值,而fseek返回的是一一個整數: 0表示成功,-1表示失敗並設置errno指出錯誤。

1 #include <stdio.h> 2 int fseek(FILE *stream, long int offset, int whence) ;
經過上面的簡單學習我們可以采用C語言庫函數實現文件拷貝

1 #include<stdio.h> 2 #include <string.h> 3 #include<stdlib.h> 4 5 6 #define BUFFER_SIZE 1024 7 8 int main(int argc,char **argv) 9 { 10 11 FILE *from_fd=NULL; 12 FILE *to_fd=NULL; 13 int file_size=0; 14 char buffer[BUFFER_SIZE]; 15 16 //判斷參數個數是否正確 17 if(argc != 3) 18 { 19 printf("Usage:%s fromfile tofile\n",argv[0]); 20 exit(1); 21 } 22 23 //打開源文件 24 if(from_fd=fopen(argv[1],"r")==NULL) 25 { 26 printf("Open %s Erron\n",argv[1]); 27 exit(1); 28 } 29 30 //創建目標文件 31 if(to_fd=open(argv[2],"wb+")==NULL) 32 { 33 printf("Open %s Erron\n",argv[2]); 34 exit(1); 35 } 36 37 //測得文件大小 38 fseek(from_fd,0,SEEK_END); 39 file_size=ftell(from_fd); 40 printf("the from file size is %d\n",file_size); 41 fseek(from_fd,0,SEEK_SET); 42 43 //進行文件的拷貝 44 while(!feof(from_fd)) 45 { 46 fread(buffer,BUFFER_SIZE,1,from_fd); 47 if(BUFFER_SIZE>=file_size) 48 fwrite(buffer,file_size,1,to_fd); 49 else 50 { 51 fwrite(buffer,BUFFER_SIZE,1,to_fd); 52 file_size=file_size-BUFFER_SIZE; 53 } 54 bzero(buffer,BUFFER_SIZE); 55 } 56 fclose(from_fd); 57 fclose(to_fd); 58 exit(0); 59 return 0; 60 }
結果可以用diff命令進行文件的比對
值得注意的是fseek()不像lseek()會返回讀寫位置, 因此必須使用ftell()來取得目前讀寫的位置。
本文涉及到的關於函數的解釋大部分來自《Linux程序設計》