linux入门——“实操‘自定义shell’”

引入

在学习了进程之后,我自己写了一个非常简单的自定义shell 用来巩固一下自己学到的知识 。

        先来看一看shell:

        显式地信息有用户名,主机名,冒号和当前的路径加上$符号,那么就可以在显示的时候先显示这部分内容:

 void ShellShowMess()
 {
      getcwd(path,sizeof(path));
      char* p = nullptr; 
 
      p = FindLastPath(path);
  
      gethostname(hostname,sizeof(hostname));
      std::printf("%s@%s:%s$ ",getenv("USER"),hostname,p);    
  }

                  这里,getcwd是直接获取当前进程的工作路径。gethostname是用来得到主机名,它是一个系统调用,用man手册是可以在第二章查到:

        FindLastPath是我自己写的函数,用来找到该路径的最后一个目录:

 //找到最后一个目录
char* FindLastPath(char* pa)
{

    char* p = pa;
    p = p + strlen(p) - 1;
     while(1)
   {
        if(*p != '/')
        {
           p--;
        }
        else
        {
            p+=1;
             break;
          }
      }
 
    return p;
}
  

        该段代码编译运行之后的效果是这样的:

        现在已经初见雏形了,然而这还不够,我们是需要他能像真正的shell一样可以执行命令的。

        于是,现在就需要得到和解析命令。

 void GetCmd()
 {   
      argc = 0;
      fgets(cmd,sizeof(cmd),stdin);
      cmd[strlen(cmd)-1] = '\0';  
      cmdbuff[argc++] = strtok(cmd," ");
      while((bool)(cmdbuff[argc++] = strtok(nullptr," ")));
      argc--;
      
     int i = 0;
  }

             这里使用fgets来从标准输入中的到字符串,为什么不用scanf或者std::cin呢?原因是这两个函数是把空格作为输入的两个字符串的分隔符,而我们的命令是带有空格的,所以我们直接从标准输入拿字符串。

        由于直接从标准输入中拿取字符串,所以这个字符串的最后一个字符是回车,所以需要进行一次特殊处理。

        strtok是C标准库的字符串处理函数,用于将指定的字符替换为'\0',第一次调用的时候先替换,然后再返回替换后的字符的前边的字符串,例如“hello world”,,我要把空格进行替换,那么第一次就会把空格替换为'\0',再返回Hello的指针,第二次只需把NULL传入转换的字符处,就会自动处理上一次的字符串。当没有处理的处理的分隔符(在这里是空格),就会直接返回最后一个字符指针,当没有字符串进行处理的时候(这里是Hello World),就会返回空指针。

        我在这里将空格替换为'\0',用来分割命令的参数。其中第一个肯定是命令,后续的肯定是参数,于是我们用另外一个数组cmdbuff去存储每个字符串的指针。这样就拿到了命令和它的选项参数。

        接下来就是命令的执行了:

 void Excute()
 {
     //创建子进程并执行命令
     pid_t id = fork();
     if(id == 0)
     {
         execvp(cmdbuff[0],cmdbuff); 
         _exit(1);
     }
 
     pid_t ret = waitpid(id,NULL,0);
     if(ret < 0)
         std::perror("waitpid");
 }

        因为我们不能影响到我们的父进程(这里使用了execvp来进行程序的替换,程序的替换是为了将该程序替换为其他程序来执行,并且程序的替换并不会创建新进程),所以在这里使用创建子进程来执行我们输入的命令,这里的程序替换的函数我们使用execvp,原因是第一个参数就是一个字符串(这里是命令),第二个参数可以传入字符的指针数组(这里是命令选项)。而exec系列的函数用同样可以在man手册里查到(以下是库函数):

        而execve是系统调用:

        可见库函数是在系统调用的基础上进行了封装。

        当子进程执行完毕后,为了防止执行和父进程一样的代码,这里需要使用_exit(1)来终止子进程(不用exit(1)的原因是exit(1)会刷新缓冲区,这样增加了一些不确定性,例如刷新出不必要的内容)。最后使用waitpid()来获取进程的结束状态,避免僵尸进程的产生。

        以下来看代码效果:

        1.使用ls命令:

        2.使用pwd命令:

        3.使用cd命令:

        哎?为什么cd命令不起作用呢?

        在linux里,cd是属于内建命令(上边的pwd也是),因为pwd指数打印当前路径(子进程继承父进程的环境变量),而cd是改变当前进程的工作路径,但是我们的命令是在子进程里执行的,那么改变的也是子进程的路径,但是我们显示的是父进程路径,所以是需要改变父进程的路径,于是乎,这里可以写一个函数用来执行内建命令:

 void BuiltInCmd()
 {
     std::string s = cmdbuff[0];
     if(s == "cd")
     {                                                                                                                                   
         chdir(cmdbuff[1]);
     }
 }

        这个函数就是用来检查是否是内建命令。而if后还可以增加需要的内建命令,在这里我只写一个cd的命令。
        内建命令的判断应该执行在非内建命令之前,保证逻辑的清晰:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值