操作系统实验四 (综合实验)设计简单的Shell程序

前言

因为是一年前的实验,很多细节还有知识点我都已经遗忘了,但我还是尽可能地把各个细节讲清楚,请见谅。

1.实验目的

综合利用进程控制的相关知识,结合对shell功能的和进程间通信手段的认知,编写简易shell程序,加深操作系统的进程控制和shell接口的认识。

2.实验内容

可以使用Linux或其它Unix类操作系统,全面实践进程控制、进程间通信的手段,编写简易shell程序要求如下:

1. 学习Shell,系统编程,实现一个基本的Shell。

2. shell是Linux等系统中的一个命令解释器, 它接受输入的命令, 解释之后与操作系统进行交互. 在Linux终端Terminal输入的指令就是被shell接收的。在shell中实现输入输出。

3. 在自己编写的Shell中 实现bash的基本指令包括 cd ,ls 管道等指令

3.实验的内容与过程

实验前需要掌握的知识点:

在实验前,我们应该先明白shell有以下几个功能:

实现一个命令解析的程序

命令包含内部命令、外部命令和非法命令

内部命令包含:使用帮助的help命令,打印内容echo,目录切换cd,退出程序exit或q

外部命令包含:系统命令,在$PATH下的命令

非法命令:找不到的命令

Shell的工作流程主要如下:

①打印提示符:可参照bash提示符,如用户名@主机名,或者自定义提示符,如myshell >

②接收用户输入的命令:按行读取内容

③解析用户输入的命令:解析行内容,按照空格来分隔成字符串数组

④执行命令:执行命令并打印命令结果到终端

⑤循环第一步

初步框架:

根据shell的工作流程,我们不难得出代码的初步框架:

根据上述代码框架,我们先编写出自己的框架。

分析:

getcwd函数用于获取当前工作目录。因为shell的命令输入是一直运行的,所以我们用while(1)来确保其不断运行。接着在while中有三个自定义函数,分别为:print_promt(用来显示命令行提示符:打印用户名和当前工作目录)、read_line(用来读取用户输入的命令)、parse_cmd(用来处理用户输入的命令)。接下来我就解释一下这三个自定义函数的功能。

print_promt函数:

print_promt函数:(打印的用户名一定要记得改)

分析:

这个函数的主要功能为打印命令行提示符,打印的内容主要为用户名和当前的工作目录,分别用蓝色字体和绿色字体输出。

打印结果:

read_line函数:

分析:

这个函数的主要功能为读取用户输入的命令,通过fgets函数将命令写入全局变量command中,如果读入失败的话,则退出程序。

Parse_command函数:

分析:

这个函数主要用来判断用户输入的命令是什么命令。当有输入命令时,用自定义函数check来判断,如果返回值为1则说明当前命令为内部命令,调用handleInternalCommand这个自定义函数来处理内部命令。如果在command里面查找到有字符‘|’则说明是一个带有管道的命令,那么就调用executeCommandWithPipes这个自定义函数来处理命令。如果上述两种情况都不满足,则说明是外部命令,便调用自定义函数executeExternalCommand来处理外部命令。接下来就对这几个自定义函数进行解释。

Check函数:

分析:

这个函数主要用来判断命令是否为内部命令,如果是则返回1,否则返回0。(命令可以自行添加)

HandleInternalCommand函数:

分析:

这个函数主要用来处理内部命令。因为我实现了三种内部命令(help、exit和cd)。如果判断出输入的命令为help,则打印出我们的提示信息。如果判断出输入的命令为exit,则退出程序。如果判断输入的命令为cd,则将路径存放起来,改变当前工作目录并保存。如果这三个命令都不是,则输出无效命令的提示消息。

Executeexternalcommand函数:

分析:

这个函数主要用来执行外部命令。如果我们判断当前输入命令为外部命令时调用该函数。在这个函数中,我们先创建一个子进程,在子进程中,我们分割出命令,然后调用execvp函数去执行外部命令。如果执行时出错,则说明是无效命令,并输出相应的提示,一直等到子进程结束。

executeCommandWithPipes函数:

分析:

这个函数主要是执行带有管道的命令。首先将命令按照管道符号“|”分割成多个子命令(由于分割的技巧,我的管道可以识别字符‘|’有空格和没有空格的情况,下面会展示),接着根据子命令个数创建相应个数的管道。接着在管道数组中创建子进程(跟执行外部命令时的道理类似)。在执行子进程时,我们要判断该子进程是第几个子进程:如果不是第一个子命令,则将管道的输入重定向到上一个子命令的输出;如果不是最后一个子命令,则将管道的输出重定向到下一个子命令的输入。(这两个过程可以通过调用dup2函数实现),接着关闭所有管道文件描述符并且执行子命令,一直等到所有子进程都结束了才算真正的执行完成了。注意,一定要关闭所有的管道文件描述符。

