目录
模拟封装C库函数
下面我们来模拟实现如下fopen、fclose、fwrite、fflush这几个C库函数,代码如下:
#include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #include<fcntl.h> #include<string.h> #include<assert.h> #include<stdlib.h> #define NUM 1024 #define NONE_FLUSH 0x0 //无缓冲 #define LINE_FLUSH 0x1 //行缓冲 #define FULL_FLUSH 0x2 //全缓冲 typedef struct _MyFILE { int _fileno; char _buffer[NUM]; int _end;//buffer缓冲区的结尾 int _flags;//标记fflush刷新策略 }MyFILE; //fopen MyFILE* my_fopen(const char* filename, const char* method) { assert(filename); assert(method); int flags = O_RDONLY; if (strcmp(method, "r") == 0) { } else if (strcmp(method, "r+") == 0) { } else if (strcmp(method, "w") == 0) { flags = O_WRONLY | O_CREAT | O_TRUNC;//写入方式 } else if (strcmp(method, "w+") == 0) { } else if (strcmp(method, "a") == 0) { flags = O_WRONLY | O_CREAT | O_APPEND;//追加方式 } else if (strcmp(method, "a+") == 0) { } int fileno = open(filename, flags, 0666); if (fileno < 0) { return NULL; } MyFILE* fp = (MyFILE*)malloc(sizeof(MyFILE)); if (fp == NULL) return fp; memset(fp, 0, sizeof(MyFILE)); fp->_fileno = fileno; fp->_flags |= LINE_FLUSH;//默认设置为行缓冲 fp->_end = 0; return fp; } //fflush void my_fflush(MyFILE* fp) { assert(fp); if (fp->_end > 0) { write(fp->_fileno, fp->_buffer, fp->_end); fp->_end = 0; syncfs(fp->_fileno);//把数据刷新到磁盘上 } } //fwrite void my_fwrite(MyFILE* fp, const char* start, int len) { assert(fp); assert(start); assert(len > 0); //abcde123 strncpy(fp->_buffer + fp->_end, start, len);//将数据写入到了缓冲区的结尾 fp->_end += len;//使end永远指向有效字符的下一个位置 if (fp->_flags & NONE_FLUSH) { } else if (fp->_flags & LINE_FLUSH) { if (fp->_end > 0 && fp->_buffer[fp->_end - 1] == '\n') { //仅仅是写入到内核中 write(fp->_fileno, fp->_buffer, fp->_end); fp->_end = 0; syncfs(fp->_fileno); } } else if (fp->_flags & FULL_FLUSH) { } } //fclose void my_fclose(MyFILE* fp) { my_fflush(fp);//先刷新缓冲区 close(fp->_fileno); free(fp); } int main() { MyFILE* fp = my_fopen("log.txt", "w"); if (fp == NULL) { printf("my_fopen error\n"); return 1; } const char* s = "hello my 111\n"; my_fwrite(fp, s, strlen(s)); printf("消息立即刷新"); sleep(2); const char* ss = "hello my 222"; my_fwrite(fp, ss, strlen(ss)); printf("写入了一个不满足刷新条件的字符串\n"); sleep(2); const char* sss = "hello my 333"; my_fwrite(fp, sss, strlen(sss)); printf("写入了一个不满足刷新条件的字符串\n"); sleep(2); const char* ssss = "end\n"; my_fwrite(fp, ssss, strlen(ssss)); printf("写入了一个满足刷新条件的字符串\n"); sleep(2); const char* sssss = "aaaaaaa"; my_fwrite(fp, sssss, strlen(sssss)); printf("写入了一个不满足刷新条件的字符串\n"); sleep(1); my_fflush(fp); sleep(2); my_fclose(fp); return 0; }
gif动图演示:
添加重定向功能到myshell
在我之前的一篇博文中,对shell进行了模拟实现:
不过上述实现的shell有一个不足之处(无法完成重定向操作):
在myshell当中添加重定向功能的步骤大致如下:
- 实现一个CheckDir函数完成检测是否有重定向行为的函数
- 对于获取到的命令进行遍历判断,若命令当中包含重定向符号 >、>>、<,则该命令需要进行处理。为了方便后续操作,我们把遇到的重定向符号设置为\0。
- 实现一个DROP_SPACE函数完成跳过重定向符号后的空格的操作
- 设置一个g_redir_flag变量(重定向标志位),当其为0时表示输入重定向,为1时表示输出重定向,为2时表示追加重定向。
- 设置一个g_redir_filename变量,用来实时指向不同重定向后的文件
- 在fork创建进程那利用switch case语句完成不同重定向操作,内部利用open及dup2函数完成重定向操作。
总体代码如下:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/wait.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<assert.h> #include<ctype.h> #define SEP " "//分隔符 #define NUM 1024 #define SIZE 128 char command_line[NUM]; char* command_args[SIZE]; char env_buffer[NUM];//for test #define NONE_REDIR - 1 #define INPUT_REDIR 0 //输入重定向 #define OUTPUT_REDIR 1 //输出重定向 #define APPEND_REDIR 2 //追加重定向 //跳过重定向符号多余的空格 #define DROP_SPACE(s) do { while (isspace(*s)) s++; } while (0) int g_redir_flag = NONE_REDIR;//重定向标志位 char* g_redir_filename = NULL;//文件名 //对应上层的内建命令 int ChangeDir(const char* new_path) { chdir(new_path); return 0;//调用成功 } void PutEnvInMyShell(char* new_env) { putenv(new_env); } //CheckDir检测 void CheckDir(char* commands) { assert(commands); //[start, end) char* start = commands;//头指针 char* end = commands + strlen(commands);//尾指针 // ls -a -l>log.txt // 从前往后遍历 while (start < end) { if (*start == '>') { if (*(start + 1) == '>') { //追加重定向 *start = '\0'; start += 2;//跳过多余的>,使start指向空格或文件的位置 DROP_SPACE(start);//跳过>>后多余的空格 g_redir_flag = APPEND_REDIR;//设置为追加重定向 g_redir_filename = start;//更新指向文件的地址 break; } else { //输出重定向 *start = '\0'; start++;//使start指向空格或文件的位置 DROP_SPACE(start);//跳过>后多余的空格 g_redir_flag = OUTPUT_REDIR;//设置为输出重定向 g_redir_filename = start;//更新指向文件的地址 break;//解析完毕 } } else if (*start == '<') { //输入重定向 *start = '\0'; start++;//时start指向空格或文件的位置 DROP_SPACE(start);//跳过<后多余的空格 g_redir_flag = INPUT_REDIR;//设置为输入重定向 g_redir_filename = start;//更新指向文件的地址 break; } else { start++; } } } int main() { //shell本质就是一个死循环 while (1) { g_redir_flag = NONE_REDIR; g_redir_filename = NULL; //1、显示提示符 printf("[张三@我的主机名 当前目录]# "); fflush(stdout); //2、获取用户输入 memset(command_line, '\0', sizeof(command_line) * sizeof(char)); fgets(command_line, NUM, stdin);//键盘,标准输入,stdin,获取到的是C风格的字符串,'\0'结尾 command_line[strlen(command_line) - 1] = '\0';//清空回车键的\n //$$重定向操作 //ls -a -l>log.txt 或者 cat < file.txt 或者 ls -a -l>>log.txt 或者 ls -a -l //ls -a -l<log.txt -> ls -a -l\0log.txt CheckDir(command_line); //3、"ls -a -l -i" -> "ls" "-a" "-l" "-i" 字符串切分 command_args[0] = strtok(command_line, SEP); int index = 1; //给ls命令添加颜色 if (strcmp(command_args[0]/*程序名*/, "ls") == 0) { command_args[index++] = (char*)"--color=auto"; } // = 是故意这么写的 // strtok截取成功,返回字符串起始地址;截取失败,返回NULL while (command_args[index++] = strtok(NULL, SEP)); //for debug //for (int i = 0; i < index; i++) //{ // printf("%d : %s\n", i, command_args[i]); //} //4、TOOD,编写后面的逻辑,内建命令 if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL) { ChangeDir(command_args[1]);//让调用方进行路径切换,父进程 continue; } if (strcmp(command_args[0], "export") == 0 && command_args[1] != NULL) { //目前,环境变量信息在command_line,会被清空 //此处我们需要自己保存一下环境变量内容 strcpy(env_buffer, command_args[1]); PutEnvInMyShell(env_buffer);//export myval=100; continue; } //5、创建进程,执行 pid_t id = fork(); if (id == 0) { int fd = -1; switch (g_redir_flag) { case NONE_REDIR: break; case INPUT_REDIR: fd = open(g_redir_filename, O_RDONLY); dup2(fd, 0);//输入重定向 break; case OUTPUT_REDIR: fd = open(g_redir_filename, O_WRONLY | O_CREAT | O_TRUNC); dup2(fd, 1);//输出重定向 break; case APPEND_REDIR: fd = open(g_redir_filename, O_WRONLY | O_CREAT | O_APPEND); dup2(fd, 1);//追加重定向 break; default: printf("Bug?\n"); break; } //child //6、程序替换 execvp(command_args[0]/*此处下标0就是保存的我们要执行的程序名字*/, command_args); exit(1);//执行到这里,子进程一定替换失败 } int status = 0; pid_t ret = waitpid(id, &status, 0); if (ret > 0) { printf("等待子进程成功:sig: %d, code: %d\n", status & 0x7F, (status >> 8) & 0xFF); } }//end while return 0; }
运行结果如下:
gitee代码链接:shell的模拟实现完整版