GNU/Linux下实现一个简易shell,支持多重管道

本文详细介绍了如何构建一个简易的Linux shell,包括实现管道、输入输出重定向、后台运行命令等功能。通过解析用户输入的命令,使用fork、execvp等系统调用执行命令,并处理管道连接、文件重定向和后台进程。同时,文章讨论了信号屏蔽、内存管理和错误处理等关键点,以及cd命令的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

intro

  • 争取在自己的简易shell中可以实现以下命令
xxx@xxx ~ $ ./xxx-super-shell
xxx@xxx ~ $ echo ABCDEF
xxx@xxx ~ $ echo ABCDEF > ./1.txt
xxx@xxx ~ $ cat 1.txt
xxx@xxx ~ $ ls -t >> 1.txt
xxx@xxx ~ $ ls -a -l | grep abc | wc -l > 2.txt
xxx@xxx ~ $ python < ./1.py | wc -c
xxx@xxx ~ $ mkdir test_dir
xxx@xxx ~/test_dir $ cd test_dir
xxx@xxx ~ $ cd -
xxx@xxx ~/test_dir $ cd -
xxx@xxx ~ $ ./xxx-super-shell # shell 中嵌套 shell
xxx@xxx ~ $ exit
xxx@xxx ~ $ exit

任务解析

  • 任务目标:

    • 实现 管道 (也就是 |)
    • 实现 输入输出重定向(也就是 < > >>)
    • 实现 后台运行(也就是 & )
    • 实现 cd,要求支持能切换到 绝对路径相对路径支持 cd -
    • 屏蔽一些信号(如 ctrl + c 不能终止)
    • 界面美观
    • 不得出现内存泄漏,内存越界等错误
  • 核心为掌握Linux系统编程进程的相关部分,能够正确调用相应API完成任务,保证每个函数的逻辑正确

  • 难点:

    • 最难的就是实现管道,而且是多重管道ಥʖ̯ಥ,关于实现管道的的基本原理可以参考我的这篇博客——Linux下实现一个简单的单向管道及其理解
    • 代码的去耦合功能分块也是一个难点,但这次做的还行
    • 写完之后再回想,想不起来很多当时觉得难的不行的东西了,其实经历完绝望之谷之后,不仅技术会提升,心态也会更好

框架函数

main()

  • 从main函数入手来剖析我们需要实现的所有功能是一个不错的选择
int main()
{
   
    my_signal();                                //屏蔽信号
    while(1){
                                      //一直循环,直至手动退出
        char place[BUFFSIZE];                   //存放当前路径
        getcwd(place, BUFFSIZE);                //获取当前路径
        printf(BEGIN(36,36)"%s:"CLOSE, place);  //色彩显示
        // readline库的使用
        char *command = readline(BEGIN(33,33)"ypd-super-shell ¥$ "CLOSE);
        if(!command){
   
            my_error("readline",__LINE__);
        }
        add_history(command);                   //存放历史命令
        write_history(NULL);                    //写入历史命令
        parse(command);                         //解析命令
        do_cmd(argc,argv);                      //执行命令
        argc = 0;                               //将argc置0,重新读取命令
        free(command);                          //释放堆区空间,等待重新分配(由readline库函数完成)
    }
}
  • 在main函数中,我们首先会注意到readline这个完全陌生的库,它帮助我们完成了很多工作:

    • 用户命令的获取
    • 动态内存的申请
    • 历史命令的存写
    • 那么,我们怎么才能掌握这么有用的库呢,这个问题的答案碍于篇幅我就不再展开,请大家参考这篇博客——readline库的简单使用
  • parse() : 顾名思义,这个函数就是用来解析命令的

  • do_cmd():核心函数,执行用户输入的命令

parse(command)

  • 解析command

SHOW ME THE CODE:

void parse(char *command)
{
   
/*
command 为用户输入的命令
*/
    //初始化argv与argc
    for(int i = 0; i < MAX_CMD; i++){
   
        argv[i] = NULL;
        for(int j = 0;j < MAX_CMD_LEN; j++){
   
            COMMAND[i][j] = '\0';
        }
    }
    argc = 0;//命令数计数器
    memset(backupCommand,0,sizeof(backupCommand));//非常重要,因为漏了这一句被整自闭了好久
    strcpy(backupCommand, command);//备份命令
    
    int j = 0;
    int len = strlen(command);
    for(int i = 0; i < len; i++){
   
        if(command[i] != ' '){
   
            COMMAND[argc][j++] = command[i];
        }else{
   //command[i] == ' '
            if(j != 0){
   //j为0则为连续空格情况
                COMMAND[argc][j] = '\0';
                argc++;
                j = 0;
            }
        }
    }
    if(j != 0){
   //处理命令行末尾
        COMMAND[argc][j] = '\0';
    }

    /*处理__内置命令__  | isspace()调用是为了处理空格*/
    argc = 0;
    int flg = OUT;
    for(int i = 0; command[i] != '\0'; i++){
   
        if(flg == OUT && !isspace(command[i])){
   
            flg = IN;
            argv[argc++] = command + i;
        }else if(flg == IN && isspace(command[i])){
   
            flg = OUT
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值