【Linux】模拟实现简易shell命令行:基于进程创建、进程等待、进程替换等知识的练习

程序功能:模拟实现一个自己的shell执行命令行。

涉及知识:字符串输入及操作函数、子进程创建、进程等待、进程替换、环境变量及获取、添加环境变量的函数

额外需要了解的功能函数:chdir(char* path)函数——改变当前工作路径 

名词解释:内建命令(Built-in Commands)是指在命令行解释器(如 Bash、Zsh 等)中直接实现的命令。这些命令不需要调用外部程序或二进制文件,而是由解释器本身提供和处理。

注意事项:

易错点 :int putenv(char *str);

putenv函数参数的指针str指向的数组,在getenv使用通过该函数导入的环境变量时,必须保证在调用getenv函数的区域中,str指针指向的数组依然有效才可以。

指针有效性问题:putenv 函数将传入的字符串指针直接接管为环境变量的一部分,而不会复制传入的字符串。因此,一旦 putenv 被调用后,传入的指针必须保持有效,直到程序退出或者重新用新值调用 putenv。

// 程序功能:模拟实现一个自己的shell执行命令行。
// 涉及知识:字符串输入及操作函数、子进程创建、进程等待、进程替换、环境变量及获取、添加环境变量的函数
// 额外需要了解的功能函数:chdir(char* path)函数——改变当前工作路径

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

#define SIZE 512 // 定义缓冲区大小默认512字节
#define NUM 32
#define CUT_CHAR " "
char cwd[SIZE*2] = {'\0'};
// 程序设计思路:
// 对象设计:shell
// 成员变量:1、string——获取命令行参数  2、string——储存命令行信息
class shell
{
public:
    // 默认构造函数


    // 1、获取命令行信息,包括:使用者USER,主机名HOSTNAME,当前工作路径PWD
    void get_command_line()
    {
        // 确保环境变量都存在于默认环境变量表中,如果不存在,getenv函数会返回为NULL,这个时候需要提前使用指针接收并判断是否为NULL;
        // 由于Ubuntu系统中不存在HOSTNAME默认环境变量,我们先对其使用putenv函数导入自定义的HOSTNAME环境变量
        char tmp_hostname[SIZE];
        snprintf(tmp_hostname, SIZE, "HOSTNAME=hecs-135712");
        if (putenv(tmp_hostname) != 0)
        {
            perror("export env fail!!!");
            exit(errno);
        }

        user = getenv("USER");
        hostname = getenv("HOSTNAME");
        pwd = getenv("PWD");
        if (user == nullptr)
        {
            std::cerr << "Error: user variables not set." << std::endl;
            exit(errno);
        }
        if (hostname == nullptr)
        {
            std::cerr << "Error: hostname variables not set." << std::endl;
            exit(errno);
        }
        if (pwd == nullptr)
        {
            std::cerr << "Error: pwd variables not set." << std::endl;
            exit(errno);
        }
        char *tmp = getenv("HOME");
        if (strncmp(pwd, tmp, strlen(tmp)) == 0)
        {
            pwd[0] = '~';
            strcpy(pwd + 1, pwd + strlen(tmp));
        }
    }

    void print_command_line()
    {
        printf("%s@%s:%s> ", user, hostname, pwd);
        fflush(stdout);
    }
    void get_command_option()
    {
        char buffer[SIZE] = {'\0'};
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strlen(buffer) - 1] = '\0';
        argv[0] = strtok(buffer, CUT_CHAR);
        int index = 1;
        while (argv[index++] = strtok(NULL, CUT_CHAR));
    }
    void execute_command()
    {
        // 创建子进程,使用进程替换执行对应的命令
        int id = fork();
        if (id == 0)
        {
            int ret = execvp(argv[0], argv);
            if (ret == -1)
            {
                perror("process replace fail!!!");
                exit(errno);
            }
        }
        else if (id > 0)
        {
            int status = 0;
            pid_t ret = waitpid(id, &status, 0);
            if (WIFEXITED(status))
            {
                int lastnode = WEXITSTATUS(status);
                if (lastnode != 0)
                {
                    printf("%s:%s\n", argv[0], strerror(errno));
                }
            }
        }
        else
        {
            perror("build child process fail!!!");
            exit(errno);
        }
    }

    void test(){
        printf("test:");
        puts(getenv("PWD"));
    }
    void cd()
    {
        const char *path = argv[1];
        if (argv[1] == NULL)
        {
            path = getenv("HOME");
        }
        int ret = chdir(path);
        if (ret == -1)
        {
            perror("change directory fail!!!");
            exit(errno);
        }
        char str[SIZE] = {'\0'};
        getcwd(str, sizeof(str));
        //char cwd[SIZE*2] = {'\0'}; 定义为全局变量
        //原因:你在执行cd命令后更新了PWD环境变量,但是在下一个循环迭代中,getenv("PWD")返回空指针。
        //这是因为putenv函数将传入的字符串指针接管为环境变量的一部分,并不会复制传入的字符串,而是直接使用传入的指针。
        //这意味着,一旦你调用putenv(cwd)之后,cwd指向的内存必须保持有效,直到程序结束或者通过putenv重新设置新的值。
        snprintf(cwd, sizeof(cwd), "PWD=%s", str);
        puts(cwd);
        putenv(cwd);
        
    }

    bool is_bash_command()
    {
        bool yes = false;
        if (strcmp(argv[0], "cd") == 0)
        {
            cd();
            yes = true;
        }
        return yes;
    }

private:
    char *user = NULL;
    char *hostname = NULL;
    char *pwd = NULL;
    char *argv[NUM] = {nullptr}; // 存储命令及选项
};
int main()
{
    shell tmp;
    while (1)
    {
        tmp.test();
        //获取命令行
        tmp.get_command_line();
        // 打印命令行
        tmp.print_command_line();

        // 获取命令及选项
        tmp.get_command_option();

        // 判断是否为内建命令
        if (!tmp.is_bash_command())
            // 执行命令q
            tmp.execute_command();
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这题怎么做?!?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值