Linux:基础IO
一:理解文件
1-1 狭义理解
文件存储在磁盘中,由于磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的;磁盘也是外设,因此磁盘上对文件的所有操作本质是对外设的输入和输出
1-2 广义理解
Linux下一切皆文件(键盘,显示器,网卡......)
1-3 文件操作的归类认知
- 对应0KB的空文件是占用磁盘空间的
- 文件是文件属性和文件内容的集合(文件 = 内容 + 属性)
- 所有的文件操作本身是对文件内容和文件属性的操作
1-4 系统角度
- 对文件的操作本质是进程对文件的操作
- 磁盘的管理者是操作系统
- 文件的读写本质不是通过C语言/C++的库函数来操作的,而是通过文件相关的系统调用接口来实现的
二 :C语言接口
2-1 fopen
函数原型:
#include<stdio.h>
FILE* fopen(const char* filename,const char* mode);
参数说明:
- filename:要打开的文件名称
- mode:文件打开模式
模式 | 说明 | 文件不存在时 | 文件存在时 |
"r" | 只读模式,从文件头部开始读取 | 打开失败 | 正常打开 |
"w" | 写入模式,清空原内容或创建新文件 | 创建新文件 | 清空原内容 |
"a" | 追加模式,在文件末尾添加内容 | 创建新文件 | 保留原有内容 |
"r+" | 读写模式,从文件头部开始读写 | 打开失败 | 正常打开 |
"w+" | 读写模式,清空原内容或创建新文件 | 创建新文件 | 清空原内容 |
"a+" | 读写模式,在文件末尾添加内容 | 创建新文件 | 保留原有内容 |
“w”写入模式:
1 #include<stdio.h>2 #include<string.h>3 4 int main()5 {6 FILE* fp = fopen("log.txt","w");7 if(fp == NULL)8 {9 perror("fopen");10 return 1;11 }12 13 fclose(fp);14 return 0;15 }
如上面代码所示,我们在myfile.c中使用写入模式打开log.txt文件,然后往文件写入了内容,文件大小为65,然后我们再次运行该可执行文件myfile,log.txt文件大小就被清空为0了。因此以写入模式“w”打开以存在的文件会将文件内容情况
“a”追加模式:
1 #include<stdio.h>2 #include<string.h>3 4 int main()5 {6 FILE* fp = fopen("log.txt","a");7 if(fp == NULL)8 {9 perror("fopen");10 return 1;11 }12 13 const char* msg = "hello world\n";14 fprintf(fp,"%s",msg);15 16 fclose(fp);17 return 0;18 }
如上面代码所示:我们以“a”追加模式打开文件log.txt,并且我们将字符串msg(hello world)写入到fp指向的文件中,即log.txt。因此第一次打印log.txt文件中的内容为hello world,然后我们执行了三次可执行文件myfile,由于我们使用追加模式打开log.txt,因此会再次执行三次将字符串写入到log.txt文件的操作
返回值:
- 成功:返回一个指向FILE对象的指针
- 失败:返回NULL,并设置全局变量errno以指示错误类型
2-2 fwrite
函数原型:
#include<stdio.h>
size_t fwrite(const void* ptr,size_t size,size_t count,FILE* stream);
参数说明:
- ptr:指向要写入数据的内存地址,类型为const void*,可接受任意类型的指针类型
- size:要写入数据项的最小单元大小(以字节为单位)
- count:要写入的数据项的个数
- stream:文件指针,指向已打开的文件
返回值:
- 成功:返回实际写入的数据项个数,即count
- 失败:返回值小于count
2-3 fclose
函数原型:
#include<stdio.h>
int fclose(FILE* stream);
参数说明:
- stream:指向已打开的文件的FILE指针
返回值:
- 成功:返回0
- 失败:返回EOF(通常为-1),并设置全局变量errno以指示错误类型
例子:
1 #include<stdio.h>2 #include<string.h>3 4 int main()5 {6 FILE* fp = fopen("log.txt","w");7 if(fp == NULL)8 {9 perror("fopen");10 return 1;11 }12 13 const char* msg = "hello world";14 int cnt = 1;15 while(cnt <= 5)16 {17 char buffer[1024];18 snprintf(buffer,sizeof(buffer),"%s%d\n",msg,cnt++);19 fwrite(buffer,strlen(buffer),1,fp);20 }21 22 fclose(fp);23 return 0;24 }
在上面代码中,我们以写入模式("w")打开log.txt文件,如果该文件不存在则创建,存在则清空内容,检查文件是否成功打开,失败是通过perror输出错误信息并退出程序。接下来每次循环将msg和计数器cnt组合成新字符串,存入buffer数组中。fwrite会将buffer里的字符串内容写入到fp所指向的文件中,即将buffer起始开始的,长度为strlen(buffer)字节的数据写入文件,最后关闭文件
三:系统文件IO
3-1 open
函数原型:
#include<sys/types.h>
#include<sys/sta.h>
#include<fcntl.h>
int open(const char* pathname,int flags);//打开以存在的文件
int open(const char* pathname,int flags,mode_t mode);//新建文件
参数说明:
- pathname:文件路径(绝对路径或相对路径)
- flags:打开模式,常用选项可通过 | 组合
- mode:创建文件时的权限位
常见选项:
- O_RDONLY:只读模式
- O_WRONLY:只写模式
- O_RDWR:读写模式
- O_CREAT:文件不存在需要新建时表示新建文件
- O_APPEND:追加模式
- O_TRUNC:情况文件内容
返回值:
- 成功:返回文件描述符
- 失败:返回-1,并设置errno指示错误原因
例子1:
1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 6 int main()7 {8 int fd = open("log.txt",O_CREAT | O_WRONLY,0666);9 if(fd < 0)10 {11 perror("open");12 return 1;13 }14 15 return 0;16 }
如上面代码所示:我们使用open函数新建(O_CREAT)并以写入模式(O_WRONLY)打开一个名为log.txt的文件,由于文件新建所以必须设置权限位,我们设置的为0666(读写权限)。
如上图所示,我们可以看到log.txt的权限位为0664而不是我们设置的0666,这是因为存在权限掩码umask
例子2:
1 #include<stdio.h>2 #include<sys/types.h>3 #include<sys/stat.h>4 #include<fcntl.h>5 #include<unistd.h>6 #include<string.h>7 8 int main()9 { 10 int fd = open("log.txt",O_CREAT | O_WRONLY,0666);11 if(fd < 0)12 { 13 perror("open");14 return 1;15 }16 printf("fd:%d\n",fd);17 18 const char* msg = "hello world\n";19 int cnt = 5;20 while(cnt)21 { 22 write(fd,msg,strlen(msg));23 cnt--;24 }25 26 close(fd);27 return 0;28 }
在上面代码中,使用open函数新建(O_CREAT)并以写入模式(O_WRONLY)打开一个名为log.txt的文件并设置权限位为0666,接着我们往fd指向的文件log.txt中写入五条hello world语句,结果如下图:
将18行和19行改成如下所示:
结果如上图所示:我们可以看到第一行的hello world已经变成了abcdo world了,这是因为我们打开文件的模式只是新建和写入模式,没有传入选项O_TRUNC(清空),所以每次打开文件都从起始位置以覆盖式写入,如果我们想要达到在文件原有内容进行写入就需要加上O_TRUNC选项,而想要在文件末尾处进行追加写入,就可以加上O_APPEND(追加)选项
3-2 write
函数原型:
#include<unistd.h>
ssize_t write(int fd,const void* buf,size_t count);
参数说明:
- fd:目标文件描述符
- buf:指向要写入数据的缓冲区指针
- count:要写入的字节数
3-3 read
函数原型:
#include<unistd.h>
ssize_t read(int fd,void* buf,size_t count);
参数说明:
- fd:文件描述符
- buf:指向用于存储读取数据的缓冲区指针,即输入的内容
- count:读取的字节数,即输入内容的大小
返回值:
- 成功:返回实际读取的字节数,0表示读到文件末尾
- 失败:返回-1,并设置errno
例子:
1 #include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 #include<fcntl.h>5 6 int main()7 {8 int fd = open("log.txt",O_RDONLY);9 if(fd < 0)10 {11 perror("open");12 return 1;13 }14 printf("fd:%d\n",fd);15 16 while(1)17 {18 char buffer[64];19 int n = read(fd,buffer,sizeof(buffer)-1);20 if(n > 0)21 {22 buffer[n] = 0;23 printf("%s\n",buffer);24 }25 else if(n == 0)26 {27 break;28 }29 }30 31 close(fd);32 return 0;33 }
在上面代码中,我们使用O_RDONLY(只读模式)打开一个已存在的文件log.txt,如果打开文件失败则退回程序,然后通过在while循环中,定义了一个大小为64字节的数组buffer,从fd指向的文件(即log.txt)中读取内容存放在buffer中,读取的大小不超过63字节,如果读取成功则在数组buffer最后一个位置加上0并打印buffer的内容,如果读取到0(即读取到文件末尾)则跳出循环
3-4close
函数原型:
#include<unistd.h>
int close(int fd);
参数:
fd:要关闭的文件描述符
返回值:
- 成功:返回0
- 失败:返回-1,并设置errno
3-5 文件描述符
1 #include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 #include<fcntl.h>5 6 int main()7 {8 printf("stdin:%d\n",stdin->_fileno);9 printf("stdout:%d\n",stdout->_fileno);10 printf("stderr:%d\n",stderr->_fileno);11 12 int fd = open("log.txt",O_RDONLY);13 if(fd < 0)14 {15 perror("open");16 return 1;17 }18 printf("fd:%d\n",fd);19 20 close(fd);21 return 0;22 }
在上面代码中, 我们先分别打印了标准输入,标准输出,标准错误的文件描述符,_fileno的FILE结构体内部的一个成员,内部存储了文件描述符;然后已只读模式(O_RDONLY)模式打开一个已存在的文件log.txt,接着打印log.txt的文件描述符fd,结果如下图:
如上图所示,我们可以看到标准输入(stdin),标准输出(stdout),标准错误(stderr)的文件描述符分别为0,1,2;而对于其他文件的文件描述符一般都是从3开始计数,如本例中log.txt的文件描述符为3
例子1:
1 #include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 #include<fcntl.h>5 6 int main()7 {8 close(0);9 int fd = open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);10 if(fd < 0)11 {12 perror("open");13 return 1;14 }15 printf("fd:%d\n",fd);16 17 18 close(fd);19 return 0;20 }
如上图所示,我们先关闭标准输入(stdin),接着我们以清空写入的模式新建一个权限位为0666的文件log.txt,接着判断是否创建成功并打印log.txt的文件描述符,最后关闭文件。由下图可知,log.txt的文件描述符为0
例子2:
1 #include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 #include<fcntl.h>5 6 int main()7 {8 close(1);9 int fd = open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);10 if(fd < 0)11 {12 perror("open");13 return 1;14 }15 printf("fd:%d\n",fd);16 17 return 0;18 }
如上图所示,我们先关闭标准输出(stdout),接着我们以清空写入的模式新建一个权限位为0666的文件log.txt,接着判断是否创建成功并打印log.txt的文件描述符,注意这里不用关闭文件。由下图可知,打印log.txt的文件描述符没有任何输出,而打印log.txt文件中的内容时将fd打印出来并且fd为1,也就是说将原本打印在显示器上的内容输出重定向到了log.txt文件中。而在底层,文件描述表中的文件描述符从标准输出指向到 log.txt ,因此从原本输出到显示器上转向为输出到 log.txt中
例子3:
1 #include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 #include<fcntl.h>5 6 int main()7 {8 close(2);9 int fd = open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);10 if(fd < 0)11 {12 perror("open");13 return 1;14 }15 printf("fd:%d\n",fd);16 17 18 close(fd);19 return 0;20 }
如上图所示,我们先关闭标准错误(stderr),接着我们以清空写入的模式新建一个权限位为0666的文件log.txt,接着判断是否创建成功并打印log.txt的文件描述符,最后关闭文件。由下图可知,log.txt的文件描述符为2
由上面三个例子可知,文件描述符的分配原则是:系统会将最小的没有被使用的文件描述符作为新的文件描述符分配给用户
3-6 dup2系统调用重定向
函数原型:
#include<unistd.h>
int dup2(int oldfd,int newfd);
将输出到newfd重定向输出到oldfd中
1 #include<stdio.h>2 #include<sys/types.h>3 #include<unistd.h>4 #include<fcntl.h>5 6 int main()7 {8 9 int fd = open("log.txt",O_CREAT | O_WRONLY | O_TRUNC,0666);10 if(fd < 0)11 {12 perror("open");13 return 1;14 }15 dup2(fd,1);16 17 printf("fd:%d\n",fd);18 19 printf("hello world\n");20 printf("hello world\n");21 22 close(fd);23 return 0;24 }
如上面代码所示:我们以清空写入的模式新建一个权限位为0666的文件log.txt,接着判断是否创建成功,接着使用dup2将输出到显示器上的内容重定向输出到fd指向的文件中,接着在显示器上打印log.txt的文件描述符并在显示器上打印两行hello world,由于前面进行了重定向操作,所以打印在显示器上的内容将会重定向到 log.txt中,最后关闭文件,结果如下图所示: