当前位置: 首页 > article >正文

深入自制Shell:解锁Linux进程控制的实践密码

亲爱的读者朋友们😃,此文开启知识盛宴与思想碰撞🎉。

快来参与讨论💬,点赞👍、收藏⭐、分享📤,共创活力社区。

在这里插入图片描述
在上一篇文章《Linux进程控制:创建、终止、等待与程序替换全解析》中,我们系统地学习了Linux进程控制相关知识,从进程的创建、终止,到进程等待与程序替换,每一个环节都为我们深入理解Linux系统的运行机制提供了关键线索。而现在,我们将沿着这条知识脉络,深入到自主Shell命令行解释器的实现领域,进一步挖掘进程控制在实际应用中的强大力量😎。

一、Shell命令行解释器:Linux系统交互的核心枢纽

Shell作为用户与Linux内核交互的接口,承担着解析用户输入命令并执行相应操作的重任。它就像是一个智能管家,接收用户的指令,调度系统资源,然后反馈执行结果🤖。对于开发者和系统管理员来说,理解Shell的工作原理和实现机制,是深入掌握Linux系统的必备技能。自制Shell命令行解释器,不仅仅是一个技术实践项目,更是一扇通往理解操作系统底层运行逻辑的窗口。通过亲手实现一个简单的Shell,我们能够将之前学习的进程控制知识融会贯通,从理论走向实践,切实感受这些知识在实际场景中的应用👏。

二、自制Shell的实现原理:进程控制的巧妙编排

(一)核心执行流程:进程的有序协作

自制Shell的核心执行流程可以概括为一个循环,这个循环不断地获取用户输入、解析命令、创建子进程执行命令,并等待子进程完成任务。这一系列操作看似简单,却蕴含着进程控制的精妙之处✨。

  1. 获取命令行输入:使用fgets函数从标准输入读取用户输入的命令行字符串。在这个过程中,需要注意处理字符串末尾的换行符,确保获取的命令行内容准确无误。例如:
// 从标准输入读取命令行字符串
bool GetCommandLine(char command_buffer[], int size) {
    // 使用fgets函数读取输入
    char *result = fgets(command_buffer, size, stdin);
    if (!result) {
        return false;
    }
    // 去除字符串末尾的换行符
    command_buffer[strlen(command_buffer) - 1] = 0;
    if (strlen(command_buffer) == 0) return false;
    return true;
}
  1. 解析命令行:利用strtok函数,按照空格等分隔符将命令行字符串分割成命令名和参数,并将它们存储在全局变量数组中。这一步是理解用户意图的关键,通过准确解析命令,才能为后续的执行操作做好准备🧐。
// 解析命令行字符串,分割出命令名和参数
void ParseCommandLine(char command_buffer[], int len) {
    // 初始化全局参数数组
    memset(gargv, 0, sizeof(gargv));
    gargc = 0;
    const char *sep = " ";
    // 分割出第一个参数
    gargv[gargc++] = strtok(command_buffer, sep);
    // 继续分割后续参数
    while ((bool)(gargv[gargc++] = strtok(nullptr, sep)));
    gargc--;
}
  1. 创建子进程:借助fork函数创建子进程。子进程将负责执行具体的命令,而父进程则等待子进程的执行结果。这一操作基于进程创建的原理,实现了任务的并行处理,提高了系统的执行效率🚀。
// 创建子进程
pid_t id = fork();
if (id < 0) return false;
  1. 替换子进程:在子进程中,使用execvpeexec函数族进行程序替换。这些函数会根据命令名在系统路径中查找相应的可执行文件,并将其加载到子进程的地址空间中运行,从而实现命令的执行💪。
if (id == 0) {
    // 执行程序替换
    execvpe(gargv[0], gargv, genv);
    // 如果替换失败,退出子进程
    exit(1);
}
  1. 父进程等待:父进程通过waitpid函数等待子进程退出,获取子进程的退出状态。这不仅可以避免僵尸进程的产生,防止内存泄漏,还能让父进程根据子进程的执行结果进行后续的处理✅。
int status = 0;
// 等待子进程退出
pid_t rid = waitpid(id, &status, 0);
if (rid > 0) {
    if (WIFEXITED(status)) {
        // 获取子进程的退出码
        lastcode = WEXITSTATUS(status);
    } else {
        lastcode = 100;
    }
}

(二)内建命令处理:Shell的智能“快捷指令”

除了处理外部命令,Shell还需要处理一些内建命令,如cdexportenvecho等。这些内建命令由Shell自身直接执行,无需启动新的进程,提高了命令执行的效率和响应速度🤗。

  1. cd命令:目录切换的便捷操作:处理cd命令时,通过chdir函数实现目录切换。根据参数的不同情况,设置合适的退出码,以反馈命令执行的结果。
