1 -> 回顾
1.1 -> 回顾C文件接口
test.c写文件
代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp = fopen("myfile", "w");
if (!fp)
{
printf("fopen error!n");
}
const char* msg = "One Piece!n";
int count = 5;
while (count--)
{
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
test.c读文件
代码语言:javascript复制#include <stdio.h>
#include <string>
int main()
{
FILE* fp = fopen("myfile", "r");
if (!fp)
{
printf("fopen error!n");
}
char buf[1024];
const char* msg = "One Piece!n";
while (1)
{
//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
size_t s = fread(buf, 1, strlen(msg), fp);
if (s > 0)
{
buf[s] = 0;
printf("%s", buf);
}
if (feof(fp))
{
break;
}
}
fclose(fp);
return 0;
}
输出信息到显示器
代码语言:javascript复制#include <stdio.h>
#include <string.h>
int main()
{
const char* msg = "hello fwriten";
fwrite(msg, strlen(msg), 1, stdout);
printf("hello printfn");
fprintf(stdout, "hello fprintfn");
return 0;
}
stdin & stdout & stderr
C默认会打开三个输入输出流,分别是stdin,stdout,stderr。
仔细观察发现,这三个流的类型都是FILE*,fopen返回值类型,文件指针。
1.2 -> 总结
打开文件的方式:
r Open text file for reading. The stream is positioned at the beginning of the file. r Open for reading and writing. The stream is positioned at the beginning of the file. w Truncate(缩短) file to zero length or create text file for writing. The stream is positioned at the beginning of the file. w Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file. a Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file. a Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.
2 -> 系统文件I/O
操作文件,除了上述C接口(当然,C 也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:
test.c写文件
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
umask(0);
int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
if (fd < 0)
{
perror("open");
return 1;
}
int count = 5;
const char* msg = "One Piece!n";
int len = strlen(msg);
while (count--)
{
write(fd, msg, len);//msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
test.c读文件
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
const char* msg = "hello bit!n";
char buf[1024];
while (1)
{
ssize_t s = read(fd, buf, strlen(msg));//类比write
if (s > 0)
{
printf("%s", buf);
}
else
{
break;
}
}
close(fd);
return 0;
}
3 -> 接口介绍
3.1 -> open
代码语言:javascript复制#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件 flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。 参数: O_RDONLY: 只读打开 O_WRONLY: 只写打开 O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 O_APPEND: 追加写 返回值: 成功:新打开的文件描述符 失败:-1
- mode_t理解:直接 man 手册,比什么都清楚。
- open函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
- write、read、close、lseek,类比C文件相关接口。
3.2 -> open函数返回值
在认识返回值之前,先来认识一下两个概念:系统调用和库函数。
- 上面的fopen、fclose、fread、fwrite都是C标准库当中的函数,我们称之为库函数(libc)。
- 而open、close、read、write、lseek都属于系统提供的接口,称之为系统调用接口。
系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。
3.3 -> 文件描述符fd
通过对open函数的学习,可以知道文件描述符就是一个小整数。
4 -> 0 & 1 & 2
- Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2。
- 0、1、2对应的物理设备一般是:键盘,显示器,显示器。
所以输入输出还可以采用如下方式:
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
size_t s = read(0, buf, sizeof(buf));
if (s > 0)
{
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
5 -> 文件描述符的分配规则
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %dn", fd);
close(fd);
return 0;
}
输出发现是 fd: 3。
关闭0或者2,在看
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %dn", fd);
close(fd);
return 0;
}
发现是结果是:fd: 0或者fd: 2可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
6 -> 重定向
那如果关闭1呢?看代码:
代码语言:javascript复制#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %dn", fd);
fflush(stdout);
close(fd);
exit(0);
}
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>、>>、<。
那重定向的本质是什么呢?
7 -> 使用dup2系统调用
函数原型如下:
#include <unistd.h> int dup2(int oldfd, int newfd);
示例代码:
代码语言:javascript复制#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./log", O_CREAT | O_RDWR);
if (fd < 0)
{
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;)
{
char buf[1024] = { 0 };
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0)
{
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}
例子1. 在minishell中添加重定向功能:
代码语言:javascript复制# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <fcntl.h>
char command[MAX_CMD];
int do_face()
{
memset(command, 0x00, MAX_CMD);
printf("minishell$ ");
fflush(stdout);
if (scanf("%[^n]%*c", command) == 0)
{
getchar();
return -1;
}
return 0;
}
char** do_parse(char* buff)
{
int argc = 0;
static char* argv[32];
char* ptr = buff;
while (*ptr != '