【Linux初阶】基础IO - 简易 shell添加重定向功能

news2024/12/23 17:31:53

🌟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的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/611782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

tinkerCAD案例:1.戒子环

基本戒指 在本课中&#xff0c;您将学习使用圆柱形状制作戒指。来吧&#xff01; 说明 将圆柱体拖动到工作平面上并使其成为孔。 圆柱体应缩放以适合其制造手指。 在本例中&#xff0c;我们将使用 17mm 作为直径&#xff0c;但请根据您的需要随意调整尺寸。 将“圆柱”形状拖…

【操作系统真象还原】第5章:保护模式进阶,向内核迈进(5.1获取物理内存)

目录 5.1 获取物理内存容量 5.1.1 学习 Linux 获取内存的方法 5.1.2 利用 BIOS中断 0x15 子功能 0xe820 获取内存 5.1.3 利用BIOS中断 0x15 子功能 0xe801 获取内存 5.1.4 利用BIOS中断0x15子功能0x88获取内存 5.1.5 实战内存容量检测 5.1 获取物理内存容量 操作系统是计…

构建智能电商推荐系统:大数据实战中的Kudu、Flink和Mahout应用【上进小菜猪大数据】

上进小菜猪&#xff0c;沈工大软件工程专业&#xff0c;爱好敲代码&#xff0c;持续输出干货。 本文将介绍如何利用Kudu、Flink和Mahout这三种技术构建一个强大的大数据分析平台。我们将详细讨论这些技术的特点和优势&#xff0c;并提供代码示例&#xff0c;帮助读者了解如何在…

数据分析--Numpy初级(二)

Numpy初级 Numpy数组属性Numpy的routines函数 Numpy数组属性 Numpy数组的维数成为秩&#xff08;rank&#xff09;&#xff0c;即轴的数量&#xff0c;一维数组的秩为1…。在Numpy中&#xff0c;每一个线性的数组称为是一个轴&#xff08;axis&#xff09;&#xff0c;也就是维…

procomponents组件库采坑日记

ModalForm组件: const formRef useRef<any>();<ModalFormkey{51}title数据仓库formRef{formRef} // 用于获取表单数据autoFocusFirstInput // 自动对话框打开后&#xff0c;首个表单项自动获得焦点width"33%"modalProps{{ // 扩展ant modal属性destroyOnC…

电动力学专题:电磁场规范不变性与规范自由度

对称性&#xff0c;不变性&#xff0c;相对性&#xff0c;协变形 在现代物理学中常常被认为具有相同的含义&#xff08;好拗口&#xff09; 规范与规范的自由度 保证电磁场物理量不改变的情况下&#xff0c;有多组势可供选择&#xff0c;而每组势可以称为一个规范 规范不变性…

linux-动态库制作与使用

​​​​​​(6条消息) linux-静态库制作与使用_云的小站的博客-CSDN博客 目录 创建动态库 使用动态库 根据静态库创建时写的两个源文件&#xff0c;我们来制作动态库 创建动态库 根据这2个源文件和2个头文件我们来学习制作动态库。 我们先让编译两个源文件成.o文件,注意要…

vite技术揭秘--环境变量

目录 环境变量 生产环境替换 自定义环境变量 .env 文件 环境加载优先级 自定义环境变量 模式 TypeScript 的智能提示 在node环境里使用环境变量 前言 我们开发中不可避免的要根据环境变量来做一些逻辑分支&#xff0c;在vite中有两种实现方式&#xff0c;即define和.env…

MIT6.824 lab 1 小白实现过程

1.总体思路 构建一个简单的MapReduce系统&#xff0c;Coordinator线程用于分配任务&#xff08;包括Map任务和Reduce任务&#xff09;&#xff0c;Worker线程向Coordinator线程请求任务&#xff0c;要求所有map任务完成后才可以请求到reduce任务&#xff0c;否则的话这个worker…

Springboot +spring security,基于默认数据库模型实现授权

一.简介 上一篇文章中讲解了如何基于内存模型来实现授权&#xff0c;在这种模型里&#xff0c;用户的信息是保存在内存中的。但是&#xff0c;保存在内存中的信息&#xff0c;是无法持久化的&#xff0c;也就是程序一旦关闭&#xff0c;或者断电等情况发生&#xff0c;内存中的…

