操作系统实验四 (综合实验)设计简单的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;
}

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

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

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

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

相关文章

Excel透视表:快速计算数据分析指标的利器

文章目录 概述1.数据透视表基本操作1.1准备数据&#xff1a;1.2创建透视表&#xff1a;1.3设置透视表字段&#xff1a;1.4多级分类汇总和交叉汇总的差别1.5计算汇总数据&#xff1a;1.6透视表美化&#xff1a;1.7筛选和排序&#xff1a;1.8更新透视表&#xff1a; 2.数据透视-数…

【B站 heima】小兔鲜Vue3 项目学习笔记Day02

文章目录 Pinia1.使用2. pinia-计数器案例3. getters实现4. 异步action5. storeToRefsx 数据解构保持响应式6. pinia 调试 项目起步1.项目初始化和git管理2. 使用ElementPlus3. ElementPlus 主题色定制4. axios 基础配置5. 路由设计6. 静态资源初始化和 Error lens安装7.scss自…

Github 2024-05-24 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-05-24统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目3非开发语言项目2TypeScript项目2JavaScript项目1Kotlin项目1C#项目1C++项目1Shell项目1Microsoft PowerToys: 最大化Windows系统生产…

软件设计师备考笔记(十):网络与信息安全基础知识

文章目录 一、网络概述二、网络互连硬件&#xff08;一&#xff09;网络的设备&#xff08;二&#xff09;网络的传输介质&#xff08;三&#xff09;组建网络 三、网络协议与标准&#xff08;一&#xff09;网络的标准与协议&#xff08;二&#xff09;TCP/IP协议簇 四、Inter…

某神,云手机启动?

某神自从上线之后&#xff0c;热度不减&#xff0c;以其丰富的内容和独特的魅力吸引着众多玩家&#xff1b; 但是随着剧情无法跳过&#xff0c;长草期过长等原因&#xff0c;近年脱坑的玩家多之又多&#xff0c;之前米家推出了一款云某神的app&#xff0c;目标是为了减少用户手…

RedisTemplateAPI:String

文章目录 ⛄1 String 介绍⛄2 命令⛄3 对应 RedisTemplate API❄️❄️ 3.1 添加缓存❄️❄️ 3.2 设置过期时间(单独设置)❄️❄️ 3.3 获取缓存值❄️❄️ 3.4 删除key❄️❄️ 3.5 顺序递增❄️❄️ 3.6 顺序递减 ⛄4 以下是一些常用的API⛄5 应用场景 ⛄1 String 介绍 Str…

ue引擎游戏开发笔记(47)——设置状态机解决跳跃问题

1.问题分析&#xff1a; 目前当角色起跳时&#xff0c;只是简单的上下移动&#xff0c;空中仍然保持行走动作&#xff0c;并没有设置跳跃动作&#xff0c;因此&#xff0c;给角色设置新的跳跃动作&#xff0c;并优化新的动作动画。 2.操作实现&#xff1a; 1.实现跳跃不复杂&…

Java中的继承和多态

继承 在现实世界中&#xff0c;狗和猫都是动物&#xff0c;这是因为他们都有动物的一些共有的特征。 在Java中&#xff0c;可以通过继承的方式来让对象拥有相同的属性&#xff0c;并且可以简化很多代码 例如&#xff1a;动物都有的特征&#xff0c;有名字&#xff0c;有年龄…

Mybatis源码剖析---第一讲

Mybatis源码剖析 基础环境搭建 JDK8 Maven3.6.3&#xff08;别的版本也可以…&#xff09; MySQL 8.0.28 --> MySQL 8 Mybatis 3.4.6 准备jar&#xff0c;准备数据库数据 把依赖导入pom.xml中 <properties><project.build.sourceEncoding>UTF-8</p…

Linux学习笔记:线程

Linux中的线程 什么是线程线程的使用原生线程库创建线程线程的id线程退出等待线程join分离线程取消一个线程线程的局部存储在c程序中使用线程使用c自己封装一个简易的线程库 线程互斥(多线程)导致共享数据出错的原因互斥锁关键函数pthread_mutex_t :创建一个锁pthread_mutex_in…

