🌟hello,各位读者大大们你们好呀🌟
🍭🍭系列专栏:【Linux初阶】
✒️✒️本篇内容:shell重定向功能的代码实现
🚢🚢作者简介:计算机海洋的新进船长一枚,请多多指教( •̀֊•́ ) ̖́-
文章目录
- 前言
- 一、Makefile文件
- 二、shell代码修改(添加重定向功能)
- 1.指令切割
- 2.需要子进程实现重定向
- 3.重定向代码填充(整体代码展示)
- 4.运行展示
- (1)输出重定向
- (2)追加重定向
- (3)输入重定向
- 三、重定向不会影响父进程
- 四、重定向不会影响程序替换
- 结语
前言
本篇博客主要讲述 shell重定向功能的代码实现,其他简易shell的基础功能实现可查看作者这篇文章 -> 【Linux初阶】进程替换的应用 - 简易命令行解释器的实现
有对重定向基础概念和实现不清楚的同学可以看这篇文章 -> 【Linux初阶】基础IO - 文件管理(深入理解文件描述符) | 重定向
一、Makefile文件
myshell:myshell.c
gcc - o $@ $ ^ -std = c99 # - DDEBUG
.PHONY:clean
clean :
rm - f myshell
二、shell代码修改(添加重定向功能)
1.指令切割
实现重定向前,先要对指令做分割
- “ls -a -l -i > myfile.txt” -> “ls -a -l -i” “myfile.txt” ->
- “ls -a -l -i >> myfile.txt” -> “ls -a -l -i” “myfile.txt” ->
- “cat < myfile.txt” -> “cat” “myfile.txt” ->
该段代码需要添加在,指令获取且去除 \n 之后
#define NONE_REDIR 0 //宏定义类型
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define trimSpace(start) do{\ //trimSpace过滤函数的实现
while(isspace(*start)) ++start;\ //isspace判断*start是不是空格,是就++跳过
}while(0) //isspace - #include <ctype.h>
int redirType = NONE_REDIR; // 定义初始类型
char* redirFile = NULL;
// "ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
void commandCheck(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"
redirType = APPEND_REDIR;
start++;
}
else
{
// "ls -a > file.log"
redirType = OUTPUT_REDIR;
}
trimSpace(start); //trimSpace过滤函数
redirFile = start;
break;
}
else if (*start == '<')
{
//"cat < file.txt"
*start = '\0'; //对指令进行拆分
start++;
trimSpace(start); //trimSpace过滤函数(实现在上面),对 *start指向的空格进行过滤
// 填写重定向信息
redirType = INPUT_REDIR;
redirFile = start;
break;
}
else
{
start++;
}
}
}
// "ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
// "ls -a -l -i >> myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
// "cat < myfile.txt" -> "cat" "myfile.txt" ->
commandCheck(lineCommand);
———— 我是一条知识分割线 ————
2.需要子进程实现重定向
原来的子进程执行代码
if (id == 0)
{
execvp(myargv[0], myargv);
exit(1);
}
修改后的子进程执行代码
if (id == 0)
{
// 因为命令是子进程执行的,真正重定向的工作一定要是子进程来完成
// 如何重定向,是父进程要给子进程提供信息的
// 这里重定向会影响父进程吗?不会,进程具有独立性
switch (redirType) //根据不同类型,执行不同的功能
{
case NONE_REDIR:
// 什么都不做
break;
case INPUT_REDIR:
{
int fd = open(redirFile, O_RDONLY); //调用open系统接口,需要三个头文件(可自行查看)
if (fd < 0) {
perror("open");
exit(errno);
}
// 重定向的文件已经成功打开了
dup2(fd, 0); //dup2接口实现输入重定向
}
break;
case OUTPUT_REDIR:
case APPEND_REDIR: //两个合封在一起
{
umask(0);
int flags = O_WRONLY | O_CREAT; //写入和创建
if (redirType == APPEND_REDIR) flags |= O_APPEND; //追加下添加flag
else flags |= O_TRUNC; //不是追加要清空
int fd = open(redirFile, flags, 0666);
if (fd < 0)
{
perror("open");
exit(errno);
}
dup2(fd, 1); //dup2接口实现输出重定向
}
break;
default:
printf("bug?\n");
break;
}
execvp(myargv[0], myargv); // 执行程序替换的时候,会不会影响曾经进程打开的重定向的文件?不会
exit(1);
}
———— 我是一条知识分割线 ————
3.重定向代码填充(整体代码展示)
由于重定向是由子进程进行的,进程每次运行完需要恢复 redirType、redirFile、errno,防止被第二次运行的子进程再次使用上一次的端口数据。
redirType = NONE_REDIR;
redirFile = NULL;
errno = 0;
我们可以将上述代码添加到,总的循环开始的地方,即 main函数的 while后。
#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 NUM 1024
#define OPT_NUM 64
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define trimSpace(start) do{\
while(isspace(*start)) ++start;\
}while(0)
char lineCommand[NUM];
char* myargv[OPT_NUM]; //指针数组
int lastCode = 0;
int lastSig = 0;
int redirType = NONE_REDIR;
char* redirFile = NULL;
// "ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
void commandCheck(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"
redirType = APPEND_REDIR;
start++;
}
else
{
// "ls -a > file.log"
redirType = OUTPUT_REDIR;
}
trimSpace(start);
redirFile = start;
break;
}
else if (*start == '<')
{
//"cat < file.txt"
*start = '\0';
start++;
trimSpace(start);
// 填写重定向信息
redirType = INPUT_REDIR;
redirFile = start;
break;
}
else
{
start++;
}
}
}
int main()
{
while (1)
{
redirType = NONE_REDIR;
redirFile = NULL;
errno = 0;
// 输出提示符
printf("用户名@主机名 当前路径# ");
fflush(stdout);
// 获取用户输入, 输入的时候,输入\n
char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
assert(s != NULL);
(void)s;
// 清除最后一个\n , abcd\n
lineCommand[strlen(lineCommand) - 1] = 0; // ?
//printf("test : %s\n", lineCommand);
// "ls -a -l -i" -> "ls" "-a" "-l" "-i" -> 1->n
// "ls -a -l -i > myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
// "ls -a -l -i >> myfile.txt" -> "ls -a -l -i" "myfile.txt" ->
// "cat < myfile.txt" -> "cat" "myfile.txt" ->
commandCheck(lineCommand);
// 字符串切割
myargv[0] = strtok(lineCommand, " ");
int i = 1;
if (myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
{
myargv[i++] = (char*)"--color=auto";
}
// 如果没有子串了,strtok->NULL, myargv[end] = NULL
while (myargv[i++] = strtok(NULL, " "));
// 如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
// 像这种不需要让我们的子进程来执行,而是让shell自己执行的命令 --- 内建/内置命令
if (myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
{
if (myargv[1] != NULL) chdir(myargv[1]);
continue;
}
if (myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
{
if (strcmp(myargv[1], "$?") == 0)
{
printf("%d, %d\n", lastCode, lastSig);
}
else
{
printf("%s\n", myargv[1]);
}
continue;
}
// 测试是否成功, 条件编译
#ifdef DEBUG
for (int i = 0; myargv[i]; i++)
{
printf("myargv[%d]: %s\n", i, myargv[i]);
}
#endif
// 内建命令 --> echo
// 执行命令
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);
}
// 重定向的文件已经成功打开了
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);
}
dup2(fd, 1);
}
break;
default:
printf("bug?\n");
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);
}
}
4.运行展示
将简易shell代码进行编译,形成可运行文件并运行
(1)输出重定向
将 ls -a -l 的内容重定向输出到 log.txt 文件中
(2)追加重定向
将 ls 的内容追加到 log.txt 中
(3)输入重定向
将文件 log.txt 中的内容重定向输入到 cat中
三、重定向不会影响父进程
重定向会影响父进程吗?不会,进程具有独立性
通过上面的代码我们可以知道,重定向是在子进程中运行的,在子进程重定向之前,它会将父进程的 task_struct 和 files_strcut都拷贝一份,用于对文件的操作,但是不需要将存储文件属性或文件内容的数据块再拷贝一次。
四、重定向不会影响程序替换
上图中所有的操作都属于内核数据结构的范畴,而程序替换替换的是代码和数据,和内核数据结构不是一个东西,它们相互之前并没有关系,因此重定向并不会影响程序替换。
结语
🌹🌹 Linux下简易 shell添加重定向功能 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