将以上的代码结合在一起就能实现一个简单的shell。至此,本次实验已经成功完成了。

4.完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define MAX_COMMAND_LENGTH 100
#define MAX_ARGUMENTS 10
#define MAX_PIPES 10

#define RED "\e[0;31m"
#define L_RED "\e[1;31m"
#define GREEN "\e[0;32m"
#define L_GREEN "\e[1;32m"
#define BLUE "\e[0;34m"   //正常设置
#define L_BLUE "\e[1;34m" //蓝色线条粗一些(0为正常,1为粗体)
#define END "\033[0m"

void handleInternalCommand(char *command);
void executeExternalCommand(char *command);
void executeCommandWithPipes(char *command);

char *inner_commmand[] = {"help", "exit", "cd"}; // 内部命令
char current_directory[MAX_COMMAND_LENGTH];      // 当前工作目录
char command[MAX_COMMAND_LENGTH];    //存放输入命令


//判断是否为内部命令
int check(char *command)
{
    for (int i = 0; i < 3; i++)
    {
        if (i == 2)
        {
            if (strncmp(command, "cd ", 3) == 0)
                return 1;
        }

        if (strcmp(command, inner_commmand[i]) == 0)
            return 1;
    }
    return 0;
}

// 输出命令行提示符
void print_prompt()
{
    printf("\e[1;34msztu202100202016\033[0m:\e[1;32m%s\033[0m$ ", current_directory);
}

void read_line()
{
    // 读取用户输入的命令
    if (fgets(command, sizeof(command), stdin) == NULL)
    {
        // 读取失败,退出程序
        exit(0);
    }

    // 删除末尾的换行符
    command[strcspn(command, "\n")] = '\0';
}

void parse_cmd()
{
    if (strlen(command) > 0)
    {
        if (check(command) == 1)
        {
            // 内部命令
            handleInternalCommand(command);
        }
        else if (strstr(command, "|") != NULL)
        {
            // 带有管道的命令
            executeCommandWithPipes(command);
        }
        else
        {
            // 外部命令
            executeExternalCommand(command);
        }
    }
}

// 内部命令的处理函数
void handleInternalCommand(char *command)
{
    if (strcmp(command, "help") == 0)
    {
        printf("这是一个简单的shell程序。\n");
        printf("支持的命令:\n");
        printf("  help - 显示帮助信息\n");
        printf("  exit - 退出myshell\n");
        printf("  cd [目录] - 改变当前工作目录\n");
        printf("  author:Horizon\n");
    }
    else if (strcmp(command, "exit") == 0)
    {
        exit(0);
    }
    else if (strncmp(command, "cd ", 3) == 0)
    {
        // 获取目标目录
        char *targetDir = &command[3];

        // 改变当前工作目录
        if (chdir(targetDir) != 0)
        {
            printf("无法改变目录:%s\n", targetDir);
        }
        else
        {
            getcwd(current_directory, sizeof(current_directory)); // 更新当前工作目录
        }
    }
    else
    {
        printf("无效命令:%s\n", command);
    }
}

// 执行外部命令
void executeExternalCommand(char *command)
{
    pid_t pid = fork();

    if (pid < 0)
    {
        // fork() 出错
        printf("无法创建子进程。\n");
        return;
    }
    else if (pid == 0)
    {
        // 子进程
        char *args[MAX_ARGUMENTS];

        // 将命令分割成参数
        int argIndex = 0;
        char *token = strtok(command, " ");
        while (token != NULL && argIndex < MAX_ARGUMENTS - 1)
        {
            args[argIndex] = token;
            token = strtok(NULL, " ");
            argIndex++;
        }
        args[argIndex] = NULL;

        // 执行外部命令
        execvp(args[0], args);

        // execvp() 只在出错时返回
        printf("无效命令:%s\n", command);
        exit(0);
    }
    else
    {
        // 等待子进程结束
        int status;
        waitpid(pid, &status, 0);
    }
}

