Linux - 模拟实现 shell 命令行解释器

目录

简介 shell 的重要性

解释为什么学习 shell 的工作原理很重要

模拟实现一个简单的 shell

        循环过程

        1. 获取命令行

        2. 解析命令行

        3. 建立一个子进程(fork)

        4. 替换子进程(execvp)

        5. 父进程等待子进程退出(wait)

        6. 处理内建命令

        具体代码


简介 shell 的重要性

        Shell 是操作系统与用户之间的重要接口,它提供了命令行环境,让用户可以直接与系统交互。通过 Shell,用户可以执行各种命令来管理文件、控制进程和进行网络配置等。对于开发者和系统管理员来说,掌握 Shell 的使用不仅能够提高工作效率,还能实现自动化任务,从而更好地维护和管理系统。

        Shell 的重要性还体现在它的可扩展性和灵活性上。通过编写 Shell 脚本,用户可以轻松地将一系列复杂操作自动化,这使得 Shell 成为系统管理和批处理任务的得力工具。此外,Shell 脚本具有跨平台特性,能够在不同的类 Unix 系统上运行,这进一步增加了它的应用广泛性。

解释为什么学习 shell 的工作原理很重要

1. 效率提升

        通过 Shell 脚本编写,用户可以轻松地实现自动化操作,避免手动执行重复性任务。无论是备份文件、批量处理数据,还是自动化部署,Shell 都能通过简洁的脚本快速完成任务,从而显著提高工作效率。

2. 灵活性强

        Shell 提供了丰富的内置命令和脚本功能,允许用户根据实际需求编写复杂的任务。通过条件判断、循环和变量,用户可以根据具体情况动态地控制执行流程,极大地增强了任务处理的灵活性。

3. 跨平台性

        不同的操作系统通常都有自己的 Shell 实现,例如 Unix 和 Linux 的 Bash Shell,macOS 的 Zsh 等。这些 Shell 脚本在不同平台之间有很好的兼容性,使得用户可以在不同的操作系统上复用同一套脚本。

4. 深入了解系统机制

        学习 Shell 工作原理可以帮助用户更好地理解操作系统的内部机制,了解进程、文件系统、网络等系统资源的管理方式。这对于系统调优、故障排查和性能监控等方面的工作有着重要的帮助。

模拟实现一个简单的 shell

        循环过程

        1. 获取命令行

        Interactive 函数负责显示提示符并获取用户的输入。提示符格式为 [username@hostname cwd]$,其中 usernamehostname 是通过环境变量获取的,cwd 表示当前工作目录。fgets 函数用于读取用户的输入,读取到的字符串中包含换行符,因此使用 strlen(out) - 1 来移除换行符。

int Interactive(char out[], int size) 
{printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir()); fgets(out, size, stdin); out[strlen(out) - 1] = 0; return strlen(out); 
}

        该函数中,fgets 用于从标准输入中获取用户的输入,并移除末尾的换行符。 

        2. 解析命令行

        Split 函数用于将用户输入的命令字符串分割成多个参数。它使用 strtok 函数按空格分隔命令,并将每个参数存储到 argv 数组中。同时,如果输入的命令是 ls,则会自动为其添加 --color 参数,以便在输出时显示彩色。

void Split(char in[]) 
{ int i = 0; argv[i++] = strtok(in, SEP); while (argv[i++] = strtok(NULL, SEP)); if (strcmp(argv[0], "ls") == 0) { argv[i - 1] = (char*)"--color"; argv[i] = NULL; } 
}

        在该实现中,如果输入的命令是 ls,Shell 会自动为其添加 --color 参数,便于显示彩色输出。 

        3. 建立一个子进程(fork)

        在 Execute 函数中,通过 fork() 创建一个子进程。fork() 返回两次,一次在父进程中,一次在子进程中。如果 fork() 失败,返回 -1;如果是在子进程中,返回 0;如果是在父进程中,则返回子进程的进程 ID。

void Execute() 
{ pid_t id = fork(); if (id == 0) { execvp(argv[0], argv); exit(1); } int status = 0; pid_t rid = waitpid(id, &status, 0); if (rid == id) lastcode = WEXITSTATUS(status); 
}

        在子进程中,execvp 会替换当前进程的代码,执行指定的命令。如果执行失败,子进程会退出并返回错误码。父进程则会等待子进程完成,并获取其退出状态。 

        4. 替换子进程(execvp)

        在子进程中,使用 execvp 函数执行命令。execvp 函数会将当前进程的代码替换为要执行的命令。如果执行失败,子进程会通过 exit(1) 退出。

        5. 父进程等待子进程退出(wait)

        父进程使用 waitpid 等待子进程执行完毕,并通过 WEXITSTATUS 获取子进程的退出状态码,存储到 lastcode 变量中,以便后续命令可以获取到上一个命令的退出状态。

        6. 处理内建命令

        内建命令包括 cdexportecho。这些命令不需要通过创建子进程来执行,而是在当前进程中直接处理。

  • cd:改变当前工作目录,如果未指定目标目录,则切换到家目录。
  • export:设置环境变量。
  • echo:输出字符串或环境变量的值,支持显示上一个命令的退出状态。

