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

news2024/9/28 19:27:30

目录

简介 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.c
	gcc	-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]);  // 将参数复制到 env
            putenv(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.coloradmin.cn/o/2066844.html

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

相关文章

服务器数据总是被恶意删除,日常该如何做好安全防范?

随着互联网技术的飞速发展&#xff0c;服务器数据安全成为企业运营中不可忽视的重要环节。服务器数据频繁遭遇恶意删除&#xff0c;不仅影响业务连续性&#xff0c;还可能带来重大的经济损失和声誉损害。因此&#xff0c;采取有效措施加强服务器数据安全防范至关重要。以下是一…

从0到1构建视频汇聚生态:EasyCVR视频汇聚平台流媒体协议支持的前瞻性布局

TSINGSEE青犀EasyCVR视频汇聚平台是一款基于云-边-端一体化架构的视频融合AI智能分析平台&#xff0c;广泛应用于工地、仓储、工厂、社区、校园、楼宇等多个领域。平台凭借其强大的数据接入、处理、转码及分发能力&#xff0c;在视频监控领域展现出显著的技术优势和应用前景。本…

共享文件夹

右键要共享的文件夹&#xff0c;Sharing和Security都要设置&#xff0c;都要设置成Everyone可以读写 Full Control

活跃窃密木马TriStealer加密通信分析

1 概述 观成安全研究团队近期在现网监测到多起TriStealer窃密木马攻击事件&#xff0c;TriStealer窃密木马从2024年4月开始活跃&#xff0c;通过Bunny CDN进行载荷下发。TriStealer会收集系统信息、屏幕截图、浏览器中存储的账号密码以及设备中所有的“txt”后缀文件、桌面文件…

Docker容器镜像及其打包

容器镜像分类 1. 系统类镜像 2. 应⽤镜像 搜索镜像 # 默认docker.hub docker search centos 下载镜像 docker pull centos 默认下载最新版本 1. 打包 [rootdocker001 ~]# systemctl start docker.service [rootdocker001 ~]# docker save -o centos.tar centos:latest [root…

重塑SaaS分销行业的合作伙伴关系:深化协同,共创未来

分销行业正步入一个全新的发展阶段&#xff0c;其中合作伙伴关系的深度重塑成为推动行业变革的重要力量。这一转型不仅仅是形式上的调整&#xff0c;更是理念与战略上的深刻变革&#xff0c;旨在构建更加稳固、高效且富有创造力的合作生态。 双向赋能&#xff0c;共筑长期共赢基…

Towards Noiseless Object Contours for Weakly Supervised SemanticSegmentation

摘要 由于图像标签容易获取&#xff0c;基于图像级标签的弱监督语义分割备受关注。现有的方法通常是从类激活图(CAM)生成伪标签&#xff0c;然后训练分割模型。CAM通常突出显示部分对象并产生不完整的伪标签。一些方法通过在CAM种子标签监督下训练轮廓模型来探索目标轮廓&…

阿里IOT云平台搭建

阿里物联网云平台&#xff1a;阿里云登录 - 欢迎登录阿里云&#xff0c;安全稳定的云计算服务平台 打开网址进行登录&#xff0c;如果之前没有开通相关服务的点击立即开通按钮 点击立即开通&#xff1a; 以下界面表示开通成功&#xff0c;登录阿里云平台需要实名认证若没有实名…

找搭子软件哪个最好?找搭子APP排行榜前十名分享!

你是否常常感到孤单乏味&#xff0c;想要找到志同道合的搭子一起玩耍却无从下手&#xff1f;别担心&#xff0c;找搭子软件排行榜前十名来啦&#xff0c;总有一款适合你&#xff01; 咕哇小程序&#xff1a;这是一个实名制的找搭子交友平台&#xff0c;没错是实名制的&#xff…

Vue: v-html安全性问题

一、问题描述 可能都知道使用v-html插入富文本&#xff0c;存在安全隐患&#xff0c;比如 cross-site scripting attack&#xff08;xss&#xff09;。但具体什么情况下v-html会引发安全问题呢&#xff1f;是否内容中含有<scrpit>标签就会触发执行脚本呢&#xff1f; 二…

字符串哈希 +二分

前言&#xff1a;之前纠结了很久的字符串哈希原来是有结论的 注意我们的数组一定要开long long 我们来看一下题目 这个题目我们可以用二分 哈希来做&#xff0c;我们要先验证一下二分的可行性&#xff0c;我们如果找到一个不符合的1 - i &#xff0c;那么不可能存在一个j ( j…

超声波清洗机哪个牌子的好?四大热销超声波清洗机款式硬核详评

夏天出汗多&#xff0c;眼镜、首饰、手表等随身佩戴的小物件常常被汗渍“侵蚀”。佩戴一段时间后&#xff0c;这些饰品容易积聚污垢&#xff0c;而且难以彻底清洁。不过&#xff0c;只需花费一两百元购买一台超声波清洗机&#xff0c;就能轻松解决这些问题。不仅能清洗眼镜&…

控标可编程带POE交换机中控系统的优势及应用领域

控标可编程带POE交换机中控系统是一种集成了控制、编程、数据交换及电力传输功能的综合性系统。以下是对其功能的详细描述及应用领域的探讨&#xff1a; 功能描述 可编程控制&#xff1a; 该系统具备强大的可编程能力&#xff0c;允许用户根据实际需求编写控制程序&#xff0…

杰里AC695X之自定义提示音

1.配置工具 在SDK\cpu\br23\tools\AC695X_config_tool打开配置工具 2.添加提示音 这里时初始的提示音文件和音频路径 将要添加的提示音文件添加到SDK\cpu\br25\tools\soundbox\standard\提示音中&#xff0c; 点击添加提示音&#xff0c;选择路径&#xff0c;取提示音名字 最…

(来点有趣的)IDEA设置背景图片

文章目录 1、点击工具栏上的“File”选项&#xff0c;然后选择“Settings”2、在“Settings”窗口中&#xff0c;选择“Appearance & Behavior > Appearance”。在“Appearance”下点击“Background Image”弹出设置背景3、设置好以后点击“ok”即可&#xff0c;以下是效…

App推广利器:Xinstall无广告码安装全解析

在移动互联网时代&#xff0c;App推广已成为企业营销的重要一环。然而&#xff0c;传统的App推广方式往往依赖于繁琐的广告码&#xff0c;不仅降低了用户体验&#xff0c;还增加了推广难度。如今&#xff0c;随着Xinstall的强势崛起&#xff0c;这一难题终于得到了有效解决。 …

生产分析报表做到什么效果才叫好?看这一篇就知道

什么是生产分析报表&#xff1f; 传统的生产分析报表&#xff0c;主要是以文档的形式来记录和分析生产活动&#xff0c;例如&#xff1a; 图源网络 这样的报表虽然可以涵盖生产的各个方面&#xff0c;但主要依赖于手工录入数据&#xff0c;因此数据整合难度较大&#xff0c;易…

趋动科技 OrionX on VMware 打造 AI 就绪平台

着科技进步和产业变革的加速演进&#xff0c;人工智能&#xff08;AI&#xff09;已经成为兵家必争之地。今年以来伴随着ChatGPT带来的鲶鱼效应&#xff0c;人工智能成为科技产业创新的焦点&#xff0c;其应用范围越来越广泛&#xff0c;并将持续发展。科技产业龙头正加大在人工…

前端宝典十四:Node缓存、安全与鉴权

本文主要从Node缓存、安全与鉴权几个方面展开解析&#xff0c;包含几个方面&#xff1a; Cookie 定义、设置、生命周期以及安全性Node缓存分类和区别Node鉴权包含session、cookie、token、jwt等 一、Cookie HTTP Cookie&#xff08;通常也叫 Web Cookie 或浏览器 Cookie&…

node.js express创建本地服务以及使用pm2启动服务

在node.js环境下安装插件&#xff1a; npm i body-parser npm i express 同目录下创建app.js // 引入express中间件 const express require(express); const bodyParser require(body-parser); // 创建web服务器 const app express(); // 使用body-parser中间件解析JSON类型…