// 执行带有管道的命令
void executeCommandWithPipes(char *command)
{
    char *pipes[MAX_PIPES];
    int pipeCount = 0;

    // 将命令按照管道符号 "|" 分割成多个子命令
    char *token = strtok(command, "|");
    while (token != NULL && pipeCount < MAX_PIPES)
    {
        pipes[pipeCount] = token;
        token = strtok(NULL, "|");
        pipeCount++;
    }

    // 创建管道
    int pipefds[2 * pipeCount];
    for (int i = 0; i < pipeCount; i++)
    {
        if (pipe(pipefds + 2 * i) < 0)
        {
            perror("无法创建管道");
            exit(1);
        }
    }

    // 执行子命令
    for (int i = 0; i < pipeCount; i++)
    {
        pid_t pid = fork();
        if (pid < 0)
        {
            // fork() 出错
            perror("无法创建子进程");
            exit(1);
        }
        else if (pid == 0)
        {
            // 子进程

            // 如果不是第一个子命令,则将管道的输入重定向到上一个子命令的输出
            if (i > 0)
            {
                if (dup2(pipefds[2 * (i - 1)], STDIN_FILENO) < 0)
                {
                    perror("无法重定向输入");
                    exit(1);
                }
            }

            // 如果不是最后一个子命令,则将管道的输出重定向到下一个子命令的输入
            if (i < pipeCount - 1)
            {
                if (dup2(pipefds[2 * i + 1], STDOUT_FILENO) < 0)
                {
                    perror("无法重定向输出");
                    exit(1);
                }
            }

            // 关闭所有管道文件描述符
            for (int j = 0; j < 2 * pipeCount; j++)
            {
                close(pipefds[j]);
            }

            // 执行子命令
            executeExternalCommand(pipes[i]);

            exit(0);
        }
    }

    // 关闭所有管道文件描述符
    for (int i = 0; i < 2 * pipeCount; i++)
    {
        close(pipefds[i]);
    }

    // 等待所有子进程结束
    for (int i = 0; i < pipeCount; i++)
    {
        int status;
        wait(&status);
    }
}

int main()
{
    getcwd(current_directory, sizeof(current_directory)); // 获取当前工作目录

    while (1)
    {
        // 显示命令行提示符
        print_prompt();

        //读取命令
        read_line();

        // 处理命令
        parse_cmd();
    }

    return 0;
}

运行的结果大家自行试验就行了,我就不展示了。

至此,我们的实验大功告成!如果大家有什么想法,可以在评论区提出,一起交流。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

轻松拿捏C语言——【字符函数】字符分类函数、字符转换函数

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f308;感谢大家的阅读、点赞、收藏和关注&#x1f495; &#x1f339;如有问题&#xff0c;欢迎指正 感谢 目录&#x1f451; 一、字符分类函数&#x1f319; 二、字符转换函数…

Spring MVC/Web

1.Spring MVC 的介绍 Spring Web MVC是基于Servlet API构建的原始Web框架&#xff0c;也是Spring框架的一部分。它提供了灵活可扩展的MVC架构&#xff0c;方便开发者构建高性能的Web应用程序&#xff0c;并与 Spring 生态系统无缝集成。 2.MVC 设计模式 MVC&#xff08;Model…

【游戏引擎】Unity脚本基础 开启游戏开发之旅

持续更新。。。。。。。。。。。。。。。 【游戏引擎】Unity脚本基础 Unity脚本基础C#语言简介C#基础 Unity脚本基础创建和附加脚本MonoBehaviour生命周期生命周期方法 示例脚本 Unity特有的API常用Unity API 实践示例&#xff1a;制作一个简单的移动脚本步骤1&#xff1a;创建…

SpringCloud系列(30)--准备使用Hystrix的前期工作,创建服务消费者模块

前言&#xff1a;在上一章节中我们创建了服务提供者模块&#xff0c;而本节内容则是创建服务消费者模块。 1、创建一个服务提供者模块&#xff0c;命名为cloud-consumer-feign-hystrix-order80 (1)在父工程下新建模块 (2)选择模块的项目类型为Maven并选择模块要使用的JDK版本 …

Ansible自动化运维中的Setup收集模块应用详解

作者主页&#xff1a;点击&#xff01; Ansible专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月22日13点14分 &#x1f4af;趣站推荐&#x1f4af; 前些天发现了一个巨牛的&#x1f916;人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xf…

分享:怎么才能保证大数据查询的准确性?

随着大数据应用到金融风控领域&#xff0c;大数据越来越重要了&#xff0c;很多朋友在查大数据的时候都会遇到一个问题&#xff0c;那就是自己查询的大数据什么信息都没有&#xff0c;要么就是很少&#xff0c;这是什么原因呢?要怎么才能保证大数据查询的准确性呢?下面小编就…

WordPress搭建流程

