标准库IO接口:
fopen
fclose
fwrite
fread
fseek
文件流函数:(类型:FILE*
)
stdin
:标准输入stdout
:标准输出stderr
:标准错误输出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(...,...,whence);
其中第3
个参数whence
(偏移量)的选项取值:
SEEK_SET
: 文件起始位置SEEK_CUR
: 当前位置SEEK_END
: 文件末尾位置系统调用接口:【open
、write
、read
、lseek
、close
】
< fcntl.h >
int open(const char *pathname,int flags,mode_t mode);
pathname
:文件路径名
flags
:选项标志 (中间通过 或 “|
” 间隔)
- 必选项(必选其一) | 含义 | fcntl.h 中宏定义 |
---|---|---|
O_RDONLY |
只读 | 0 |
O_WRONLY |
只写 | 1 |
O_RDWR |
可读可写 | 2 |
- 可选项 | 含义 |
---|---|
O_CREAT |
文件不存在则创建,存在则打开 |
O_EXCL |
与O_CREAT同用时,若文件存在则报错 |
O_TRUNC |
打开文件同时截断文件长度为0处,打开时清空文件内容 |
O_APPEND |
追加写入 |
mode
:创建文件时给定权限。(八进制数字)
mode_t umask(mode_t mask);
最终创建出文件的权限情况计算方法:
mode & (~umask)
当前设定的权限仅仅针对于调用进程,不针对于操作系统。
-1
。open函数用法演示
int fd = open("./temp.txt",O_RDWR | O_CREAT | O_TRUNC,0777);
if(fd < 0){
perror("open error");
return -1;
}
< 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(f,buf,strlen(buf));
if(ret < 0){
peror("write error");
return -1;
}
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);
功能:跳转读写位置
off_t lseek(int fd,off_t offset,int whence);
fd
:打开文件所返回的文件描述符offset
:偏移量whence
:偏移位置int close(int fd);
如何修改调用进程的文件创建权限掩码?
mode_t umask(mode_t mask);
例如:umask(0)
,可以在创建进程之前设置,这样创建出的进程就和设置的参数保持一致。但还是如之前所说,当前设定的权限仅仅针对于调用进程,不针对于操作系统。
另外的一些系统IO接口:
- ftruncate
:通过文件名将文件长度为指定长度
- unlink
:通过文件名称删除一个文件
- fileno
:通过文件流指针,获取文件描述符
文件描述符和文件流指针的关系:
FILE*
fd
文件流指针这个结构体中包含文件描述符,文件流指针fp
通过文件描述符fd
来读写数据。
fp
中找到_fileno
,即fd
。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
,说明将空闲的最小下标写入了这个文件。
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
- 在子进程中打开文件,将标准输出重定向到这个文件,进行程序替换