1. 基础IO操作
1.1 c语言的IO接口
fopen:打开一个文件,按照指定方式
参数:filename 文件名,也可以是路径,mode:打开方式
返回打开的文件指针
fread:从指定流中读数据
参数:从FILE对象中读数据,每次读size字节大小的数据,最多读count次,读的数据写在buffer里
返回实际读的数据个数
fwrite:把数据写到指定的流中
参数 :从buffer中读数据,每次读size字节大小的数据,最多读count次,读的数据写在stream里
返回实际写的数据个数
fclose:关闭打开的文件
不同的语言,比如c,c++,java....都有对应的IO接口,语言的底层封装的其实都是操作系统对应的IO接口,在语言层面
好处有:使用方便,学习成本低,一套接口,在不同的操作系统下都可以使用,具有跨平台可移植性
1.2 Linux的IO接口
open:打开文件
参数:pathname 路径名称,flags:标记位,打开方式,mode:文件属性(新建)
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算,构成flags。
参数:
- O_RDONLY: 只读打开
- O_WRONLY: 只写打开
- O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个
- O_CREAT : 若文件不存在,则创建它。需要使用mode选项(0666-umask),来指明新文件的访问权限
- O_APPEND: 追加写
- O_TRUNC:清空写
返回值:成功:新打开的文件描述符 失败:-1
读fd文件,写到buf里,读count字节
读buf内容,写到fd文件里,写count字节
关闭文件
2. 文件描述符fd
open函数返回值是int类型的fd,这个fd是什么呢?
我们在写代码,调用接口,然后文件被编译运行,形成进程,也就是进程在打开文件。
打开文件是进程在执行,打开文件,就是把文件从,磁盘加载到内存上,还需要一个标识符,让进程能够找到这个内存上的文件。这个标识符就是fd,file describe 文件描述符。通过fd,进程就能找到对应的文件,完成下面的操作。
一个进程,可能要打开多个文件,就会有多个fd,如何对fd进行管理呢?
而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来 描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进 程和文件关联起来。
每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件 描述符,就可以找到对应的文件
c库默认会打开3个文件:stdin标准输入,stdout标准输出,stderr标准错误
他们的fd分别对应0(键盘),1(屏幕),2(屏幕)
接下来新建的文件,就会从3开始,依次往下,
文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
3. 文件重定向
cpp
#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 | O_TRUNC, 0666);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <
那重定向的本质是什么呢?
可以使用系统提供的函数接口:dup2
使用这个函数也可以达到相同的效果
cpp
dup2(3,1);
文件重定向:
|-------|---------------------------------------|---------------|
| 输出重定向 | open(O_WRONLY | O_CREAT | O_APPENT) | stdout重定向到新目标 |
| 追加重定向 | open(O_WRONLY | O_CREASE | O_TRUNC) | stdout重定向到新目标 |
| 输入重定向 | open(O_RDONLY ) | stdin重定向到新目标 |
stdout和stderr的使用区分
stdout是用来输出打印信息,stderr一般用来输出程序的报错记录
bash
./proc > out.txt 2> err.txt
可以分开把stdou输出的内容重定向到out.txt,stderr输出的内容重定向到err.txt
bash
./proc > all.txt 2>&1
把两个都输出到all.txt文件中
myshell实现文件重定向
cpp
//整体结构:创建子进程,由子进程获取指令,父进程判断完成的怎么样
//1.打印标识开头
//2.获取指令字符串
//3.分析字符串提取指令到grev[]
//4.部分指令的特殊处理,例如cd
//5.替换进程execvpe
//
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#define SIZE 1024
#define NUM 32
#define EMPTY 0
#define INPUTDIR 1
#define OUTPUTDIR 2
#define APPPUTDIR 3
int status_dir;
char str[SIZE];
char* _grev[NUM];
char _env[NUM][NUM];
char* getfile(char* str, int end)
{
while(str[end] != ' ')
{
if(str[end] == '>')
{
if(str[end - 1] == '>')
{
status_dir = APPPUTDIR;
str[end-1] = '\0';
return &str[end+1];
}
status_dir = OUTPUTDIR;
str[end] = '\0';
return &str[end+1];
}
else if(str[end] == '<')
{
status_dir = INPUTDIR;
str[end] = '\0';
return &str[end+1];
}
else{
end--;
}
}
return NULL;
}
int main()
{
int num_env = 0;
status_dir = EMPTY;
while(1)
{
//1.
printf("[root$loadhost myshell]# ");
fflush(stdout);
//2.
memset(str,SIZE,'\0');
fgets(str, SIZE, stdin);
int sz = strlen(str);
str[sz - 1] = '\0';
//3.
int end = sz - 2;
char* file_end = getfile(str, end);
_grev[0] = strtok(str, " ");
int index = 1;
//4.
if(strcmp(_grev[0],"ls") == 0)
{
_grev[index++] = (char*)"--color=auto";
}
if(strcmp(_grev[0], "ll") == 0)
{
_grev[0] = (char*)"ls";
_grev[index++] = (char*)"--color=auto";
_grev[index++] = (char*)"-l";
}
while(_grev[index++] = strtok(NULL, " "));
if(strcmp(_grev[0], "cd") == 0)
{
if(_grev[1]) chdir(_grev[1]);
continue;
}
if(strcmp(_grev[0], "export") == 0 && _grev[1])
{
memcpy(_env[num_env],_grev[1],strlen(_grev[1])+1);
putenv(_env[num_env]);
num_env++;
continue;
}
pid_t id = fork();
if(id < 0)
{
perror("fork");
exit(1);
}
else if(id == 0)
{
//child
//5
int fd;
switch (status_dir)
{
case INPUTDIR:
fd = open(file_end, O_RDONLY);
dup2(fd,0);
break;
case OUTPUTDIR:
fd = open(file_end, O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd,1);
break;
case APPPUTDIR:
fd = open(file_end, O_WRONLY | O_CREAT | O_APPEND, 0666);
dup2(fd,1);
break;
case EMPTY:
break;
default:
printf("bug?\n");
break;
}
execvp(_grev[0], _grev);
exit(2);
}
else {
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret < 0)
{
printf("等待子进程失败\n");
exit(2);
}
else{
if(WIFEXITED(status))
{
printf("子进程退出码:%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("子进程终止信号:%d\n",WTERMSIG(status));
}
}
}
}
return 0;
}