设置网站建设,网站开发和编程有什么区别,凡科网站建设网站,个人介绍微电影网站模板免费下载文章目录 初次谈论文件重温C语言文件操作系统文件操作接口openwriteread 再次谈论文件文件描述符文件描述符的分配规则 重定向什么是重定向重定向的本质系统调用接口实现重定向、、 初次谈论文件
开始之前先谈论一下关于文件的一些共识性问题。
一个文件可以… 文章目录 初次谈论文件重温C语言文件操作系统文件操作接口openwriteread 再次谈论文件文件描述符文件描述符的分配规则 重定向什么是重定向重定向的本质系统调用接口实现重定向、、 初次谈论文件
开始之前先谈论一下关于文件的一些共识性问题。
一个文件可以分为两部分内容和属性。基于上面的认识空文件也要在磁盘中占据空间因为空文件的内容为空但是还有属性在例如文件的创建时间… 而这部分属性也是要存储的。所以对文件的操作就变成了对文件的内容或对文件的属性进行操作。如果我们要标识一个文件必须要通过路径文件名的方式来唯一标识。当我们使用相关方法进行文件操作的时候我们一般只写一个文件名此时我们并没有指明文件的路径此时默认是在当前路径(访问文件的进程的当前路径)下进行相关文件操作。当我们写完一份代码代码中有对文件进行操作的内容当我们把代码编译成可执行文件后在我们执行这个程序之前文件操作并没有被执行只有当我们运行这个程序程序变成进程之后才会真正执行相应的文件操作所以对文件操作本质上是进程对文件的操作。我们无论是以哪种方式访问文件前提是都要打开这个文件。而打开文件这个动作是谁完成的呢谁能管理文件的存储谁就能打开所以是操作系统打开的或者准确一点是操作系统收到进程的访问请求时打开的。所以文件操作的本质是进程和被打开文件之间的关系。 重温C语言文件操作
我们常说C默认会打开三个输入输出流stdin、stdout、stderr这点后面还会用到。
C语言中有诸如fopen、fclose、fprintf、fscanf、fwrite、fread等涉及文件操作的函数方法。
这里就简单回顾一下部分接口。 FILE * fopen ( const char * filename, const char * mode ); 用fopen函数可以打开一个文件参数分别是文件名和打开方式返回一个FILE指针。 而mode有多种选项例如r - 只读r - 可读可写w - 只写w - 可读可写a - 追加a - 可追加可读… 除此之外还有二进制读写、文件不存在是否创建文件、文件是否会覆盖重写等细节问题。 int fprintf ( FILE * stream, const char * format, ... ); fprintf与普通的printf的区别在于printf是默认向stdout中输出打印而fprintf则可以指定文件。 … 系统文件操作接口
实际上我们所用到的无论是C语言还是python、Java、c它们的相关访问文件的接口虽然各有不同但它们都是对系统提供的文件操作接口的封装。换句话说各个所提提供的文件方法底层都是封装了相同的系统提供的文件操作接口。
下面就简单学习几种文件操作接口。
因为要查系统调用接口所以在使用man手册查询的时候要加个2选项man 2 [name]。
open
#include sys/types.h
#include sys/stat.h
#include fcntl.hint 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: 追加写O_TRUNC: 清空文件//...
mode: 如果创建文件的话文件的访问权限返回值成功新打开的文件描述符失败-1这里提供了两个open函数差别在最后一个mode参数mode其实就是创建文件时文件的默认权限Linux默认是0666想了解文件权限的小伙伴可以跳转到这个链接【Linux】对权限的初步理解_LeePlace的博客-CSDN博客。
参数pathname没什么好说的就是要打开的文件的完整路径不过只有文件名的话就默认当前路径。
下面着重介绍flags。
我们打开文件时是以读的方式还是以写的方式是以文本文件的形式读写还是以二进制文件这些信息都需要通过参数来进行信息传递。
flags是一个int整形拥有32个bit位我们如果给这32个bit位每一位都赋予一定意义比如第一个bit为1就是以读的方式打开第二个bit位为1就是以写的方式打开… 此时每一个bit都是一个标记位而系统给我们提供了一些宏比如O_WRONLY可能是1 0O_CREAT可能是1 1O_TRUNC可能是1 2此时把这三个数按位或就得到一个前三个比特位都是1的flag表示以只写、文件不存在的时候创建文件、打开时清空文件的方式来打开一个文件。
学过c访问文件的方式的小伙伴对这种方式肯定不陌生比如经常用到像ios::in | ios::binary的参数。
open会返回一个int叫文件描述符后面再对文件描述符进行更进一步的讨论。
所以我们现在就可以试着用open来打开一个文件
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);close(fd);return 0;
}因为是以只写的方式打开文件不存在我们选择创建文件所以可以成功创建文件而文件的权限并不是0666这是因为文件掩码的存在这里就不多做解释如果想不受文件掩码的影响可以加一句umask(0)。
当然上面还用到了close这其实就是系统提供的关闭文件的接口参数是要关闭文件的文件描述符就不多做介绍了。
如果想以只读的方式打开文件则是open(FILE_NAME, O_RDONLY);
如果想以追加的方式打开文件则是open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666); write
NAMEwrite - write to a file descriptorSYNOPSIS#include unistd.hssize_t write(int fd, const void *buf, size_t count);DESCRIPTIONfd: 要写入的文件描述符buf: 一个指针指向待写入的数据count: 待写入的数据的大小单位是字节RETURN VALUE成功返回写入的字节数失败返回-1并适当设置errno所以我们可以试着用write向文件中写入
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main()
{int fd open(log.txt, O_WRONLY | O_CREAT | O_TRUNC, 0666);if(fd 0) //打开文件失败{perror(open);return 1;}int cnt 5;char outBuffer[64];while(cnt){//将aaaa和cnt序列化成字符串存储到outBuffer中sprintf(outBuffer, %s:%d\n, aaaa, cnt--);//将outBuffer的内容写入到文件中write(fd, outBuffer, strlen(outBuffer));}close(fd);return 0;
}此时log.txt文件中就写入了我们指定的内容 如果我们打开文件时选择追加上面的操作则会不断向文件中追加相同的内容。 read
NAMEread - read from a file descriptorSYNOPSIS#include unistd.hssize_t read(int fd, void *buf, size_t count);DESCRIPTIONfd: 要读的文件描述符buf: 存放读取的数据count: 要读取的字节数RETURN VALUE如果成功则返回读取的字节数如果出现错误则返回-1并适当地设置errno刚刚我们创建一个文件并向其中写入现在我们试着将写入的内容读出来并进行打印
#include stdio.h
#include string.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.hint main()
{int fd open(log.txt, O_RDONLY, 0666);if(fd 0) //打开文件失败{perror(open);return 1;}char buffer[1024];ssize_t num read(fd, buffer, sizeof(buffer) - 1); //预留一个位置存放\0if(num 0) buffer[num] 0;printf(%s, buffer);close(fd);return 0;
}系统接口就简单介绍这么多。 再次谈论文件
文件描述符
一个进程就可以打开多个文件而系统中又存在着这么多进程所以系统中一定存在着大量的被打开文件而且某些文件还可能被打开了多次而这些被打开的文件毫无疑问也是要被操作系统所管理的。那问题来了操作系统是怎么管理这些被打开文件的呢 —— 先描述再组织。
为了管理文件必定要创建相应的内核数据结构来描述文件这个内核数据结构就是struct file结构体内部包含了文件的大部分属性每一个文件都有一个对应的内核数据结构struct file将这些结构通过一定的数据结构组织起来通过算法对这部分数据结构进行增删查改不就实现了对文件的管理吗
前面介绍open的时候涉及到了文件描述符的概念我们初步知道文件描述符是一个整数那不妨试着打印一下这个整数
#define FILE_NAME(number) log.txt#number int main()
{int fd[5] { 0 };fd[0] open(FILE_NAME(1), O_WRONLY | O_CREAT | O_APPEND, 0666);fd[1] open(FILE_NAME(2), O_WRONLY | O_CREAT | O_APPEND, 0666);fd[2] open(FILE_NAME(3), O_WRONLY | O_CREAT | O_APPEND, 0666);fd[3] open(FILE_NAME(4), O_WRONLY | O_CREAT | O_APPEND, 0666);fd[4] open(FILE_NAME(5), O_WRONLY | O_CREAT | O_APPEND, 0666);for (int i 0; i 5; i)printf(%d\n, fd[i]);return 0;
}此时能想到3、4、5、6、7像是数组下标但为什么是从3开始呢
这就涉及到前面提到的C默认打开的三个输入输出流stdinstdoutstderr。
我们用C语言打开文件时会返回一个FILE*上面三个输入输出流其实也是FILE*类型的 FILE结构体里有一个字段_fileno这个其实就是文件描述符我们可以试着打印一下
int main()
{printf(stdin - %d\n, stdin-_fileno);printf(stdout - %d\n, stdout-_fileno);printf(stderr - %d\n, stderr-_fileno);return 0;
}带着现象和问题下面进行讲解。
我们前面说了C语言的一系列接口是封装的系统接口而系统接口访问文件并不是依托文件名而是文件描述符那怎么通过文件描述符找到对应的文件呢
系统为每个文件都创建了内核数据结构struct file用以保存文件的大部分属性而我们的进程需要找到文件也就是进程需要通过一定的方式与许多的struct file结构关联起来所以最好整一个指针数组数组每一个元素都指向一个struct file所以每个进程都有一个这样的指针数组struct file* fd_array[]称之为文件描述符表所以进程只需要找到这张表就能找到要访问的文件。但是进程与文件的关系不光只靠文件描述符还有其它的一些关系需要描述而这些描述进程和文件之间关系的字段都被封装到了一个结构体struct files_struct中进程的PCB中间接保存了指向这个结构体的指针struct files_struct *files这样进程和它打开的所有文件就建立起了完整的连接。
下面用一张图来描述这个连接 综上我们就知道了文件描述符就是从0开始的小整数当我们第一次打开某个文件时OS要在内存中给文件创建内核数据结构file来描述文件表示一个已经打开的文件对象。
而进程打开文件时必须让进程和被打开文件关联起来进程的PCB中有一个指针files指向一张表files_struct该表中有一个字段是一个指针数组fd_array每一个元素都是指向file的指针而文件描述符就是该数组元素的下标。
因此只要知道了下标就可以找到对应的文件。 文件描述符的分配规则
先看下面这段代码
int main()
{int fd open(myfile, O_RDONLY);if(fd 0){perror(open);return 1;}printf(fd: %d\n, fd);close(fd);return 0;
}运行上面的代码结果是fd: 3。
因为每个进程默认会指向三个files分别是标准输入标准输出标准错误(也是标准输出)所以下标0、1、2都被占用了顺着就分配到了3。
那再看下面这段代码
int main()
{close(0);int fd open(myfile, O_RDONLY);if(fd 0){perror(open);return 1;}printf(fd: %d\n, fd);close(fd);return 0;
}此时代码的运行结果是fd: 0。
由此我们可以推断出文件描述符的分配规则即在files_struct数组当中找到当前没有被使用的最小的一个下标作为新的文件描述符。 重定向
什么是重定向
有基础的小伙伴应该听说过输入重定向、输出重定向和追加重定向。
在命令行中我们可以通过、、分别实现输入重定向、输出重定向和追加重定向。
比如下面这样
输出重定向 追加重定向 输入重定向 简单看来重定向就是本该从file1输入或向file1输出结果却从file2中输入或向file2中输出了。 重定向的本质
上面已经铺垫好了文件描述符的内容所以现在打开一个进程会有下面的一个关系 我们还说C默认会打开三个文件分别是stdin、stdout和stderr而这三个东西的本质是指向三个FILE结构的指针在每个FILE结构里分别存放了一个文件描述符依次是0、1、2所以stdin默认和0绑定stdout默认和1绑定stderr默认和2绑定。
所以我们像stdout中写入本质是像文件描述符1映射的文件写入而1默认是和显示器建立映射关系的如果我们手动改变这个映射关系呢
以下面的代码为例
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.hint main()
{close(1);int fd open(myfile, O_WRONLY|O_CREAT, 00644);if(fd 0){perror(open);return 1;}printf(fd: %d\n, fd);fflush(stdout);close(fd);exit(0);
}首先调用close接口关掉了1也就是断掉了文件描述符1和显示器之间的映射关系 然后我们又打开了一个文件myfile按照文件描述符的分配规则1现在会和刚打开的文件建立起新的映射关系 此时我们再调用printf向stdout中输出会发生什么呢 此时并没有向显示器输出而是输出到了新打开的文件。
再理解一下上面那个过程
首先通过调用close接口断开1与显示器的映射关系注意stdout并不是直接关联的显示器而是它指向的FILE结构体对象内部存储的文件描述符是1fd_array[1]默认指向OS给显示器创建的内核数据结构。
此时我们再打开一个文件然后根据文件描述符的分配规则OS发现1是空的于是1就指向了新打开的文件当我们用printf打印时由于上层stdout的文件描述符还是存的1就会在内核中寻找fd_array[1]对应的文件进行打印操作而此时1已经不再映射显示器而是myfile随意此时打印的内容就到了myfile中。
所以重定向的本质是上层用到的文件描述符不变在内核中更改文件描述符映射的文件。 系统调用接口实现重定向
我们可以通过上面的方法先close再open实现重定向但不够优雅。
实际上操作系统也提供了实现重定向的接口dup/dup2/dup3下面只介绍dup2
SYNOPSIS#include unistd.hint dup2(int oldfd, int newfd);DESCRIPTIONdup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:* If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.* If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.RETURN VALUEOn success, dup2 returns the new descriptor. On error, -1 is returned, and errno is set appropriately.以上内容来自man手册。
解释一下dup2会拷贝oldfd到newfd中如果必要的时候会先关掉newfd但是还有两点需要注意的
如果oldfd是无效的文件描述符那么调用就会失败原有的newfd也不会关闭。如果oldfd是有效的文件描述符而newfd跟oldfd一样也就是传进来的两个文件描述符是相同的那么函数什么也不干。
此时有这样的映射关系 然后我们试着调用dup2(4, 1)映射关系就会发生下面的变化 此时再向stdout中输出也就完成了输出重定向。 、、
命令行解释器中我们可以通过、、分别实现输入重定向。输出重定向、追加重定向那这是怎么实现的呢
我们可以写一个简易的命令行解释器demo
#include stdio.h
#include assert.h
#include stdlib.h
#include unistd.h
#include sys/wait.h
#include sys/types.h
#include string.hchar lineCommand[1024]; // 接收输入的命令
char* myargv[64]; // 存储解析的命令行参数
int lastCode; // 保存子进程的退出码
int lastSig; // 保存子进程的退出信号
char pwd[64]; // 保存当前所在文件路径//解析当前位于哪个文件夹下
char* get_path(char* pwd)
{int pre 0, cur 0;while (pwd[cur]){cur;if (pwd[cur] /){pre cur;}}return pwd pre;
}int main()
{ // 初始化pwdstrcpy(pwd, getenv(PWD));while (1){// 打印命令行提示符并将其从缓冲区刷新打印printf([%s%s %s]$ , getenv(LOGNAME), getenv(HOSTNAME), get_path(pwd));fflush(stdout); // 立即刷新stdout的缓冲区// 用户输入命令记得去掉最后一个\nchar* s fgets(lineCommand, sizeof(lineCommand) - 1, stdin); // 预留一个位置存\0assert(s ! NULL);lineCommand[strlen(lineCommand) - 1] 0; // 去掉最后一个\n(void)s; // 将s置空意思就是后面不用s了// 将命令分解为一个个单字符串int i 0;myargv[i] strtok(lineCommand, );while (myargv[i] strtok(NULL, ));// 如果命令是ls则可能需要配置颜色if (myargv[0] ! NULL strcmp(ls, myargv[0]) 0){myargv[i - 1] (char*)--colorauto;myargv[i] NULL;}// 如果命令是ll则需要特殊处理一下if (myargv[0] ! NULL strcmp(ll, myargv[0]) 0){myargv[0][1] s;myargv[i - 1] (char*)--colorauto;myargv[i] (char*)-l;myargv[i 1] NULL;}// 如果命令是echo则在当前进程就可以完成为内建命令// 这里只支持输出上个进程的退出信息和普通信息if (myargv[0] ! NULL myargv[1] ! NULL strcmp(echo, myargv[0]) 0){// 输出上一个进程的退出信息if (strcmp($?, myargv[1]) 0)printf(code:%d\tsig:%d\n, lastCode, lastSig);// 有啥输出啥else printf(%s\n, myargv[1]);continue;}// 如果命令是cd则只能在当前进程完成因为需要修改PWD - 当前路径if(myargv[0] ! NULL strcmp(myargv[0], cd) 0){if(myargv[1] ! NULL){chdir(myargv[1]);strcpy(pwd, myargv[1]); // 当前所在文件路径也要随之改变}continue;}// 创建子进程进行进程替换pid_t id fork();assert(id ! -1);if (id 0){execvp(myargv[0], myargv);exit(1);}int status 0;pid_t ret waitpid(id, status, 0);assert(ret 0);(void)ret;lastCode (status 8) 0xFF;lastSig status 0x7F;}return 0;
}
在此基础上我们可以添加重定向的功能。
首先我们定义几个宏表示重定向类型并定义保存重定向类型和重定向文件的变量
#define NONE_REDIR 0 // 无重定向
#define INPUT_REDIR 1 // 输入重定向
#define OUTPUT_REDIR 2 // 输出重定向
#define APPEND_REDIR 3 // 追加重定向int redirType NONE_REDIR; // 记录重定向类型
char *redirFile NULL; // 记录重定向文件首先我们需要解析命令判断当前是什么重定向类型并记录重定向的文件所以我们可以写一个command_check函数完成这部分内容
void command_check(char *commands)
{assert(commands);char *start commands;char *end commands strlen(commands);// 开始遍历命令while(start end){// 遍历到了此时可能是输出重定向也可能是追加重定向if(*start ){// 首先将这个位置置零之后解析命令行参数就用不到之后的内容了*start \0;start;// 继续判断下一个字符如果是追加重定向if(*start ){// 类似这样的形式ls -a file.log// 首先设置重定向类型// 但是不要着急保存文件信息// 因为可能存在这种情况ls -a file.logredirType APPEND_REDIR;start;}// 否则就是输出重定向else{redirType OUTPUT_REDIR;}// 我们可以写一个trim_space函数或宏帮我们跳过空格// 之后才可保存文件信息trim_space(start);redirFile start;break;}// 遍历到了此时就是输入重定向else if(*start ){*start \0;start;redirType INPUT_REDIR;// 确保去掉先导空格才可记录重定向文件trim_space(start);redirFile start;break;}else{start;}}
}补充一下上面出现的trim_space函数这里写成了宏函数的形式
#define trimSpace(start) do{\while(isspace(*start)) start;\}while(0)因为真正的命令是靠子进程来执行的所以重定向工作需要子进程完成并且在进程替换之前就要做好所有的重定向工作。在执行进程替换之前需要根据父进程提供的信息判断一下是否需要重定向如果需要的话则执行相关命令
switch(redirType)
{case NONE_REDIR:break;case INPUT_REDIR:{// 先以只读的方式打开文件int fd open(redirFile, O_RDONLY);// 如果打开文件失败就退出进程if(fd 0){perror(open);exit(errno);}// 输入重定向改变文件描述符0的指向dup2(fd, 0);}break;case OUTPUT_REDIR:case APPEND_REDIR:{umask(0);// 无论是输入重定向还是输出重定向都需要以写的方式打开文件// 并且文件不存在时需要创建文件int flags O_WRONLY | O_CREAT;// 如果是追加重定向则添加要进行追加的信息if(redirType APPEND_REDIR) flags | O_APPEND;// 如果是输出重定向则需要先清空文件else flags | O_TRUNC;// 确定好打开文件的方式之后打开文件int fd open(redirFile, flags, 0666);if(fd 0){perror(open);exit(errno);}// 输出和追加都是像显示器输出或追加所以需要改变文件描述符1的指向dup2(fd, 1);}break;
}在每次开始时都初始化一下重定向信息和错误信息添加相关头文件就得到了完整代码
#include stdio.h
#include string.h
#include stdlib.h
#include unistd.h
#include ctype.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include sys/wait.h
#include assert.h
#include errno.h#define NONE_REDIR 0 // 无重定向
#define INPUT_REDIR 1 // 输入重定向
#define OUTPUT_REDIR 2 // 输出重定向
#define APPEND_REDIR 3 // 追加重定向#define trim_space(start) do{\while(isspace(*start)) start;\}while(0)int redirType NONE_REDIR; // 记录重定向类型
char *redirFile NULL; // 记录重定向文件char lineCommand[1024]; // 接收输入的命令
char* myargv[64]; // 存储解析的命令行参数
int lastCode; // 保存子进程的退出码
int lastSig; // 保存子进程的退出信号
char pwd[1024]; // 保存当前所在文件夹名//解析当前位于哪个路径下
char* get_path(char* pwd)
{int pre 0, cur 0;while (pwd[cur]){cur;if (pwd[cur] /){pre cur;}}return pwd pre;
}void command_check(char *commands)
{assert(commands);char *start commands;char *end commands strlen(commands);// 开始遍历命令while(start end){// 遍历到了此时可能是输出重定向也可能是追加重定向if(*start ){// 首先将这个位置置零之后解析命令行参数就用不到之后的内容了*start \0;start;// 继续判断下一个字符如果是追加重定向if(*start ){// 类似这样的形式ls -a file.log// 首先设置重定向类型// 但是不要着急保存文件信息// 因为可能存在这种情况ls -a file.logredirType APPEND_REDIR;start;}// 否则就是输出重定向else{redirType OUTPUT_REDIR;}// 我们可以写一个trim_space函数或宏帮我们跳过空格// 之后才可保存文件信息trim_space(start);redirFile start;break;}// 遍历到了此时就是输入重定向else if(*start ){*start \0;start;redirType INPUT_REDIR;// 确保去掉先导空格才可记录重定向文件trim_space(start);redirFile start;break;}else{start;}}
}int main()
{ // 解析当前文件夹名strcpy(pwd, getenv(PWD));while (1){// 初始化重定向信息和错误信息redirType NONE_REDIR;redirFile NULL;errno 0;// 打印命令行提示符并将其从缓冲区刷新打印printf([%s%s %s]$ , getenv(LOGNAME), getenv(HOSTNAME), get_path(pwd));fflush(stdout); // 立即刷新stdout的缓冲区// 用户输入命令记得去掉最后一个\nchar* s fgets(lineCommand, sizeof(lineCommand) - 1, stdin); // 预留一个位置存\0assert(s ! NULL);lineCommand[strlen(lineCommand) - 1] 0; // 去掉最后一个\n(void)s;// 将命令分解为一个个单字符串int i 0;myargv[i] strtok(lineCommand, );while (myargv[i] strtok(NULL, ));// 如果命令是ls则可能需要配置颜色if (myargv[0] ! NULL strcmp(ls, myargv[0]) 0){myargv[i - 1] (char*)--colorauto;myargv[i] NULL;}// 如果命令是ll则需要特殊处理一下if (myargv[0] ! NULL strcmp(ll, myargv[0]) 0){myargv[0][1] s;myargv[i - 1] (char*)--colorauto;myargv[i] (char*)-l;myargv[i 1] NULL;}// 如果命令是echo则在当前进程就可以完成为内建命令if (myargv[0] ! NULL myargv[1] ! NULL strcmp(echo, myargv[0]) 0){// 输出上一个进程的退出信息if (strcmp($?, myargv[1]) 0)printf(code:%d\tsig:%d\n, lastCode, lastSig);else printf(%s\n, myargv[1]);continue;}// 如果命令是cd则只能在当前进程完成因为需要修改PWD - 当前路径if(myargv[0] ! NULL strcmp(myargv[0], cd) 0){if(myargv[1] ! NULL){chdir(myargv[1]);strcpy(pwd, myargv[1]);get_path(pwd);}continue;}// 创建子进程进行进程替换pid_t id fork();assert(id ! -1);if (id 0){switch(redirType){case NONE_REDIR:break;case INPUT_REDIR:{// 先以只读的方式打开文件int fd open(redirFile, O_RDONLY);if(fd 0){perror(open);exit(errno);}// 输入重定向改变文件描述符0的指向dup2(fd, 0);}break;case OUTPUT_REDIR:case APPEND_REDIR:{umask(0);// 无论是输入重定向还是输出重定向都需要以写的方式打开文件// 并且文件不存在时需要创建文件int flags O_WRONLY | O_CREAT;// 如果是追加重定向则添加要进行追加的信息if(redirType APPEND_REDIR) flags | O_APPEND;// 如果是输出重定向则需要先清空文件else flags | O_TRUNC;// 确定好打开文件的方式之后打开文件int fd open(redirFile, flags, 0666);if(fd 0){perror(open);exit(errno);}// 输出和追加都是像显示器输出或追加所以需要改变文件描述符1的指向dup2(fd, 1);}break;}execvp(myargv[0], myargv);exit(1);}int status 0;pid_t ret waitpid(id, status, 0);assert(ret 0);(void)ret;lastCode (status 8) 0xFF;lastSig status 0x7F;}return 0;
}
这里需要想明白一件事重定向并不会影响父进程因为进程之间具有独立性。