本文讲述Linux C 中对文件的操作。分为以下几部分内容:
打开方式 (包含 创建 能力)
读、写操作
读、写单个字符 读、写字符串 格式化读、写操作
数据块的读写
错误码判断与操作
位置指针移动
关闭文件
删除文件
以上操作涉及的函数#include <stdio.h>
即可。
#打开方式
FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd, const char *mode); FILE *freopen(const char *path, const char *mode, FILE *stream);
打开一个文件可以用以上三个函数来实现,该函数执行后则会有相应的流与FILE*绑定。 如果函数正常执行则会返回FILE *,否则返回NULL,且具体的失败原因可以从errno
中查询。
#####FILE *fopen(const char *path, const char *mode); #####
fopen()
的第一个参数path
用于指定要打开的文件名,第二个参数mode
指定打开的方式模式。打开文件的模式可以是下列表中的某一项,或者某几项的组合。下表中给出了子模式的含义,且与其它模式的区别。
|模式|文件不存在|读写能力|读位置|写位置|写操作对原文件内容影响| |—-|—-| |r|—-|只读|文件开头|—-|—-| |r+|—-|读写|文件开头|文件开头|原内容被新内容从文件开头按顺序被逐个字符地覆盖| |w|创建|只写|—-|文件开头|原内容被清空,新内容从文件开头写入| |w+|创建|读写|文件开头|文件开头|原内容被清空,新内容从文件开头写入| |a|创建|只写|—-|文件结尾|新内容写在原内容后面| |a+|创建|读写|文件开头|文件结尾|新内容写在原内容后面|
r: read w:write a:append b:binary
有些时候会看到文件打开模式除了上面的几种情况或由这几种情况的任意组合外,还会有和b
的组合,含义是: 出现b
表示以 二进制文件方式 操作文件,否则以 文本文件方式 操作。
模式w+
和a+
虽然都具有读权限,但实践中有可能出现无法读出文件中的内容,原因如下:
使用fopen
时w+
一开始会把文件清空,这时候去读是没有内容的;写操作完毕后文件已经有内容了,这时候直接去读取,由于文件指针位置在文件末尾,所以仍然无法读取到内容,得把文件指针位置
移动到文件之前的位置,才有内容可以读出。
使用a+
时,情形同w+
相似,文件指针位置
是在文件的结束位置,导致读不出内容。
示例代码将在文章的下半部分文件位置指针移动 中给出。
#####FILE *fdopen(int fd, const char *mode);#####
fdopen()
是使用文件描述符fd
来操作文件流的。 功能几乎与fopen()
相似。差别在于
fdopen()
在使用模式w
和w+
时,并不会对原有的文件内容进行清空操作;
fdopen()
可以直接使用STDIN_FILENO
(mode要具备r
权限)、STDOUT_FILENO
(mode要具备w
权限)、STDERR_FILENO
(mode要具备w
权限)来操作标准输入输出流。
相关:函数fileno()
可以获取某个文件的fd
。
int fileno(FILE *stream);
示例,以下代码演示从终端命令行中敲入一个字符并打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #incluce <unistd.h> #incluce <stdio.h> #incluce <stdlib.h> int main () { FILE* file = fdopen(STDIN_FILENO, "r" ); if (file == NULL ) { printf ("fopen fail:%s " , filePath); perror("" ); exit (EXIT_FAILURE); } int ch = fgetc(file); if (ch == EOF) { if (errno != 0 ) { perror("fgetc fail:" ); printf ("feof %d \n" , feof(file)); } else { printf ("read file end. %d \n" , feof(file)); } } else { printf ("fgetc:%c \n" , ch); } fclose(file); return EXIT_SUCCESS; }
#####FILE *freopen(const char *path, const char *mode, FILE *stream); #####freopen()
的功能是将一个已经打开的流(参数中的stream,可以是文件也可以是标准输出输入流)重定向到另一个流(函数返回值)中去,重定向的位置由参数path
决定。
示例代码:将标准输出流重定向到指定文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #incluce <unistd.h> #incluce <stdio.h> #incluce <stdlib.h> int main () { FILE * tmp; if ((tmp = freopen("/Users/sodino/reopen.txt" , "w" , stdout )) == NULL ) { fprintf (stderr ,"error1:redirecting stdout \n" ); } else { printf ("This will go into a file. %d\n" , (tmp == stdout )); } if ((tmp = freopen("/Users/sodino/reopen.2.txt" , "w" , stdout )) == NULL ) { fprintf (stderr ,"error1 redirecting stdout\n" ); } else { printf ("2This will go into a file. %d\n" , (tmp == stdout )); } fclose(tmp); return EXIT_SUCCESS; }
#读、写操作 ##### 读、写单个字符##### 读:
int fgetc(FILE *stream); int getc(FILE *stream); int getchar(void);
fgetc()
从指定的流中读取下一个字符,把unsigned char强制转换成int。getc()
功能与fgetc()
一致,只是在函数执行时可能会对流多做一次评估验证。getchar()
功能等同于getc(stdin)
。
以上三个函数在正常执行时都会返回读取到的字符int
,在发生错误或已经读到流的结束位置则返回EOF
。
写:
int fputc(int c, FILE *stream); int putc(int c, FILE *stream); int putchar(int c);
fputc()
将int转化为unsigned char并写入流中。putc()
功能与fputc()
一致,只是在函数执行时可能会对流多做一次评估验证。putchar()
功能等同于putc(c, stdout)
。
三个函数在正常执行时会将写入的字符c
返回,在执行发生错误时则返回EOF
。
Copy Demo
下面的示例代码演示复制一个文件的过程,copyFile_str()
是逐字节复制,而copyFile_data()
是每次8KB的数据块在复制,所以效率会高一些。fread()
及fwrite()
将会在下文中继续提到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 #include <stdio.h> #include <stdlib.h> static long copyFile (FILE*src, FILE*dest) { if (src == NULL || dest == NULL ) { return -1 ; } if (ftell(src) > 0 ) { rewind(src); } int res = 0 ; long count = 0 ; while (1 ) { int ch = fgetc(src); if (ch == EOF) { if (!feof(src)) { printf ("fgetc() break res=-1. count=%ld \n" , count); res = -1 ; } else { } break ; } else { int tmp = fputc(ch, dest); if (tmp == EOF) { res = -1 ; printf ("fputc() break res=-1. count=%ld \n" , count); break ; } count ++; continue ; } } return (res == -1 )?-1L :count; } static long copyFile_str (const char *srcPath, const char *destPath) { FILE * src = fopen(srcPath,"r" ); FILE * dest = fopen(destPath,"w" ); long res = copyFile(src, dest); fclose(dest); fclose(src); return res; } static long copyFileData (FILE*src, FILE*dest) { if (src == NULL || dest == NULL ) { return -1 ; } fseek(src, 0 , SEEK_END); long file_size = ftell(src); rewind(src); int res = 0 ; long count = 0 ; const int LEN_DATA = 8 *1024 ; unsigned char data[LEN_DATA]; while (1 ) { long current = ftell(src); long leave = file_size - current; int readSize = LEN_DATA; if (leave == 0 ) { printf ("leave == 0 feof.\n" ); break ; } else if (leave < LEN_DATA) { readSize = (int )leave; } size_t res_t = fread(&data, 1 , readSize, src); if (res_t == readSize) { size_t w_res = fwrite(&data, 1 , res_t , dest); count += w_res; if (res_t == w_res) { printf ("write=%ld res_t=%zu \n" , count, res_t ); } else { printf ("fwrite fail. \n" ); res = -1 ; break ; } } else if (feof(src) != 0 ){ printf ("feof res_t=%zu \n" , res_t ); break ; } else { printf ("fread fail...\n" ); res = -1 ; break ; } } return (res == -1 )?-1 :count; } static long copyFile_data (const char * srcPath, const char *destPath) { FILE * src = fopen(srcPath,"r" ); FILE * dest = fopen(destPath,"w" ); long res = copyFileData(src, dest); fclose(dest); fclose(src); return res; } int main (int argc, char **argv) { char *srcPath = "/Users/sodino/workspace/xcode/CString/CString/a.out" ; char *destPath = "/Users/sodino/workspace/xcode/CString/CString/a1.out" ; long res = copyFile_str(srcPath, destPath); if (res == -1 ){ printf ("copyFile_str() fail. \n\n\n" ); } else { printf ("copyFile_str() count=%d. \n\n\n" , res); } printf ("do copyFile_data() \n" ); res = copyFile_data(srcPath, destPath); printf ("copy.file.length=%ld \n" , res); if (res == -1 ) { return EXIT_FAILURE; } else { return EXIT_SUCCESS; } }
#####读、写字符串##### 读:
char *fgets(char *s, int size, FILE *stream);
从流中读取一个小于size
长度的字符串,并将读到的字符串s
返回。在读取过程中如果碰到EOF
或换行,则把已经读到的字符串返回不再继续往下读取。返回的字符串会自动加上字符串终止符/0
,字符串终止符是算在size
里面的,所以得到的字符串一直是小于size
的。 当执行发生错误或已经读到流的结束位置了则返回NULL。
写:
int fputs(const char *s, FILE *stream); int puts(const char *s);
fputs()
会将参数中字符串s
去掉字符串终止符/0
然后写入指定的流中去。puts()
相当于fputs(s, stdout)
,即把字符串输出到标准输出流中去,且会在字符串后面自动加上换行符。
当函数正常执行时,则返回一个非0的数字代表写入的字符个数;否则返回EOF
。
#####格式化读、写操作######include <stdio.h>
int fscanf(FILE *stream, const char *format, …); // 格式化读 int fprintf(FILE *stream, const char *format, …); // 格式化写
之前的文章已经讲过了printf() 强大的日志打印功能,在文件流的读、写上fprintf()
相当于是把printf()
的内容写入到指定的流中去;fscanf()
则是fprintf()
的反操作,读。
示例代码将在文件位置指针移动 中给出。
#数据块的读写
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
fread()
用于流操作中数据块的读取。将读到的数据存储于第一个参数ptr
中,第二个参数size
告诉函数每个数据块的大小,第三个参数nmemb
指明要读取数据块的数量,第四个参数stream
指明读取的数据流。 本函数正常执行完毕后返回数据块的读取数量,即nmemb
;当执行出现错误或遇到了文件结束的位置时,则返回0或比nmemb
小的数。至于是出现错误还是文件结束导致的非正常执行,则要通过feof()
和ferror()
来判断。
fwrite()
用于流操作中的数据块写入。第一个参数ptr
表示要写入的数据,第二个参数size
告诉函数每个数据块的大小,第三个参数nmemb
指明要写入数据块的数量,第四个参数stream
指明写入的数据流。 本函数正常执行完毕后返回数据块的读取数量,即nmemb
;当执行出现错误,则返回0或比nmemb
小的数。
这两个方法经常用于二进制文件的操作,所以打开方式上要与b
模式组合。
示例代码:批量读写 sturct student
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <stdio.h> #include <stdlib.h> struct student { char name[25 ]; char sex; struct { int year; int month; int day; }birthday; float BWH[3 ]; }; int main (int argc, char ** argv) { struct student pes [3] = { {"jack ma" ,'M' ,1985 ,2 ,22 ,80 ,95 ,60.5 }, {"pony" ,'F' ,1984 ,1 ,2 ,57 ,60 ,78 }, {"robin" ,'F' ,1995 ,3 ,2 ,77.0 ,90 ,77.5 } }; int i; FILE *fpr; struct student b ; fpr = fopen("W.DAT" ,"wb+" ); for (i=0 ;i<3 ;i++) { fwrite(pes+i,sizeof (struct student),1 ,fpr); } fseek(fpr,0L ,SEEK_SET); for (i=0 ;i<3 ;i++) { fread(&b,sizeof (struct student),1 ,fpr); printf ("%7s " ,b.name); printf ("%c " ,b.sex); printf ("%04dY %02dM %02dD " ,b.birthday.year,b.birthday.month,b.birthday.day); printf ("%5.1f %5.1f %5.1f" ,b.BWH[0 ],b.BWH[1 ],b.BWH[2 ]); printf ("\n" ); } fclose(fpr); return EXIT_SUCCESS; }
#错误码判断与操作 有时候在执行流的读操作时,发现返回为NULL或者EOF,这时需要判断是到了流(文件)的结束位置了?还是出现错误了?这就涉及到如下函数:
int feof(FILE *stream); int ferror(FILE *stream); void clearerr(FILE *stream);
feof()
判断文件是否处于文件结束位置,如文件结束,则返回值为1,否则为0。ferror(文件指针)
检查文件在用各种输入输出函数进行读写时是否出错。 如ferror返回值为0表示未出错,否则表示有错。
clearerr()
用于清除出错标志和文件结束标志,使它们为初始值0。
#文件位置指针移动 在读流(文件)时,已经读到文件结束的位置时,或者想以当前位置为准跳过指定长度的数据继续往下读,则需要移动文件位置指针。一般常用的涉及到以下三个函数:
long ftell(FILE *stream); int fseek(FILE *stream, long offset, int whence); void rewind(FILE *stream);
ftell()
用于获知当前文件指针的位置。fseek()
用于改变文件指针的位置,第二个参数offset
是指参照whence
后指针进一步的偏移量。第三个参数whence
有三种赋值及其含义分别为: SEEK_SET : 文件指针回到文件初始位置。 SEEK_CUR : 文件指针不变,仍处于当前位置。一般配合offset
移动文件指针到当前位置前后若干字节。 SEEK_END : 文件指针跳到文件结束的位置。 函数正常执行时则返回0,否则返回-1且会把错误码设置在errno
中。rewind()
相当于fseek(file, 0, SEEK_SET)
。即直接跳转到文件开头位置。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> #include <stdlib.h> int main (int argc, char **argv) { char *filePath = "/Users/sodino/workspace/xcode/CString/CString/test.txt" ; FILE* file = fopen(filePath, "w+" ); printf ("integer descriptor:%d \n" , fileno(file)); if (file == NULL ) { printf ("fopen fail:%s " , filePath); perror("" ); exit (EXIT_FAILURE); } fprintf (file, "%s %d" , "My_Age_is" , 19 ); fseek(file, 0 , SEEK_SET); char arrRead[100 ]; int age; fscanf (file, "%s %d" , arrRead, &age); printf ("read: %s %d \n" , arrRead, age); fclose(file); return EXIT_SUCCESS; }
#关闭文件
int fclose(FILE *stream); 关闭文件(流),如果流中仍有缓存内容未进行写操作,则会执行完写操作后再关闭。 关闭文件(流)之后,任何函数再有对stream
进行操作的行为结果则都是未知的。所以请及时对stream
置NULL。
#删除文件
int remove(const char *pathname); Thread-Safe remove()
函数可以从文件系统中删除指定路径下的文件(夹),内部实现中使用unlink()
删除文件,使用rmdir()
删除文件夹。
如果指定的路径是其所代表的文件(夹)的最后一个链接(参照硬链接和软链接 )且没有被其它进程后引用,则该文件(夹)会被立即删除,其所占用的空间也可以被重新利用。但如果有其它进程仍在引用着该文件(夹),则删除和空间的回收操作要等到其它进程释放了当前文件(夹)的引用之后才会继续进行。
如果指定的路径是一个软链接文件,则只会删除该软链接文件,而非其指向的原始文件。