前言
理解文件原理和操作:
我们先快速回忆下一C语言的文件操作:
首先看一下fopen函数的使用,然后我们写一段简单的C语言打开文件的代码如下图:
#include <stdio.h> #define LOG "log.txt" int main() { //默认写方式打开文件,如果文件不存在,就删除它 FILE* fp = fopen(LOG,"w"); if (fp==NULL) { perror("fopen:"); return 1; } //正常进行文件操作 const char* msg = "hello friends\n"; int cnt = 1; while (cnt) { fputs(msg,fp); --cnt; } fclose(fp); return 0; }
接下来我们运行一下:
通过上图我们可以看到成功将我们的字符串写入文件中,那么这次我们什么都不写再看看:
为什么原先log里的字符串没了呢?因为我们打开的方式是w,文件内容会先被清空再写入。
下面我们再介绍一个接口snprintf:
普通的printf是默认向显示器打印,fprintf是指定文件流向指定文件进行打印,下面我们演示一下:
首先我们修改一下代码,将fputs替换为fprintf函数
int main() { //默认写方式打开文件,如果文件不存在,就删除它 FILE* fp = fopen(LOG,"w"); if (fp==NULL) { perror("fopen:"); return 1; } //正常进行文件操作 const char* msg = "hello friends\n"; int cnt = 5; while (cnt) { // fputs(msg,fp); fprintf(fp,"%s:%d:sxy\n",msg,cnt); --cnt; } fclose(fp); return 0; }
运行结果如上图所示,并且我们不仅可以像文件中打印,也可以在输出流中打印:
我们都知道,一个程序会默认打开以上三个输入输出流,下面我们像stdout打印一下:
运行后我们发现直接就能显示不用再打开log.txt查看了,下面我们再来演示一下snprintf函数:
int main() { //默认写方式打开文件,如果文件不存在,就删除它 FILE* fp = fopen(LOG,"w"); if (fp==NULL) { perror("fopen:"); return 1; } //正常进行文件操作 const char* msg = "hello friends\n"; int cnt = 5; while (cnt) { char buffer[256]; snprintf(buffer,sizeof(buffer),"%s:%d:sxy\n",msg,cnt); fputs(buffer,fp); --cnt; } fclose(fp); return 0; }
snprintf就是将msg格式化到buffer数组里并且以buffer的大小进行打印,然后将数组中的数据写入文件中。
后面还有a追加写入等的方式我们就不在演示,这些都是我们C语言的时候学过的。
以上都是语言进行文件的操作,下面我们进入今天的正题,在系统层面使用文件操作。
一、linux系统中的文件操作以及文件接口
首先我们看一下系统中文件的接口open:
然后我们再看一下这个接口的返回值是什么:
如果成功返回一个新的文件描述符,否则返回-1,下面我们演示一下这个接口:
首先一定要包头文件:
这个函数的第一个参数与刚刚一样都是文件名,第二个参数其实是用位图实现的,就是用一个标志位充当不同的行为,如下图所示:
在上图中我们就利用位图传不同的flags就能打打印不同的数,并且不仅可以打印一个也可以同时打印多个:
而第二个参数的使用方式与上面演示的一样,都有下面这几种选项:
这些都是宏像我们刚刚演示的#define的那样。下面我们演示这个接口:
//系统层面 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define LOG "log.txt" int main() { int fd = open(LOG,O_WRONLY); printf("fd:%d\n",fd); close(fd); return 0; }
0_CREAT是如果没有这个文件就给我们创建一个文件,O_WRONLY是只写的意思。
我们先删除原先的log文件,运行后发现返回值为-1,也就是说出错了,那是什么原因呢?我们修改一下代码如果出错就打印一下出错原因:
因为我们要写这个文件,但是这个文件并不存在,所以我们没有的话应该先创建一个文件:
这次我们发现文件创建成功了,错误码为0。我们这里用的是root所以文件的权限没有问题,如果这里是普通用户创建文件的话文件权限是乱码有问题的,如下图:
所以我们一般创建文件用的是三个接口的那个函数,我们演示的这个函数没有提供文件权限的接口。
下面我们用三个接口的open创建一下文件:
我们先删掉之前的文件,然后重新运行一下发现这次文件的权限没有问题是正确的(不是666是因为受umask影响)。所以,一个文件被创建默认权限受umask影响,那么如果想不受umask影响呢?
我们可以直接将所有的权限掩码设置为0:
我们发现这次就不受umask影响了。接下来我们写入文件试试:
关于写入的文件接口的函数为write:
向文件描述符写入一个缓冲区大小为count,返回值是实际写入多少字节,写入失败返回-1.
int main() { umask(0); int fd = open(LOG,O_WRONLY | O_CREAT,0666); if (fd==-1) { printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno)); } else printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno)); printf("fd:%d\n",fd); const char* msg = "hello friend"; int cnt = 5; while (cnt) { char line[128]; snprintf(line,sizeof(line),"%s,%d\n",msg,cnt); write(fd,line,strlen(line)); --cnt; } close(fd); return 0; }
接下来我们运行一下:
通过上图我们发现写入成功,其实在我们刚刚strlen的时候发现我们并没有带\0,为什么没有\0也能成功写入呢,因为\0是C语言的标准不是系统的规定,并且\0在系统中写入会变成乱码。
下面我们重新写一下:
在我们重新运行后发现文件中保留了上一次的数据,也就是说O_CREAT | O_WRONLY是不会清空原来的文件再写入的,这个时候我们加上O_TRUNC选项就可以每次写入前先清空文件了:
搞定了写入那么饿追加就不是问题了,追加只需要注意将写入替换为追加,并且我们不希望每次打开文件先清空文件,所以不需要O_TRUNC选项:
为什么我们上面的运行结果不正确呢?因为0_APPEND只是追加不是追加写,所以才没有写入。下面我们修改一下代码:
这样我们就成功的追加了字符串,那么如果是只读呢?其实只读就很简单了,只读只需要调用两个接口的open函数即可,因为只读默认认为是有文件的。
读取的返回值就是当前读取了多少字节,读取到文件结尾就是0,失败就是-1。
int main() { umask(0); int fd = open(LOG,O_RDONLY); if (fd==-1) { printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno)); } else printf("fd:%d,errno:%d,errstring:%s\n",fd,errno,strerror(errno)); char buffer[1024]; ssize_t n = read(fd,buffer,sizeof(buffer)-1); //使用系统接口来进行IO的时候,一定要注意\0问题。 if (n>0) { buffer[n] = '\0'; printf("%s\n",buffer); } close(fd); return 0; }
我们在读取的时候为什么-1呢?因为我们不考虑\0的问题,只有当读取的时候返回值大于0我们将\0放到n的位置以字符串的形式打印的时候才需要\0,所以最后需要将\0放到n的位置再打印。
以上就是系统级的文件接口,而入C语言等要进行文件操作都必须调用系统接口。