Linux下实现自己的shell

TASK

打造一个绝无伦比的 xxx-super-shell (xxx 是你的名字),它能实现下面这些功能:

  • 实现 管道 (也就是 |)
  • 实现 输入输出重定向(也就是 > < >>)
  • 要求实现 在管道组合命令的两端实现重定向运算符
# Require 
cat < 1.txt | grep -C 10 abc | grep -L efd | tac >> 2.txt
# Does not require
cat < 1.txt | grep -C 10 abc > test1.txt | test2.txt > grep -L efd | tac >> 2.txt
  • 实现 后台运行(也就是 &
  • 实现 cd,要求支持能切换到绝对路径,相对路径和支持 **cd -**
  • 屏蔽一些信号(如 ctrl + c 不能终止)
  • 界面美观
  • 开发过程记录、总结、发布在个人博客中
    要求:
  • 不得出现内存泄漏,内存越界等错误
  • 学会如何使用 gdb 进行调试,使用 valgrind 等工具进行检测
测试用例
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
  • 核心为掌握Linux系统编程进程的部分

框架主体

main()

从main函数来分析实现的整体框架

int main(){
   
  signal(SIGINT,SIG_IGN);//屏蔽ctrl+c
  signal(SIGTSTP,SIG_IGN); //屏蔽ctrl+z
  while(1){
   
    char*argv[MAX]={
   NULL};
    printname();
    char*command=readline(" ");//readline函数输出给出的字符串并读取一行输入,并为读取的输入动态分配内存,返回值为指向读取输入的指针
    if (command == NULL) continue;//屏蔽ctrl+d 
    if (strlen(command) == 0) continue;//回车不爆栈
    int argc=1;  
    argv[0] = strtok(command, " ");
    for(int i=1;argv[i] = strtok(NULL, " ");i++) argc++;//将命令行输入分割为多个命令
    analyze_cmd(argc,argv);//解析命令
    do_cmd(argc,argv);//实现命令
    free(command); //释放空间
    clear_para();//重置参数
    }
}
  • 一些声明如MAX可以结合文章最后的全部代码查看

  • 首先要调用signal函数屏蔽一些信号

  • 由于shell是交互进程(进程分类详见此博客(超链接)),所以我们进入while(1)循环,注意,这里的argcargv不是main函数的参数,而是我们自定义的参数,意义和main函数的参数类似。

  • printname函数负责每次输入命令前和后的终端名字显示和路径显示,具体下文给出。

  • 这里使用了一个动态链接库readlilne,需要我们单独下载并通过相应头文件使用,具体点击这里。功能有很多,我们这里用到的是readline()函数读取输入和显示历史命令

  • 整体框架已经有了,下面给出各个部分的详细解释

接口详解

printname()

void printname(){
   
    char pathname[PATHMAX];
    getcwd(pathname,PATHMAX);//获取当前目录
    printf(BLUE"Whosefrienda-shell"CLOSE);//打印shell名称
    printf(GREEN" :%s"CLOSE,pathname);//打印路径
    printf("$ ");
    fflush(stdout);//清除缓冲区
}
  • 具体注释有解释,(下文代码也是主要看注释,特殊的具体解释)这里只说一下这个BLUEGREENCLOSE是通过宏定义实现的,定义如下

    #define BLUE "\033[34m"//宏定义实现有色字体
    #define GREEN "\033[32m"
    #define CLOSE "\033[0m"
    

analyze_cmd(argc,argv)()

这里用了全局变量

int cd =0;
int i_redir=0;
int o_redir=0;
int _pipe=0;
int a_o_redir=0;
int pass=0;//命令解析的参数

code

int analyze_cmd(int argc,char*argv[]){
   
    if (argv[0] == NULL) return 0;
    if (strcmp(argv[0], "cd") == 0) cd = 1;
    for (int i = 0; i < argc; i++){
   
      if (strcmp(argv[i], ">") == 0) o_redir = 1;
      if (strcmp(argv[i], "|") == 0) _pipe = 1;
      if (strcmp(argv[i], ">>") == 0) a_o_redir = 1;
      if (strcmp(argv[i], "<") == 0) i_redir = 1;
      if (strcmp(argv[i], "&") == 0){
   
        pass = 1;
        argv[i]=NULL;
      }
    }
}

每个参数都在``do_cmd```函数中辅助判定,从而使用不同的接口来实现命令。

void do_cmd(int argc,char*argv[])

void do_cmd(int argc,char*argv[]){
   
  if(pass==1) argc--;
  if (cd == 1) mycd(argv);
  else if (strcmp(argv[0], "history") == 0) showhistory();//展示历史命令
  else if (strcmp(argv[0], "exit") == 0)
  {
   
    printf("exit\n");
    printf("有停止的任务\n");
    exit(0);
  }
  else if ( i_redir== 1) iredir(argv);// < 
  else if ( o_redir== 1) oredir(argv);// >
  else if ( a_o_redir== 1) aoredir(argv);// >>
  else if ( _pipe == 1) mymulpipe(argv, argc);// | 管道放在最后判定,因为重定向中也有管道的判定
  else //需要fork子进程进行执行的命令
  {
   
    if (pid < 0)
    {
   
      perror("fork");
      exit(1);
    }
    else if (pid == 0) //子进程
    {
   
      execvp(argv[0], argv);
      perror("command");
      exit(1);
    }
    else if (pid > 0) //父进程
    {
   
      if(pass==1)
      {
   
        printf("%d\n",pid);
        return;
      }
      waitpid(pid, NULL, 0);
    }
  }
}

这个接口实际上通过判定参数真假值来调用其他函数来实现命令,本身只实现没有重定向和管道等的需要forkexecve的简单命令

  • 这里只讲一下fork子进程实现的命令,其他在下面的具体接口再详解
  • fork返回两个pid值,一个是父进程的,一个是子进程的,fork后的代码会被父进程和子进程分别执行一遍,所以需要进行判定来分别编写父进程和子进程需要执行的代码
  • 这里,子进程需要调用execvp来加载命令实现需要的代码
  • 父进程则调用waitpid来监控子进程的进行,并且在有&的情况下将控制权重新交给主函数,从而让子进程在后台执行命令的同时不影响shell前台继续执行新命令

void showhistory()

void showhistory()
{
   
  int i = 0;
  HIST_ENTRY **his;
  his = history_list();
  while (his[i] != NULL)
    printf("%-3d   %s\n", i, his[i++]->line);
}

这里的HIST_ENTRY类型和history_list函数都在<readline/history.h>中有定义

void mycd(char *argv[])

char lastpath[MAX];//为实现cd-而声明
void mycd(char *argv[]){
   
if (argv[1] == NULL)//未输入要跳转的目录的情况
  {
   
    getcwd(lastpath, sizeof(lastpath));
    chdir("/home");
  }
  else if (strcmp(argv[1], "-") == 0)//实现cd -
  {
   
    char newlastpath[MAX];
    getcwd(newlastpath, sizeof(lastpath));
    chdir(lastpath);
    printf("%s\n", lastpath);
    strcpy(lastpath, newlastpath);
  }
  else if (strcmp(argv[1], 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

谁的友人A

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

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

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

打赏作者

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

抵扣说明:

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

余额充值