雷电预警监控系统:守护安全的重要防线

TH-LD1在自然界中&#xff0c;雷电是一种常见而强大的自然现象。它既有震撼人心的壮观景象&#xff0c;又潜藏着巨大的安全风险。为了有效应对雷电带来的威胁&#xff0c;雷电预警监控系统应运而生&#xff0c;成为现代社会中不可或缺的安全防护工具。 雷电预警监控系统的基本…

makefile 编写规则

1.概念 1.1 什么是makefile Makefile 是一种文本文件&#xff0c;用于描述软件项目的构建规则和依赖关系&#xff0c;通常用于自动化软件构建过程。它包含了一系列规则和指令&#xff0c;告诉构建系统如何编译和链接源代码文件以生成最终的可执行文件、库文件或者其他目标文件…

Node.js知识点以及案例总结

思考&#xff1a;为什么JavaScript可以在浏览器中被执行 每个浏览器都有JS解析引擎&#xff0c;不同的浏览器使用不同的JavaScript解析引擎&#xff0c;待执行的js代码会在js解析引擎下执行 为什么JavaScript可以操作DOM和BOM 每个浏览器都内置了DOM、BOM这样的API函数&#xf…

开源模型应用落地-食用指南-以最小成本博最大收获

一、背景 时间飞逝&#xff0c;我首次撰写的“开源大语言模型-实际应用落地”专栏已经完成了一半以上的内容。由衷感谢各位朋友的支持,希望这些内容能给正在学习的朋友们带来一些帮助。 在这里&#xff0c;我想分享一下创作这个专栏的初心以及如何有效的&#xff0c;循序渐进的…

STM32F103C8T6 HC-SR04超声波模块——超声波障碍物测距(HAl库)

超声波障碍物测距 一、HC-SR04超声波模块&#xff08;一&#xff09;什么是HC-SR04&#xff1f;&#xff08;二&#xff09;HC-SR04工作原理&#xff08;三&#xff09;如何使用HC-SR04&#xff08;四&#xff09;注意事项 二、程序编写&#xff08;一&#xff09;CubeMX配置1.…

2024全新Langchain大模型AI应用与多智能体实战开发

2024全新Langchain大模型AI应用与多智能体实战开发 LangChain 就是一个 LLM 编程框架&#xff0c;你想开发一个基于 LLM 应用&#xff0c;需要什么组件它都有&#xff0c;直接使用就行&#xff1b;甚至针对常规的应用流程&#xff0c;它利用链(LangChain中Chain的由来)这个概念…

Facebook之魅:数字社交的体验

在当今数字化时代&#xff0c;Facebook作为全球最大的社交平台之一&#xff0c;承载着数十亿用户的社交需求和期待。它不仅仅是一个简单的网站或应用程序&#xff0c;更是一个将世界各地的人们连接在一起的社交网络&#xff0c;为用户提供了丰富多彩、无与伦比的数字社交体验。…

C++实现基础二叉搜索树(并不是AVL和红黑树)

本次实现的二叉搜索树并不是AVL数和红黑树&#xff0c;只是了解流程和细节。 目录 二叉搜索树的概念K模型二叉搜索树的实现二叉搜索树的架构insert插入find 查找中序遍历Inorder删除earse替换法的思路情况一 &#xff1a;假如要删除节点左边是空的。在左边时在右边时 情况二&a…

文心智能体,零代码构建情感表达大师智能体

前言 随着智能体技术的突飞猛进&#xff0c;各行各业正迎来前所未有的变革与机遇。智能体&#xff0c;作为人工智能领域的重要分支&#xff0c;以其自主性、智能性和适应性&#xff0c;正逐步渗透到我们生活的每一个角落&#xff0c;成为推动社会进步和科技发展的新动力。 为了…

visual studio 2022 ssh 主机密钥算法失败问题解决

 Solution - aengusjiang 问题&#xff1a; I follow the document, then check sshd_config, uncomment“HostKey /etc/ssh/ssh_host_ecdsa_key” maybe need add the key algorithms: #HostKeyAlgorithms ssh-ed25519[Redacted][Redacted]rsa-sha2-256,rsa-sha2-512 Ho…