本文已参与「新人创作礼」活动,一起开启掘金创作之路。
标准库IO接口
标准库IO接口:
fopenfclosefwritefreadfseek
文件流函数:(类型:FILE*)
-
stdin:标准输入 -
stdout:标准输出 -
stderr:标准错误输出
fopen
r:只读r+:可读可写,每次都在文件首部开始,==覆盖式读写==。w:只写,文件不存在则创建,存在则清空内容。 (truncate:截断,file to zero length截断点为0,清空内容再进行写入)w+:读写,文件不存在则创建,存在则清空内容,==覆盖式读写==。a:只写追加,文件不存在则创建,每次写入数据都是写入文件末尾。 因为是只写追加,故不能读取数据,不能使用fseek回到SEEK_SET,必须改变方式为a+即可。a+:可读写追加,文件不存在则创建,读数据的初始位置是在文件起始,写数据一直追加在文件末尾。
其他接口:
fgets:获取一行数据
printf:格式化数据,直接打印,即写入标准输出stdout。
fprintf:格式化数据,写入指定的文件流指针,给定stdout就与printf等效了。
sprintf:格式化数据,放入一个容器buf。是一个用于字符串链接的函数。
snprintf:多了一个size,格式化时只向buf中写入固定个数的单位,防止溢出。
sscanf:把数据字符串按照格式拆解。
perror:是面向库函数的,库函数中会封装许多系统调用接口,所以一旦出错,不容易确定到底是哪个接口出的问题。
标准库IO接口操作句柄是:文件流指针FILE*,文件流指针这个结构体中就包含了文件描述符,当使用标准库接口进行IO,最终本质是通过文件流指针找到文件描述符,进而对文件进行操作。
fseek
函数原型:fseek(...,...,whence);
其中第3个参数whence(偏移量)的选项取值:
SEEK_SET: 文件起始位置SEEK_CUR: 当前位置SEEK_END: 文件末尾位置
系统IO接口
系统调用接口:【open、write、read、lseek、close】
open
- 所属头文件
< fcntl.h > - 函数定义:
int open(const char *pathname,int flags,mode_t mode);
pathname:文件路径名flags:选项标志 (中间通过 或 "|" 间隔)-
必选项(必选其一) |含义|
fcntl.h中宏定义O_RDONLY|只读|0O_WRONLY|只写|1O_RDWR|可读可写|2 -
可选项|含义
O_CREAT| 文件不存在则创建,存在则打开O_EXCL| 与O_CREAT同用时,若文件存在则报错O_TRUNC| 打开文件同时截断文件长度为0处,打开时清空文件内容O_APPEND| 追加写入
-
mode:创建文件时给定权限。(八进制数字)
- 所属头文件:
<sys/stat.h> - 函数定义:
mode_t umask(mode_t mask);
最终创建出文件的权限情况计算方法:
mode & (~umask)
当前设定的权限仅仅针对于调用进程,不针对于操作系统。
- 返回值
返回文件描述符 ,其实就是一个正整数,错误时返回
-1。
open函数用法演示
int fp = open("./temp.txt",O_RDWR | O_CREAT | O_TRUNC,0777);
if(fp < 0){
perror("open error");
return -1;
}
write
- 所属头文件
< unistd.h > - 函数定义:
ssize_t write(int fd,const void *buf,size_t count);
ssize_t:有符号长整型(signed size_t)fd:打开文件所返回的文件描述符buf:要向文件写入的数据count:要写入的数据长度- 返回值:实际的写入字节数,错误返回
-1
write函数用法演示
char buf[1024] = "nihao\n";
int ret = write(fg,buf,strlen(buf));
if(ret < 0){
peror("write error");
return -1;
}
read
- 函数定义:
ssize_t read(int fd,void *buf,size_t count);
fd: 打开文件所返回的文件描述符buf: 对读取到的数据进行储存的位置,是一个地址count: 要读取的数据长度- 返回值:实际的读取字节数,错误返回
-1,如果为0说明读到了文件末尾。
read函数用法演示
memset(buf,0x00,1024);
ret = read(fd,buf,1023); //只读 1023 个字节是为了不出现乱码,因为上一步在最后一个字节手动置了\0
if(ret < 0){
perror("read error");
return -1;
}
printf("read buf:[%s]\n",bug);
close(fd);
lseek
功能:跳转读写位置
- 函数定义:
off_t lseek(int fd,off_t offset,int whence);
fd:打开文件所返回的文件描述符offset:偏移量whence:偏移位置- 返回值:返回当前位置到文件起始位置的偏移量
close
- 函数定义:
int close(int fd);
如何修改调用进程的文件创建权限掩码?
mode_t umask(mode_t mask);
例如:umask(0),可以在创建进程之前设置,这样创建出的进程就和设置的参数保持一致。但还是如之前所说,当前设定的权限仅仅针对于调用进程,不针对于操作系统。
另外的一些系统IO接口:
- ftruncate :通过文件名将文件长度为指定长度
- unlink :通过文件名称删除一个文件
- fileno :通过文件流指针,获取文件描述符
文件描述符 fd
文件描述符和文件流指针的关系:
- 标准库接口使用文件流指针
FILE* - 系统调用接口使用文件描述符
fd
文件流指针这个结构体中包含文件描述符,文件流指针fp通过文件描述符fd来读写数据。
- 通过调用库函数向文件中写入数据时:
1. 库函数调用系统调用函数时,先要得到文件描述符。
2. 用文件流指针
fp中找到_fileno,即fd。 3. 进行操作,完成通过系统调用来写读写数据。 - 当使用标准库接口进行
IO,则最终是通过文件流指针找到文件描述符进而对文件进行操作。
系统已定义的文件流指针
| 标准输入 | 标准输出 | 标准错误 |
|---|---|---|
| stdin | stdout | stderr |
| 0 | 1 | 2 |
头文件中的宏:STDIN_FILENO | STDOUT_FILENO | STDERR_FILENO |
printf打印数据的时候,如果没有刷新缓冲区,数据并不会被直接写入文件而是先写入缓冲区中。每一个文件都有文件缓冲区,缓冲区的描述信息就在文件流指针中。
缓冲区在文件流指针中的描述信息:
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_ptr;
...
缓冲区实际是文件流指针为每个文件所维护的一个缓冲区,是一个用户态的缓冲区。
内核态:进程运行在内核态:指的是当前完成功能时操作系统内核完成,操作内核空间。用户态:进程运行在用户态:指的是当前操作,操作都是在用户空间完成。
- 文件描述符是什么? 是一个正整型数字,实际上是一个==数组下标==。
【文件流指针_IO_FILE结构体中的fileno就是文件描述符】
- 为什么可以通过这个数字操作文件?
当进程每打开一个文件:
- 都会先使用
struct file结构体来描述这个文件。 - 并且将描述信息加载到
struct files_struct这个结构中的file结构体数组中fd_array[](类型为file*)] - 并向用户返回数组下标作为文件描述符。
- 用户通过文件描述符对文件进行操作,但内核实际上是通过文件描述符找到文件描述信息,进而操作文件。
- 注:
一个进程运行之后,默认打开
3个文件:标准输入,标准输出,标准错误,所以要从下标为3开始分配!struct files_struct结构体也是PCB中的描述信息。 描述符个数有限,不能打开了不关闭,会引起==资源泄露==。
文件描述符分配规则:==最小未使用原则==
演示:
int main(){
close(1); //关闭标准输出
int fd = open("./env.c",O_RDWR);
printf("%d\n",fd);
fflush(stdout);
close(fd);
return 0;
}
printf函数通过stdout,也就是下标为1的标准输出文件流指针来找文件描述符,此时原1号标准输出已经关闭,所以目前最小空闲为1号,所以env.c就占据了1号下标,内容写入了这个文件。这也就是==重定向==的原理,修改数据流向。
所以:结果无法显示,因为标准输出关闭了。
但是env.c中写入了一个1,说明将空闲的最小下标写入了这个文件。
dup2()
- 函数定义:
int dup2(int oldfd,int newfd);
针对文件描述符的重定向函数。
- 功能:将
newfd文件描述符重定向到oldfd所在的文件。 若newfd本身已有打开文件,重定向时则关闭已打开文件。
如dup2(fd,1);让1也指向了fd所指向的文件。
演示
int main(){
int fd = open("./env.c",O_RDWR);
dup2(fd,1); //将1号下标描述符中内容改变成为fd下标中的描述信息,即1号也指向了fd。向fd中写入,1也会被写入
fflush(stdout);
close(fd);
return 0;
}
效果:
1->stdout 3(fd)->a.txt
↓
1->a,txt 3->a.txt
对minishell完善
重定向功能,其流程为:
- 接受标准输入数据
- 解析命令,判断是否包含重定向符号
- 如果包含,则认为需要输出重定向,这时获取重定向符号后的文件名(将
>截断),将重定向符号替换成\0- 在子进程中打开文件,将标准输出重定向到这个文件,进行程序替换