总结

        通过以上步骤,模拟实现了一个简单的 Shell。这个 Shell 可以处理用户输入、解析命令行、创建子进程执行命令,并支持内建命令。通过这样的实现,您不仅能够理解 Shell 的基本工作原理,还可以深入掌握操作系统与用户之间的交互机制,这对于系统管理和自动化任务的实现都有着极大的帮助。

        具体代码

myshell:myshell.cgcc	-o	$@	$^
.PHONY:clean
clean:rm	-f	myshell
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define SIZE 1024       // 用于存储用户输入命令的缓冲区大小
#define MAX_ARGC 64     // 最大参数数量,存储分割后的命令行参数
#define SEP " "         // 参数分隔符,这里使用空格作为分隔符// 全局变量
char *argv[MAX_ARGC];   // 用于存储分割后的命令行参数
char pwd[SIZE];         // 用于存储当前工作目录路径
char env[SIZE];         // 用于存储环境变量
int lastcode = 0;       // 用于存储上一个命令的退出状态码// 获取主机名
const char* HostName()
{char *hostname = getenv("HOSTNAME"); // 从环境变量中获取主机名if(hostname) return hostname;        // 如果环境变量存在,返回主机名else return "None";                  // 如果不存在,返回 "None"
}// 获取用户名
const char* UserName()
{char *username = getenv("USER");     // 从环境变量中获取用户名if(username) return username;        // 如果环境变量存在,返回用户名else return "None";                  // 如果不存在,返回 "None"
}// 获取当前工作目录
const char *CurrentWorkDir()
{char *cwd = getenv("PWD");           // 从环境变量中获取当前工作目录if(cwd) return cwd;                  // 如果环境变量存在,返回当前工作目录else return "None";                  // 如果不存在,返回 "None"
}// 获取家目录
char *Home()
{return getenv("HOME");               // 返回家目录路径
}// 显示提示符并获取用户输入
int Interactive(char out[], int size)
{// 显示命令提示符,格式为 [username@hostname cwd]$printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());// 从标准输入获取用户输入的命令行fgets(out, size, stdin);// 去掉输入字符串末尾的换行符out[strlen(out)-1] = 0;return strlen(out);  // 返回命令行字符串的长度
}// 分割用户输入的命令行字符串
void Split(char in[])
{int i = 0;// 使用空格分割命令行字符串,并将其存储到 argv 数组中argv[i++] = strtok(in, SEP);  // 获取第一个参数// 循环获取剩余的参数while(argv[i++] = strtok(NULL, SEP));// 如果输入的命令是 ls,则自动添加 --color 参数if(strcmp(argv[0], "ls") == 0){argv[i-1] = (char*)"--color";  // 添加 --color 参数argv[i] = NULL;                // 确保最后一个参数为 NULL}
}// 执行外部命令
void Execute()
{pid_t id = fork();  // 创建子进程if(id == 0){// 在子进程中执行命令execvp(argv[0], argv);exit(1);  // 如果执行失败,退出子进程}int status = 0;pid_t rid = waitpid(id, &status, 0);  // 父进程等待子进程结束if(rid == id) lastcode = WEXITSTATUS(status);  // 获取子进程的退出状态码
}// 处理内建命令
int BuildinCmd()
{int ret = 0;// 检查是否为内建命令if(strcmp("cd", argv[0]) == 0)  // 处理 cd 命令{ret = 1;char *target = argv[1];  // 获取目标目录if(!target) target = Home();  // 如果没有指定目录,跳转到家目录chdir(target);  // 改变当前工作目录char temp[1024];getcwd(temp, 1024);  // 获取当前目录路径snprintf(pwd, SIZE, "PWD=%s", temp);  // 更新 PWD 环境变量putenv(pwd);  // 设置环境变量}else if(strcmp("export", argv[0]) == 0)  // 处理 export 命令{ret = 1;if(argv[1]){strcpy(env, argv[1]);  // 将参数复制到 envputenv(env);  // 设置环境变量}}else if(strcmp("echo", argv[0]) == 0)  // 处理 echo 命令{ret = 1;if(argv[1] == NULL) {printf("\n");  // 如果没有参数,输出空行}else{if(argv[1][0] == '$')  // 检查是否为环境变量{if(argv[1][1] == '?')  // 输出上一个命令的退出状态码{printf("%d\n", lastcode);lastcode = 0;}else{char *e = getenv(argv[1]+1);  // 获取环境变量的值if(e) printf("%s\n", e);  // 输出环境变量的值}}else{printf("%s\n", argv[1]);  // 输出字符串}}}return ret;  // 返回是否执行了内建命令
}// 主函数
int main()
{while(1){char commandline[SIZE];  // 存储用户输入的命令行int n = Interactive(commandline, SIZE);  // 获取用户输入if(n == 0) continue;  // 如果输入为空,继续等待输入Split(commandline);  // 分割命令字符串n = BuildinCmd();  // 检查并执行内建命令if(n) continue;  // 如果是内建命令,继续等待输入Execute();  // 执行非内建命令}return 0;
}

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

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

相关文章

合宙LuatOS AIR700 IPV6 TCP 客户端向NodeRed发送数据

为了验证 AIR700 IPV6 &#xff0c;特别新建向NodeRed Tcp发送的工程。 Air700发送TCP数据源码如下&#xff1a; --[[ IPv6客户端演示, 仅EC618系列支持, 例如Air780E/Air600E/Air780UG/Air700E ]]-- LuaTools需要PROJECT和VERSION这两个信息 PROJECT "IPV6_SendDate_N…

Jupyter安装指南:最简便最详细的步骤

一.介绍 JupyterNotebook 是一个款以网页为基础的交互计算环境&#xff0c;可以创建Jupyter的文档&#xff0c;支持多种语言&#xff0c;包括Python, Julia, R等等。一般来说&#xff0c;如果是使用R语言的话&#xff0c;使用Rstudio居多&#xff0c;使用Python的话&#xff0…

开源的数据库增量订阅和消费的中间件——Cancl

目录 工作原理 MySQL主备复制原理 Canal 工作原理 主要功能和特点 应用场景 实验准备 安装JDK11 下载MySQL8.0 配置canal.admin 配置canal-deployer 测试数据读取 新增一台主机用做被同步的目标机器测试 官方地址&#xff1a;https://github.com/alibaba/canal?ta…

【gitlab】gitlab-ce:17.3.0-ce.0 1:使用docker engine安装

ce版本必须配置代理。 极狐版本可以直接pull 社区版GitLab不支持Alibaba Cloud Linux 3,本操作以Ubuntu/Debian系统为例进行说明,其他操作系统安装说明,请参见安装社区版GitLab。 docker 环境重启 sudo systemctl daemon-reload sudo systemctl restart docker脚本安装 安裝…

宝塔面板实现定时任务删除 logs文件 加条件删除 只删除一个月前的日志

我们在开发中难免用到了日志功能&#xff0c;随着日志越来越多导致占用我们的内存 下面是一个简单的 使用宝塔面板里面的定时任务来实现删除日志案例 第一步 首先我的日志文件目录 都在log文件夹里面&#xff0c; 每个月生成一个日志文件夹 文件夹命名是年月来命名的 第二…

探索深度学习的力量:从人工智能到计算机视觉的未来科技革命

目录 1. 引言 2. 人工智能的历史背景 3. 深度学习的崛起 3.1 深度神经网络的基本原理 4. 计算机视觉的发展现状 4.1 传统计算机视觉与深度学习的结合 5. 深度学习在计算机视觉中的应用 5.1 图像分类 5.2 目标检测 6. 深度学习引领的未来科技创新与变革 7. 结论 引言…

【vue3+Typescript】手撸了一个轻量uniapp导航条

最近公共组件写到导航条&#xff0c;本来打算拿已有的改。看了下uniapp市场上已有的组件&#xff0c;一是不支持vue3typescript&#xff0c;二是包装过重。索性自己手撸了一个导航条&#xff0c;不到100行代码全部搞定&#xff0c;因为自己的需求很简单&#xff1a; 1&#xf…

pycharm2023.1破解

下载解压文件&#xff0c;文件夹 /jetbra 复制电脑某个位置 注意&#xff1a; 补丁所属文件夹需单独存放&#xff0c;且放置的路径不要有中文与空格&#xff0c;以免 Pycharm 读取补丁错误。 点击进入 /jetbra 补丁目录&#xff0c;再点击进入 /scripts 文件夹&#xff0c;双…

javaer快速入门 goweb框架 gin

gin 入门 前置条件 安装环境 配置代理 # 配置 GOPROXY 环境变量&#xff0c;以下三选一# 1. 七牛 CDN go env -w GOPROXYhttps://goproxy.cn,direct# 2. 阿里云 go env -w GOPROXYhttps://mirrors.aliyun.com/goproxy/,direct# 3. 官方 go env -w GOPROXYhttps://goproxy.…

鸿蒙内核源码分析——(自旋锁篇)

本篇说清楚自旋锁 读本篇之前建议先读系列篇 进程/线程篇. 内核中哪些地方会用到自旋锁?看图: 概述 自旋锁顾名思义&#xff0c;是一把自动旋转的锁&#xff0c;这很像厕所里的锁&#xff0c;进入前标记是绿色可用的&#xff0c;进入格子间后&#xff0c;手一带&#xff0c…

10分钟学会LVM逻辑卷

华子目录 前言认识LVMLVM基本概念LVM整体流程LVM管理命令pvs&#xff0c;vgs&#xff0c;lvs命令pvs基本用法选项示例 vgs基本用法选项示例 lvs基本用法 pvcreate&#xff0c;vgcreate&#xff0c;lvcreate命令pvcreate示例 vgcreate基本用法示例选项 lvcreate基本用法示例 pvr…

Python爬虫入门教程(非常详细)适合零基础小白

一、什么是爬虫&#xff1f; 1.简单介绍爬虫 爬虫的全称为网络爬虫&#xff0c;简称爬虫&#xff0c;别名有网络机器人&#xff0c;网络蜘蛛等等。 网络爬虫是一种自动获取网页内容的程序&#xff0c;为搜索引擎提供了重要的数据支撑。搜索引擎通过网络爬虫技术&#xff0c;将…

【电路笔记】-无源衰减器总结

无源衰减器总结 文章目录 无源衰减器总结1、概述2、L-型无源衰减器设计3、T-型无源衰减器设计4、桥接 T 型衰减器设计5、π型无源衰减器设计无源衰减器是一个纯电阻网络,可用于控制输出信号的电平。 1、概述 无源衰减器是一种纯电阻网络,用于削弱或“衰减”传输线的信号电平…

Element UI中报dateObject.getTime is not a function解决方法~

1、错误信息。 2、该报错原因是Element UI中日期组件的校验规则是type: "date",而一般我们从后台拿到的数据是字符串型的&#xff0c;不满足预期&#xff0c;就会报错。 3、解决方法。 去掉日子组件中的type: "date"校验规则即可。 rules: {newName: [{…

EasyCVR视频汇聚平台:深度解析GB/T 28181协议下的视频资源整合与应用

随着安防技术的快速发展和智慧城市建设的推进&#xff0c;视频监控系统作为公共安全、城市管理、企业运营等领域的重要基础设施&#xff0c;其重要性和应用范围不断扩大。在这一过程中&#xff0c;GB/T 28181作为国家标准中关于视频监控设备通信协议的规范&#xff0c;正逐渐受…

C2M商业模式分析与运营平台建设解决方案(四)

C2M商业模式以消费者需求驱动生产制造&#xff0c;实现个性化与效率的双赢。本解决方案将围绕构建智能化、数据驱动的运营平台&#xff0c;通过精准把握市场需求、优化生产流程、强化供应链管理&#xff0c;打造高效、敏捷、柔性的C2M运营体系&#xff0c;助力企业快速响应市场…

华为AR1220配置GRE隧道

1.GRE隧道的配置 GRE隧道的配置过程,包括设置接口IP地址、配置GRE隧道接口和参数、配置静态路由以及测试隧道连通性。GRE隧道作为一种标准协议,支持多协议传输,但不提供加密,并且可能导致CPU资源消耗大和调试复杂等问题。本文采用华为AR1220路由器来示例说明。 配置…

【电路笔记】-桥接 T 型衰减器

桥接 T 型衰减器 文章目录 桥接 T 型衰减器1、概述2、桥接 T 型衰减器示例 13、可变桥接 T 型衰减器4、完全可调衰减器5、可切换桥接 T 型衰减器Bridged-T 衰减器是另一种电阻衰减器设计,它是标准对称 T 垫衰减器的变体。 1、概述 顾名思义,桥接 T 形衰减器具有一个额外的电…

Cesium模型制作,解决Cesium加载glb/GLTF显示太黑不在中心等问题

Cesium模型制作&#xff0c;解决Cesium加载glb/GLTF显示太黑不在中心等问题 QQ可以联系这里&#xff0c;谢谢

Spring SSM框架--MVC

SSM框架–Mybatis 一、介绍 Spring 框架是一个资源整合的框架&#xff0c;可以整合一切可以整合的资源&#xff08;Spring 自身和第三方&#xff09;&#xff0c;是一个庞大的生态&#xff0c;包含很多子框架&#xff1a;Spring Framework、Spring Boot、Spring Data、Spring…