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…

【STM32单片机_(HAL库)】3-2-3【中断EXTI】【电动车报警器项目】433M无线收发模块实验

1.硬件 STM32单片机最小系统433M无线收发模块LED灯模块 2.软件 驱动文件添加GPIO常用函数中断配置流程main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "exti.h"int main(void) {HAL_Init(); …

用Python实现生信分析——隐马尔可夫模型(HMM)在生物信息学中的应用详解

在生物信息学中&#xff0c;隐马尔可夫模型&#xff08;HMM&#xff09; 被广泛应用于基因组注释、蛋白质结构预测、基因预测等领域。以下是针对生物信息学应用的详细讲解&#xff0c;包括案例、Python实现、运行结果和分析。 1. HMM在生物信息学中的应用场景 HMM在生物信息学…

开源的数据库增量订阅和消费的中间件——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; 每个月生成一个日志文件夹 文件夹命名是年月来命名的 第二…

使用 C++ 实现一个简单的数据库连接池

使用 C 实现一个简单的数据库连接池 在现代应用程序中&#xff0c;数据库连接的管理是一个重要的性能瓶颈。频繁地创建和销毁数据库连接会导致显著的性能下降。为了解决这个问题&#xff0c;连接池技术应运而生。本文将介绍如何使用 C 实现一个简单的数据库连接池&#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…

Python模块篇(五)

模块 模块与包模块的导入与使用标准库的常用模块第三方库的安装与使用&#xff08;如&#xff1a;pip工具&#xff09; 模块与包 模块是一个包含 Python 代码的文件&#xff0c;通常以 .py 作为扩展名。一个模块可以包含函数、类、变量&#xff0c;以及可执行的代码段。模块的…

pycharm2023.1破解

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

leetcode_55. 跳跃游戏

55. 跳跃游戏 题目描述&#xff1a;给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#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、概述 无源衰减器是一种纯电阻网络,用于削弱或“衰减”传输线的信号电平…

golang中defer的执行时间是什么时候?是在return前还是return后执行的?

在Go语言中&#xff0c;defer语句指定的函数调用会在包含它的函数即将完成时执行&#xff0c;具体来说&#xff1a; 执行时间&#xff1a;defer语句指定的函数在包含它的函数的返回值被确定后执行&#xff0c;但在该函数真正的返回操作之前执行。这意味着defer执行时&#xff0…

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

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