0基础学习VR全景平台篇第36篇:场景功能-导览

大家好&#xff0c;欢迎观看蛙色VR官方系列——后台使用课程&#xff01; 本期为大家带来蛙色VR平台&#xff0c;场景管理—导览功能操作。 功能位置示意 一、本功能将用在哪里&#xff1f; 导览&#xff0c;指给VR漫游作品预先设置好路线&#xff0c;并且可以自定义路线的旋…

DMBOK知识梳理for CDGA/CDGP——第三章数据治理

关 注gzh“大数据食铁兽” 回复“知识点”获取《DMBOK知识梳理for CDGA/CDGP》常考知识点&#xff08;第三章数据治理&#xff09; 第三章 数据治理 第三章在是CDGA|CDGP考试的重点考核章节之一&#xff0c;知识点比较密集&#xff0c;本章重点为语境关系图及数据治理概念…

初心不改凌云志 热血浇灌信仰花 《凭栏一片风云起》湖北卫视热力开播

浮光灼夏 御风而行&#xff0c; 由著名导演金琛执导&#xff0c; 胡一天、章若楠、王劲松 张晞临、张赫、林子璐领衔主演&#xff0c; 高伟光特邀出演的 年代战争剧《凭栏一片风云起》&#xff0c; 将于今晚19:30起&#xff0c; 登陆【湖北卫视】长江剧场。 电视剧《凭栏…

音乐人解密:究竟是如何一步一步成为音乐人的?

音乐人解密&#xff1a;究竟是如何一步一步成为音乐人的&#xff1f; 音乐是人类伟大的产物&#xff0c;近些年来越来越多的人都开始尝试学习音乐&#xff0c;成为一名音乐人。而艺术高考等途径也为许多想要学习音乐、成为职业歌手或者编曲师的人群提供了途径。然而想要成为一名…

初识EasyUI

2.1何为EasyUI. EasyUI的全称是“JQuery EasyUI”&#xff0c;是一种基于jQuery、Angular、Vue和React的用户界面的插件的集合&#xff0c;EasyUI的目标就是帮助web开发者更轻松的打造出功能丰富并且美观的UI界面。开发者不需要编写复杂的javascript&#xff0c;也不需要对css样…

【Protobuf速成指南】Win/Centos7下Protobuf安装教程

文章目录 安装教程一、Windows1.1 下载编译器1.2 配置PATH1.3 其他依赖项 二、Centos72.1 安装必要的工具2.2 下载安装包2.3 安装 安装教程 以版本为V21.11为例说明 一、Windows 1.1 下载编译器 下载地址&#xff1a;链接&#xff0c;一直往下翻找到 V21.11版本 win用户根据…

火爆全网,最全性能测试从0到1进阶总结,高阶内卷学习路线...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 例如&#xff1a;…

ArduPilot飞控开源代码之滤波设置

ArduPilot飞控开源代码之滤波设置 1. 源由2. 原理3. 调优3.1 ACC低通滤波 INS_ACCEL_FILTER3.2 GRYO低通滤波 INS_GYRO_FILTER3.3 陷波滤波 INS_HNTCH_ENABLE & INS_HNTC2_ENABLE 4. 总结5. 参考资料 1. 源由 对于飞控传感器来来说&#xff0c;振动噪声也是数据。 单纯从数…

yolov3

文章目录 前言一、主干网络darknet53二、从特征获取预测结果 前言 本文主要讲解yolov3的基本知识&#xff0c;如有错误请指出。 本文主要来自 博客1 博客2 一、主干网络darknet53 53是因为有53层。 1、darknet53没有使用pooling 来进行下采样&#xff0c;而是用一个33&…

电脑数据隐藏原因有哪些?电脑里隐藏的数据怎么恢复

电脑里隐藏的数据怎么恢复&#xff1f;电脑中的数据很容易被隐藏&#xff0c;这时候很多人可能会感到焦急和无助。不过不用担心&#xff0c;本文将为大家介绍三种方法&#xff0c;让你轻松找回被隐藏的数据&#xff01; ※电脑数据隐藏原因有哪些 电脑数据可能会隐藏&#xf…