if (strcmp(gargv[0], "cd") == 0) {
    if (gargc == 2) {
        // 切换工作目录
        chdir(gargv[1]);
        lastcode = 0;
    } else {
        lastcode = 1;
    }
    return true;
}
  1. export命令:环境变量的动态设置:用于设置环境变量。将新的环境变量添加到全局环境变量数组genv中,并根据操作结果设置相应的退出码。
else if (strcmp(gargv[0], "export") == 0) {
    if (gargc == 2) {
        // 添加环境变量
        AddEnv(gargv[1]);
        lastcode = 0;
    } else {
        lastcode = 2;
    }
    return true;
}
  1. env命令:环境变量的展示窗口:负责显示当前环境变量。通过遍历全局环境变量数组genv,将每个环境变量打印出来,并设置退出码为0,表示命令执行成功。
else if (strcmp(gargv[0], "env") == 0) {
    for (int i = 0; genv[i]; i++) {
        // 打印环境变量
        printf("%s\n", genv[i]);
    }
    lastcode = 0;
    return true;
}
  1. echo命令:信息输出的简单工具:根据参数情况,判断是输出普通字符串还是特定的变量值(如$?表示上一个命令的退出码),并设置相应的退出码。
else if (strcmp(gargv[0], "echo") == 0) {
    if (gargc == 2) {
        if (gargv[1][0] == '$') {
            if (gargv[1][1] == '?') {
                // 输出上一个命令的退出码
                printf("%d\n", lastcode);
                lastcode = 0;
            }
        } else {
            // 输出普通字符串
            printf("%s", gargv[1]);
            lastcode = 0;
        }
    } else {
        lastcode = 3;
    }
    return true;
}

三、Shell命令行解释器的代码实现与优化探索

(一)完整代码呈现:功能的有机整合

下面是一个简化但完整的Shell命令行解释器代码示例,它整合了上述的核心执行流程和内建命令处理逻辑。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>

using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;

// 全局的命令行参数表
char *gargv[argvnum];
int gargc = 0;
// 全局的变量,存储上一个命令的退出码
int lastcode = 0;
// 我的系统的环境变量
char *genv[envnum];
// 全局的当前Shell工作路径
char pwd[basesize];
char pwdenv[basesize];

// 去除字符串开头的空格
#define TrimSpace(pos) do { \
    while (isspace(*pos)) { \
        pos++; \
    } \
} while (0)

// 获取当前用户名
string GetUserName() {
    string name = getenv("USER");
    return name.empty()? "None" : name;
}

// 获取主机名
string GetHostName() {
    string hostname = getenv("HOSTNAME");
    return hostname.empty()? "None" : hostname;
}

// 获取当前工作目录
string GetPwd() {
    if (nullptr == getcwd(pwd, sizeof(pwd))) return "None";
    snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd);
    putenv(pwdenv);
    return pwd;
}

// 获取当前工作目录的最后一级目录名
string LastDir() {
    string curr = GetPwd();
    if (curr == "/" || curr == "None") return curr;
    size_t pos = curr.rfind("/");
    if (pos == std::string::npos) return curr;
    return curr.substr(pos + 1);
}

// 生成命令行提示符
string MakeCommandLine() {
    char command_line[basesize];
    snprintf(command_line, basesize, "[%s@%s %s]# ", GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
    return command_line;
}

// 打印命令行提示符
void PrintCommandLine() {
    printf("%s", MakeCommandLine().c_str());
    fflush(stdout);
}

// 从标准输入读取命令行字符串
bool GetCommandLine(char command_buffer[], int size) {
    char *result = fgets(command_buffer, size, stdin);
    if (!result) {
        return false;
    }
    command_buffer[strlen(command_buffer) - 1] = 0;
    if (strlen(command_buffer) == 0) return false;
    return true;
}

// 解析命令行字符串,分割出命令名和参数
void ParseCommandLine(char command_buffer[], int len) {
    memset(gargv, 0, sizeof(gargv));
    gargc = 0;
    const char *sep = " ";
    gargv[gargc++] = strtok(command_buffer, sep);
    while ((bool)(gargv[gargc++] = strtok(nullptr, sep)));
    gargc--;
}

// 执行命令
bool ExecuteCommand() {
    // 创建子进程
    pid_t id = fork();
    if (id < 0) return false;
    if (id == 0) {
        // 执行程序替换
        execvpe(gargv[0], gargv, genv);
        // 如果替换失败,退出子进程
        exit(1);
    }
    int status = 0;
    // 等待子进程退出
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0) {
        if (WIFEXITED(status)) {
            // 获取子进程的退出码
            lastcode = WEXITSTATUS(status);
        } else {
            lastcode = 100;
        }
    }
    return true;
}

// 添加环境变量
void AddEnv(const char *item) {
    int index = 0;
    while (genv[index]) {
        index++;
    }
    genv[index] = (char*)malloc(strlen(item) + 1);
    strncpy(genv[index], item, strlen(item) + 1);
    genv[++index] = nullptr;
}

