Linux基础IO
C语音文件IO
C语言文件接口汇总
C语言中文件操作函数如下:
文件函数操作 | 功能 |
fopen | 打开文件 |
fclose | 关闭文件 |
fputc | 写入一个字符 |
fgetc | 读取一个字符 |
fputs | 写入一个字符串 |
fgets | 读取一个字符串 |
fprintf | 格式化写入数据 |
fscanf | 格式化读取数据 |
fwrite | 向二进制文件写入数据 |
fread | 从二进制文件读取数据 |
fseek | 设置文件指针的位置 |
ftell | 计算当前文件指针相对于起始位置的偏移量 |
rewind | 设置文件指针到文件的起始位置 |
ferror | 判断文件操作过程总是否发生错误 |
feof | 判断文件指针是否读到文件末尾 |
对文件进行写入操作示例:
int main()
{
FILE *fp = fopen("log.txt", "w");
if(fp == NULL)
{
perror("fopen");
return -1;
}
int count = 5;
while(count--)
{
fputs("holle world!!!\n", fp);
}
fclose(fp);
return 0;
}
运行程序后,在当前路径下会生成对应的文件,文件当中就是我们写入的内容
对文件进行读取操作示例:
#include <stdio.h>
int main()
{
FILE *fp = fopen("log.txt", "r");
if(fp == NULL)
{
perror("fopen");
return -1;
}
char buff[128] = {0};
for(int i = 0; i < 10; ++i)
{
fgets(buff, sizeof(buff), fp);
printf("%s\n", buff);
}
fclose(fp);
fp = NULL;
return 0;
}
运行之后我们提取到的内容会打印到显示器上:
什么是当前路径?
当fopen以写入的方式打开一个文件时,若文件不存在,则会自动在当前路径创建该文件,那么这里所说的当前路径指的是什么呢?
例如,我们在test_2_26目录下运行可执行程序myproc,那么该可执行程序创建的log.txt文件会出现在test_2_26目录下
这里是否能说明的“当前路径”是指当前可执行程序所处的路径
这时我们删除刚才可执行程序生成的log.txt先删除
测试一下:回退到上级目录,在上级目录下运行该可执行程序
通过以上测试,log.txt并没有在刚刚的test_2_26目录中创建log.txt文件
当该可执行程序变成进程之后。我们可以获取该进程的PID然后根据该PID在根目录下的proc目录下查看该进程的消息
可以观察到上图有两个软链接文件cwd和exe,cwd就是进程运行时我们所处的路径,而exe就是可执行文件的所处路径
总结:实际上,我们这里所说的当前路径不是指可执行程序所处的路径( /home/zyx/code/myproc),而是指该可执行程序运行成为进程时我们所处的路径( /home/zyx/code)
默认打开三个流
Linux下一切皆文件, 在Linux下任何东西都可以看作是文件,那么显示器和键盘也可以看作是文件,那么显示器和键盘当然也可以是文件。我们能看到显示器上的数据,是因为我们可以向 “显示器文件” 写入了数据,电脑能获取到我们敲击键盘时对应的字符,是因为电脑从 “键盘文件” 读取到了数据
需要注意的是,打开文件时一定是进程运行时打开的,而任何进程在运行的时候都会默认打开三个输入输出流,即:标准输出流,标准输入流,标准错误流,对应到C语言中就是:stdin, stdout,stderr
其中标准输入流对应的设施就是键盘,标准输出流和标准错误流对应的设施就是显示器
查看man手册我们可以发现这三个实际上都是FILE*类型的
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
当我们C程序被运行起来时,操作系统就会默认使用C语言的相关接口将这三个输入输出流打开
我们来做一个测试,以下代码是否会显示到显示器文件上
int main()
{
fputs("hello Linux!\n", stdout);
fputs("hello Linux!\n", stdout);
fputs("hello Linux!\n", stdout);
return 0;
}
运行之后可以看到以上代码的确实出现在了显示器上
注意:不止是C语言中有标准输入流和标准输出流, 标准错误流,基本上市面上所有语言都有这样的概念,例如C++中也有对应的cin,cout,cerr。实际上这种特性并不是某种语言所持有的,而是由操作系统所支持的
open
系统调用接口中使用open函数打开文件,open函数原型如下:
int open(const char *pathname, int flags, mode_t mode)
open的第一个参数
open函数第一个参数是pathname,表示要打开或创建的目标文件
- 若是pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建
- 若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建(注意当前路径的含义)
open第二个参数
open函数的第二个参数是flags,表示打开文件的方式
其中常用选项有如下几个:
参数选项 | 含义 |
O_RDONLY | 以只读的方式打开文件 |
O_WRONLY | 以只写的方式打开文件 |
O_APPEND | 以追加的方式打开文件 |
O_RDWR | 以读写的方式打开文件 |
O_CREAT | 当文件不存在时创建文件 |
O_TRUNC | 打开并清空文件 |
打开文件时,可以传入多个参数选项,当有多个选项传入时,将这些选项用 “或” 运行符隔开
例如,若想以只写的方式打开文件,但当目标文件不存在时自动创建文件,则第二个参数设置如下:
O_WRONLY|O_CREAT
传入多个参数的原理:
#define ONE 1 //1 0001
#define TWO (1<<1) //2 0010
#define THREE (1<<2) //4 0100
#define FOUR (1<<3) //8 1000
void show(int flags)
{
if(flags&ONE) printf("function 1!\n");
if(flags&TWO) printf("function 2!\n");
if(flags&THREE) printf("function 3!\n");
if(flags&FOUR) printf("function 4!\n");
}
int main()
{
show(ONE);
show(ONE|TWO);
show(ONE|TWO|THREE);
show(FOUR);
return 0;
}
运行以上代码:
open的第三个参数
open函数的第三个参数是mode,表示创建文件的默认权限
例如将mode设置为0666,则文件创建出来的权限如下:
-rw-rw-rw-
但实际上创建出来的文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来的文件的权限为:mode&(~umask)。umask的默认值一般为0002,当我们设置mode值为0666时实际上创建出来文件的权限为0664
-rw-rw-r--
若想创建出来文件的权限值不受umask的影响则需要在创建文件前使用umask函数将文件默认掩码值设置为0
umask(0) //将文件默认掩码值设置为0
注意:当不需要创建文件时,open的第三个参数可不必设置
open的返回值
open函数的返回值是新打开文件的文件描述符
我们可以尝试打开多个文件,然后分别打开它们的文件描述符
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
umask(0); //默认掩码设置为0
int fd1 = open("log.txt1", O_RDONLY|O_CREAT|O_TRUNC, 0666);
int fd2 = open("log.txt2", O_RDONLY|O_CREAT|O_TRUNC, 0666);
int fd3 = open("log.txt3", O_RDONLY|O_CREAT|O_TRUNC, 0666);
int fd4 = open("log.txt4", O_RDONLY|O_CREAT|O_TRUNC, 0666);
int fd5 = open("log.txt5", O_RDONLY|O_CREAT|O_TRUNC, 0666);
printf("fd1: %d\n", fd1);
printf("fd2: %d\n", fd2);
printf("fd3: %d\n", fd3);
printf("fd4: %d\n", fd4);
printf("fd5: %d\n", fd5);
return 0;
}
运行程序后可以看到,打开文件的文件描述符是从3开始连续且递增的
我们再尝试打开一个根本不存在的文件,也就是open函数打开文件失败
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd = open("test.txt",O_RDONLY);
printf("fd: %d\n", fd);
return 0;
}
运行程序失败后可以看到,打开文件失败时获取到的文件描述符是-1
实际上这里的文件描述符本质上是一个指针数组的下标,指针数组当中的每一个指针都指向一个被打开文件的文件信息,通过对应文件的文件描述符就可以找到对应的文件信息,当使用open函数打开文件成功时数组当中的指针个数增加,,然后将该指针的在数组当中的下标进行返回,而当文件打开失败时返回-1,因此,成功打开多个文件时所获得的文件描述符就是连续且递增的
而Linux进行默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2,这就是为什么成功打开文件时所得到的文件描述符是从3开始进行分配的
close
系统接口中使用close函数关闭文件,close函数的函数原型如下:
int close(int fd);
使用close函数时传入需要关闭的文件的文件描述符即可,关闭成功返回0,失败则返回-1
write
系统接口中使用write函数向文件写入信息
write函数的函数原型如下:
sszie_t write(int fd, const void *buf, size_tcount);
我们可以使用write函数,将buf位置开始向后count字节的数据写入文件描述符为fd的文件当中
- 如果数据写入成功,则返回实际写入数据的字节个数
- 如果数据写入失败,则返回-1
对文件进行写入操作示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
const char* msg = "hello world!\n";
for(int i = 0; i < 5; i++)
{
write(fd, msg, strlen(msg));
}
close(fd);
return 0;
}
运行程序之后,在当前路径下就会生成对应文件,文件当中就是我们写入的内容
read
系统接口中使用read函数从文件读取信息,read函数的函数原型如下:
ssize_t read(int fd, void *buf, size_t count);
我们可以使用read函数,从文件描述符为fd的文件读取count字节的数据到buf位置当中
- 如果数据读取成功,则返回实际读取数据的字节个数
- 如果数据读取失败,-1被返回
对文件进行读取操作示例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt", O_RDONLY, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
char ch;
while(1)
{
ssize_t s = read(fd, &ch, 1);
if(s <= 0) //返回值为0或者不为1结束
{
break;
}
write(1, &ch, 1);
}
close(fd);
return 0;
}
运行程序之后,就会将我们刚才写入文件的内容读取出来然后打印到显示器上
文件描述符fd
文件由程序运行时打开的,一个进程可以打开多个文件,而系统当中又存在大量进程,也就是说,在系统中任何时刻都可能存在大量已经打开的文件
因此,操作系统务必要对这些已经打开的文件进行管理,操作系统会为每个已经打开的文件创建各自的struct file结构体,然后将这些结构体以双链表的形式连接起来,之后操作系统对文件的管理也就变成了对这张双链表的增删查改等操作
而为了区分已经打开的文件那些属于特定的某一个进程,我们就还需要建立进程和文件之间的对应关系
进程和文件之间的对应关系是如何建立的?
我们知道,当一个程序运行起来时,操作系统会将该程序的代码和数据加载到内存,然后为其创建对应的task_struct,mm_struct,页表等相关的数据结构,并通过页表建立虚拟内存和物理内存之间的映射关系
而task_struct当中有一个指针,该指针指向一个名为files_struct的结构体,在该结构体当中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符
当进程打开log.txt文件时,我们需要先将该文件从磁盘当中加载到内存,形成对应的struct file,将该struct file连入文件双链表中,并将该结构体的首地址填入到fd_array数组中下标为3的指针指向该struct file,最后返回该文件的文件描述符给调用进程即可
因此,我们只要有某一个文件的文件描述符就可以找到与该文件相关的文件信息,进而对文件进行一系列输入输出操作
注意:向文件写入数据时,是先将数据写入到对应文件的缓冲区当中,然后定期将缓冲区数据刷新到磁盘当中
如果两个文件描述符指向同一个文件,这里使用的技术是引用计数
进程创建的时候会默认打开0,1,2
0就是标准输入流,对应键盘;
1就是标准输出流,对应显示器;
2就是标准错误流,对应显示器;
而键盘和显示器属于硬件,属于硬件就意味着系统就会根据键盘,显示器,显示器形成各自的struct file,将这3个struct file连入文件双链表当中,并将这3个struct file的地址分别填入fd_array数组下标为0,1,2的位置,至此就默认打开了标准输入流,标准输出流和标准错误流
磁盘文件VS内存文件
当文件储存在磁盘当中时,我们将其称之为磁盘文件,而当磁盘文件被加载到内存当中后又叫内存文件。磁盘文件和内存文件之间的关系就像程序和进程的关系一样,当程序运行起来后便成了进程,当磁盘文件加载到内存之后就成了内存文件
磁盘文件由两部分构成文件属性+文件内容。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,例如文件名,文件大小以及文件创建时间等信息都是文件属性,文件属性又被称为:元信息
文件加载到内存时,一般先加载文件的属性信息,当需要对文件内容进行读取,输入或输出等操作时,再延后式的加载文件数据
文件描述符的分配规则
连续打开五个文件,看看这五个打开后获取到的文件描述符
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
int fd4 = open("log4.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
int fd5 = open("log5.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
printf("fd1:%d\n", fd1);
printf("fd2:%d\n", fd2);
printf("fd3:%d\n", fd3);
printf("fd4:%d\n", fd4);
printf("fd5:%d\n", fd5);
return 0;
}
可以看到这五个文件获取到的文件描述符都是从3开始连续递增的, 当进程创建时就默认打开了标准输入流,标准输出流和标准错误流,也就是说数组当中下标为0,1,2的位置已经被占用了,所以只能从3开始分配
如果我们再打开这五个文件前,先关闭文件描述符为0的文件,此后文件描述符的分配又会是怎样呢?
close(0);
可以看到,第一个打开的文件获取到的文件描述符变成了0,而之后打开文件获取到的文件描述符还是从3开始依次递增的
我们再试试再打开这五个文件前,将文件描述符为0和2的文件都关闭(不要关闭1号描述符,因为我们需要对显示器进行写入操作)
close(0);
close(2);
可以看到前两个获取到的文件描述符是0和2,之后获取到的文件描述符才是从3开始递增的
结论:从0下标开始,寻找最小但是没有被使用过的fd_array数组下标位置,它的下标就是新文件的文件描述符
重定向
重定向的原理
请看一下三个例子,你会发现重定向的本质就是修改文件描述符下标对应的struct file*的内容
输出重定向的原理
输出重定向就是,将我们本应该输出到一个文件中的数据重定向到另一个文件中
如果我们想让本应该输出到“显示器文件”的数据输出到log.txt文件当中,那么我们可以在打开log.txt文件之前将文件描述符为1的文件的关闭,也就是将 “显示器文件” 关闭, 这样一来,当我们后续打开log.txt文件时所分配到的文件描述符就是1
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
close(1);
int fd = open("log.txt", O_RDWR|O_CREAT|O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("1号\n");
printf("1号\n");
printf("1号\n");
printf("1号\n");
printf("1号\n");
close(fd);
return 0;
}
运行结果后,我们发现显示器上并没有输出数据,对应数据输出到了log.txt文件当中
对以上现象进行说明:
1.printf函数是默认向stdout输出数据的,而stdout指向的是一个struct FILE类型的结构体,该结构体当中封装了一个文件描述符的变量,而stdout指向的FILE结构体中存储的文件描述符就是1,因此printf实际上就是向1号文件描述符的文件输出数据
2.C语言的数据并不是立马写到了内存操作系统里面,而是写到C语言的缓冲区中,所以使用printf打印完后需要使用fflush将C语言缓冲区当中的数据刷新到文件中
追加重定向的原理
输出重定向和追加重定向最大的区别:输出重定向是覆盖式输出数据,而追加重定向是追加式输出数据
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
close(1);
int fd = open("log.txt", O_RDWR|O_CREAT|O_APPEND, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("hello world!!!\n");
printf("hello world!!!\n");
printf("hello world!!!\n");
printf("hello world!!!\n");
printf("hello world!!!\n");
fflush(stdout);
close(fd);
return 0;
}
查看log.txt文件时,我们可以发现数据追加式的输出到了log.txt文件当中
输入重定向原理
输入重定向就是。将我们本应该从一个文件读取数据,现在重定向为另一个文件读取数据
例如,如果我们让本应该从 “键盘文件” 读取数据的scanf函数,改为从log.txt文件当中读取数据那么我们可以在打开log.txt文件之前将文件描述符为0的文件关闭,也就是将 “键盘文件” 关闭,这样一来,当我们后续打开log.txt文件时所分配道德文件描述符就是0
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
close(0);
int fd = open("log.txt", O_RDONLY|O_CREAT, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
char str[40];
while(scanf("%s", str) != EOF)
{
printf("%s\n", str);
}
close(fd);
return 0;
}
运行结果后,我们可以发现scanf函数将log.txt文件当中的数据都读取出来了
说明一下:
scanf函数是默认从stdin读取数据的,而stdin指向的FILE结构体中存储数据的文件描述符是0,因此scanf实际上就是向文件描述符为0的文件读取数据
标准输出流和标准错误流对应的都是显示器,它们有什么区别了?
我们来看看下面这段代码,代码中分别向标准输出流和标准错误流输出了两行字符串
int main()
{
printf("hello printf\n"); //stdout
perror("error"); //stderr
fprintf(stdout, "hello stdout!!!\n");
fprintf(stderr, "hello stderr!!!\n");
return 0;
}
直接运行程序,结果很显然就是在显示器上输出四行字符串
但是我们将运行结果重定向输出到log.txt文件上,我们会发现log.txt当中只有向标准输出流输出的两行字符串,而向标准错误流输出的两行数据并没有重定向到文件当中,而是仍然输出到了显示器上
实际上我们使用重定向时,重定向的是文件描述符是1的标准输出流,而并不会对文件描述符是2的标准错误流进行重定向
dup2
要完成重定向我们只需要进行fd_array数组当中元素的拷贝即可。例如,我们若是将fd_array[3]当中的内容拷贝到fd_array[1]当中,因为C语言当中的stdout就是向文件描述符为1的文件输出数据,那么此时输出重定向就定到了log.txt中
在Linux操作系统中提供了dup2,我们可以使用该函数完成重定向操作。
dup函数原型:
int dup2(int oldfd, int newfd);
函数功能:dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中,如果有必要的话我们需要先使用关闭文件描述符为newfd的文件
函数返回值:dup2如果调用成功,返回newfd,否则返回-1
使用dup2时,我们需要注意以下两点:
1.如果oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭
2.如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd
例如,我们将打开log.txt获取到的文件描述符和1传入dup2函数,那么dup2将会把fd_array[fd]的内容拷贝fd_array[1]中,在代码中我们向stdout输出数据,而stdout是向文件描述符为1的文件输出数据,因此,本应该输出到显示器的数据就会重定向输出到log.txt文件当中
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt", O_CREAT|O_WRONLY|O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
printf("hello Linux!!\n");
fprintf(stdout, "hello stdout\n");
return 0;
}
运行代码后,我们即可发现数据被输出到了log.txt文件中
其实我们只需要知道int dup2(int oldfd, int newfd);的两个参数当中最后只有oldfd会被留下,newfd会被覆盖并关掉
添加重定向功能到myshell
在myshell当中添加重定向功能的步骤大概如下:
1.对于获取到的命令进行判断,若命令当中包含重定向符号>,>>或是<,则该命令需要进行处理
2.设置rdir变量,rdir为0表示命令当中包含输出重定向,rdir为1表示命令当中包含追加重定向,rdir为2表示命令当中有输入重定向
3.重定向符号后面的字段标识为目标文件名,若rdir为0,则以写的方式打开文件;若rdir值为1,则以追加的方式打开目标文件;若rdir值为2,则以读的方式打开目标文件
4.若rdir值为0或者为1,则使用dup2接口实现目标文件与标准输出流的重定向;若rdir值为2,则使用dup2接口实现目标文件与标准输入流的重定向
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <ctype.h>
#define LEFT "["
#define RIGHT "]"
#define DELIM " \t" //忽视tab建
#define LINE_SIZE 1024
#define LABLE "#"
#define ARGV_SIZE 32
#define EXIT_CODE 44
#define OUT_REDIR 0
#define APPEND_REDIR 1
#define IN_REDIR 2
#define NONE -1
int rdir = 0; //判断重定向类型
int lastcode = 0;
int quit = 0;
char *argv[ARGV_SIZE];
char commandline[LINE_SIZE];
char pwd[LINE_SIZE];
const char **environ;
char myenv[LINE_SIZE];
char *rdirfilename;
const char *getusername()
{
return getenv("USER");
}
const char *gethostname()
{
return getenv("HOSTNAME");
}
void getpwd()
{
getcwd(pwd, sizeof(pwd));
}
void check_redir(char *cmd)
{
char *pos = cmd; //避免commandline被破坏
while(*pos)
{
if(*pos == '>')
{
if(*(pos + 1) == '>') //追加重定向
{
*pos++ = '\0';
*pos++ = '\0';
while(isspace(*pos)) pos++;
rdirfilename = pos;
rdir = APPEND_REDIR;
break;
}
else //输出重定向
{
*pos++ = '\0';
while(isspace(*pos)) pos++;
rdirfilename = pos;
rdir = OUT_REDIR;
break;
}
}
else if(*pos == '<') //输入重定向
{
*pos++ = '\0';
while(isspace(*pos)) pos++;
rdirfilename = pos;
rdir = IN_REDIR;
break;
}
else
{
//do nothing
}
pos++;
}
}
char* interact(char *cline, int size)
{
//更新当前路径
getpwd();
printf(LEFT"%s@%s:~%s"RIGHT""LABLE" " , getusername(), gethostname(), pwd);
//从缓冲区读取数据到cline中
char *s = fgets(cline, size, stdin);
assert(s);
//定义变量而不使用,编译器可能会报错
//为了避免编译器报错,这里我们需要使用s
(void)s;
//输入“abcd\n\0”,读取缓冲区数据时,肯定会读取到\n,我们不需要\n
if(s) cline[strlen(cline) - 1] = '\0';
check_redir(cline);
}
//2.子串分割问题
int splitstring(char *cline, char *argv[])
{
int i = 0;
argv[i++] = strtok(cline, DELIM);
while(argv[i++] = strtok(NULL, DELIM)); //接着第一次的继续切割
return i - 1; //最后i会多加一次
}
//普通命令的执行需要fork创建子进程
void NormalExcute(char *argv[])
{
pid_t id = fork();
if(id == 0) //child
{
int fd = 0;
//execvpe(argv[0], argv, environ); 也可以不使用execvpe因为子进程会继承父进程的路径
//让子进程执行命令
//在进行重定向的时候,进程替换不会影响到重定向?
if(rdir == IN_REDIR)
{
fd = open(rdirfilename, O_RDONLY);
dup2(fd, 0);
}
else if(rdir == OUT_REDIR)
{
fd = open(rdirfilename, O_WRONLY|O_CREAT|O_TRUNC, 0666); //可以设置权限
dup2(fd, 1);
}
else if(rdir == APPEND_REDIR)
{
fd = open(rdirfilename, O_WRONLY|O_CREAT|O_APPEND, 0666);
dup2(fd, 1);
}
execvp(argv[0], argv);
exit(EXIT_CODE);
}
else if(id > 0) //parent
{
int status = 0; //输出型参数
pid_t rid = waitpid(id, &status, 0); //WONHANG 非等待轮询
//阻塞等待
if(id == rid)
{
lastcode = WEXITSTATUS(status);
}
}
else //error
{
perror("Normal::Excute");
return;
}
}
//内建命令的处理
int bulidCommand(char *argv[], int argc)
{
if(argc == 2 && strcmp(argv[0], "cd") == 0)
{
chdir(argv[1]); //系统调用接口chdir更改当前路径
getpwd();//刷新pwd中路径
sprintf(getenv("PWD"), "%s", pwd); //修改环境变量PWD的值
return 1;
}
else if(argc == 2 && strcmp(argv[0], "export") == 0)
{
//往环境变量表中导入新元素时并不是将环境变量的value值拷贝进表中而是将指向它的指针放入表中
//所以我们需要将环境变量单独放在一个空间中,避免它的数据被覆盖(commandline)
strcpy(myenv, argv[1]);
putenv(myenv);
return 1;
}
else if(argc == 2 && strcmp(argv[0], "echo") == 0)
{
if(argv[1] == "$?")
{
printf("%d\n", lastcode); //返回上一次的退出码
lastcode = 0;
}
else if(*argv[1] == '$')
{
char *val = getenv(argv[1] + 1);
if(val) printf("%s\n", val); //输除环境变量的值
}
else
{
printf("%s\n", argv[1]); //直接输出
}
return 1;
}
if(argc == 1 && strcmp(argv[0], "ls")) //普通命令
{
argv[argc++] = "--color";
argv[argc] = NULL;
}
return 0;
}
int main()
{
while(!quit)
{
rdir = NONE;
rdirfilename = NULL; //每次输出命令都需要清空rdirfilename
//1.交互问题 获取命令行
interact(commandline, sizeof(commandline));
//debug
//2.子串分割问题,解析命令行
int argc = splitstring(commandline, argv);
if(argc == 0) continue;
//3.指令的判断 内建命令和普通命令
//内建命令
int n = bulidCommand(argv, argc);
if(!n) NormalExcute(argv);
//普通命令
}
return 0;
}