1. 简介 WordPress 是一个 PHP 编写的网站制作平台。WordPress 本身免费,并且拥有众多的主题可以使用,适合用于搭建个人博客、公司官网、独立站等。 2. 环境准备 2.1 WordPress 下载 WordPress 可以在 Worpress中文官网 下载(如果后续要将后台调成中文的话,一定要从中文…

如何通过软件SPI读写W25Q64

STM32F1之SPI通信软件SPI代码编写-CSDN博客 目录 1. W25Qxx系列简介 2. W25Q64硬件电路 3. W25Q64框图 4. Flash操作注意事项 5. 代码编写 5.1 初始化 5.2 W25Q64读取ID号 5.3 W25Q64写使能 5.4 W25Q64等待忙 5.5 W25Q64页编程 5.6 W25Q64扇区擦除&#x…

521源码-在线客服-CRMChat网页版客服系统 UNIAPP 全方位在线客服系统源码与管理体系平台

CRMChat客服系统&#xff1a;基于Swoole4Tp6RedisVueMysql构建的高效沟通桥梁 CRMChat是一款独立且高性能的在线客服系统&#xff0c;它结合了Swoole4、Tp6、Redis、Vue以及Mysql等先进技术栈&#xff0c;为用户提供了卓越的在线沟通体验。该系统不仅支持在Pc端、移动端、小程…

16.线性回归代码实现

线性回归的实操与理解 介绍 线性回归是一种广泛应用的统计方法&#xff0c;用于建模一个或多个自变量&#xff08;特征&#xff09;与因变量&#xff08;目标&#xff09;之间的线性关系。在机器学习和数据科学中&#xff0c;线性回归是许多入门者的第一个模型&#xff0c;它…

【机器学习】机器学习基础概念与初步探索

❀机器学习 &#x1f4d2;1. 引言&#x1f4d2;2. 机器学习概述&#x1f4d2;3. 机器学习基础概念&#x1f389;2.1 机器学习的分类&#x1f389;2.2 数据预处理&#x1f308;数据清洗与整合&#x1f308; 特征选择和特征工程&#x1f308;数据标准化与归一化 &#x1f4d2;4. …

Android Studio 所有历史版本下载

一、官网链接 https://developer.android.google.cn/studio/archive 操作 二、AndroidDevTools地址 https://www.androiddevtools.cn/ 参考 https://blog.csdn.net/qq_27623455/article/details/103008937

电表远传抄表是什么?

1.电表远传抄表&#xff1a;简述 电表远传抄表&#xff0c;又称为远程控制自动抄表系统&#xff0c;是电力行业的智能化技术运用&#xff0c;它通过无线或通信网络技术&#xff0c;完成对电表数据信息的远程收集解决。此项技术不仅提升了抄水表高效率&#xff0c;降低了人工偏…

RK3568平台(UART篇)uart应用编程读取模块数据

一.串口介绍 串口设备是嵌入式开发中最常用的外设之一&#xff0c;通过串口打印信息可以调试程序的运行&#xff0c;通 过串口也可以链接很多种外设&#xff0c;比如串口打印机&#xff0c;蓝牙&#xff0c;wifi&#xff0c;GPS&#xff0c;GPRS 等等。 数据传输方式&#xf…

C++ | Leetcode C++题解之第97题交错字符串

题目&#xff1a; 题解&#xff1a; class Solution { public:bool isInterleave(string s1, string s2, string s3) {auto f vector <int> (s2.size() 1, false);int n s1.size(), m s2.size(), t s3.size();if (n m ! t) {return false;}f[0] true;for (int i …

全同态加密生态项目盘点:FHE技术的崛起以及应用

撰文&#xff1a;Chris&#xff0c;Techub News 在当今数字化的时代&#xff0c;隐私保护已成为一个全球性的焦点话题&#xff0c;特别是在加密货币和区块链技术快速发展的背景下。虽然当前的隐私技术在保护数据安全方面多有欠缺&#xff0c;引发了广泛的关注和批评&#xff0c…

Java枚举的本质

目录 1.枚举简介 1.1.规范 1.2.枚举类真实的样子 1.3.枚举类的特点 1.4.枚举可以使用的方法 1.4.1.toString()方法 1.4.2.valueOf方法 1.4.3.values方法 1.4.4.ordinal方法 1.5.枚举的用法 1.5.1.常量 1.5.2.switch 1.5.3.枚举中增加方法 1.5.4.覆盖枚举方法 1.5…

热题系列章节1

22. 括号生成 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”] 示例 2&#xff1a…

LeetCode/NowCoder-链表经典算法OJ练习3

孜孜不倦&#xff1a;孜孜&#xff1a;勤勉&#xff0c;不懈怠。指工作或学习勤奋不知疲倦。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;返回倒数第k个节点 题目二&#xff1a;链表的回文结构 题目三&#xff1a;相交链表 SUMUP结尾 说在前…

两篇文章讲透数据结构之堆(一)!

目录 1.堆的概念 2.堆的实现方式 3.堆的功能 4.堆的声明 5.堆的实现 5.1堆的初始化 5.2堆的插入 5.2.1向上调整算法 5.2.2堆的插入 5.3堆的删除 5.3.1向下调整算法 5.3.2堆的删除 5.4获取堆顶元素 5.5获取堆的元素个数 5.6判断堆是否为空 5.7打印堆 5.8建堆 …