// 检查并执行内建命令
bool CheckAndExecBuiltCommand() {
    if (strcmp(gargv[0], "cd") == 0) {
        if (gargc == 2) {
            // 切换工作目录
            chdir(gargv[1]);
            lastcode = 0;
        } else {
            lastcode = 1;
        }
        return true;
    } else if (strcmp(gargv[0], "export") == 0) {
        if (gargc == 2) {
            // 添加环境变量
            AddEnv(gargv[1]);
            lastcode = 0;
        } else {
            lastcode = 2;
        }
        return true;
    } else if (strcmp(gargv[0], "env") == 0) {
        for (int i = 0; genv[i]; i++) {
            // 打印环境变量
            printf("%s\n", genv[i]);
        }
        lastcode = 0;
        return true;
    } else if (strcmp(gargv[0], "echo") == 0) {
        if (gargc == 2) {
            if (gargv[1][0] == '$') {
                if (gargv[1][1] == '?') {
                    // 输出上一个命令的退出码
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
            } else {
                // 输出普通字符串
                printf("%s", gargv[1]);
                lastcode = 0;
            }
        } else {
            lastcode = 3;
        }
        return true;
    }
    return false;
}

// 初始化环境变量
void InitEnv() {
    extern char **environ;
    int index = 0;
    while (environ[index]) {
        genv[index] = (char*)malloc(strlen(environ[index]) + 1);
        strncpy(genv[index], environ[index], strlen(environ[index]) + 1);
        index++;
    }
    genv[index] = nullptr;
}

int main() {
    // 初始化环境变量
    InitEnv();
    char command_buffer[basesize];
    while (true) {
        // 打印命令行提示符
        PrintCommandLine();
        if (!GetCommandLine(command_buffer, basesize)) {
            continue;
        }
        // 解析命令行
        ParseCommandLine(command_buffer, strlen(command_buffer));
        if (CheckAndExecBuiltCommand()) {
            continue;
        }
        // 执行命令
        ExecuteCommand();
    }
    return 0;
}

(二)优化思路拓展:迈向更强大的Shell

  1. 错误处理的强化升级:目前的代码虽然对部分系统调用进行了错误处理,但还可以进一步完善。对于forkexecvpewaitpid等函数,在出错时打印更详细的错误信息,便于开发者快速定位和解决问题。可以结合errno变量,使用perror函数输出具体的错误原因🧐。

  2. 命令行编辑功能的添加:为了提升用户体验,可以添加命令行编辑功能,如支持箭头键进行命令行历史导航、删除字符等操作。这可以通过引入readline库来实现,使Shell的交互更加便捷和高效👍。

  3. 管道和重定向功能的实现:实现对命令管道(|)和输入输出重定向(<>>>)的支持,将极大地增强Shell的功能。这需要深入理解进程间通信和文件描述符的操作,通过创建管道、复制文件描述符等技术来实现🚀。

四、总结与展望:探索Linux系统的无限可能

通过实现这个简单的Shell命令行解释器,我们将Linux进程控制的知识应用到了实际项目中,深入理解了Shell的工作原理和命令执行机制。在这个过程中,我们不仅掌握了如何创建、控制和管理进程,还学会了处理内建命令、解析命令行以及处理命令执行结果👏。

展望未来,我们可以继续对这个Shell进行扩展和优化,添加更多实用的功能,使其逐渐向专业的Shell工具靠拢。同时,这一实践也为我们进一步学习Linux系统编程、操作系统原理等知识打开了新的大门,激励我们在操作系统的广阔领域中不断探索和创新,挖掘更多的技术奥秘🌟。
在这里插入图片描述


http://www.kler.cn/a/593634.html

相关文章:

  • C#通过API接口返回流式响应内容---SignalR方式
  • Powershell WSL导出导入ubuntu22.04.5子系统
  • 【数据库系统原理】Ch7 数据库应用设计与开发实例
  • NLP探索
  • 目标检测中归一化的目的?
  • 用数组模拟循环队列
  • IOS接入微信方法
  • atoi 函数
  • IP查询底层逻辑解析:数据包与地理位置
  • stm32 2.0.3.0
  • 知识库项目开场白
  • 封装Socket编程接口
  • 蓝桥杯--冲刺题单--随时更新
  • 物联网平台与边缘计算网关的深度结合应用
  • Spring Boot 集成 Kafka 消息发送方案
  • ‌C# I/O 核心用法
  • 【工程实践/大批量文件处理】文件排序
  • 笛卡尔轨迹规划之齐次变换矩阵与欧拉角、四元数的转化
  • 数据类设计_图片类设计之7_矩阵图形类设计更新_实战之页面简单设计(前端架构)
  • VLLM专题(十九)—兼容 OpenAI 的服务器