zoukankan      html  css  js  c++  java
  • 模拟Linux的shell

    在学习了Linux的进程控制之后,学习了fork函数和exec函数族,通过这些个函数可以简单的实现一份shell,就是实现一份命令行解释器,当然是简单版的,实现功能如下

    1. 能执行普通的命令如ls ,ps ,top等
    2. 可以实现目录的跳转cd命令
    3. 能执行命令并加上参数如ls-l
    4. 能执行打开man手册
    5. 能识别管道符

    还不能实现正则表达式,要实现这个我当前的代码根本不能用,要重头开始改写。。。

    下面贴代码

      1 #include <stdio.h>
      2 #include <unistd.h>
      3 #include <string.h>
      4 #include <sys/types.h>
      5 #include <sys/wait.h>
      6 #include <stdlib.h>
      7 #include <pwd.h>
      8 #include <sys/utsname.h>
      9 #include <libgen.h>
     10 
     11 
     12 void eatblank(char **buf)
     13 {
     14   while(**buf==' ')
     15   {
     16     (*buf)++;
     17   }
     18 }
     19 
     20 
     21 void GetHostName(char *hostname,int length)
     22 {
     23   gethostname(hostname,length);
     24   char *p=hostname;
     25   while(*p!='')
     26   {
     27     if(*p=='.')
     28     {
     29       *p='';
     30     }
     31     p++;
     32   }
     33 }
     34 
     35 void Pipe(char **my_argv,char *buf);
     36 void BuildCommand(char **my_argv,char *buf)
     37 {
     38   eatblank(&buf);
     39   my_argv[0]=buf;
     40   int index=1;
     41   char *p=buf;
     42       while(*p!='')
     43       {
     44         if(*p==' ')
     45         {
     46           *p='';
     47           p++;
     48           eatblank(&p) ;
     49           if(*p!='|')
     50           {
     51             my_argv[index++]=p;
     52           }
     53           continue;
     54         }
     55         else if(*p=='|')
     56         {
     57           p++;
     58           //p++;
     59           my_argv[index]=NULL;
     60           Pipe(my_argv,p);
     61         }
     62         else
     63         {
     64           p++;
     65         }
     66       }
     67      my_argv[index]=NULL;
     68 }
     69 
     70 
     71 
     72 void Pipe(char ** my_argv,char *buf)
     73 {
     74   int fd[2];
     75   pipe(fd);
     76   pid_t id2=fork();
     77   if(id2==0)
     78   {
     79     close(1);
     80     dup(fd[1]);
     81     close(fd[1]);
     82     close(fd[0]);
     83     execvp(my_argv[0],my_argv);
     84   }
     85   else
     86   {
     87     waitpid(id2,NULL,0);
     88     close(0);
     89     dup(fd[0]);
     90     close(fd[0]);
     91     close(fd[1]);
     92     BuildCommand(my_argv,buf);
     93     execvp(my_argv[0],my_argv);
     94   }
     95   //在此处添加exec族函数
     96 }
     97 
     98 
     99 int main()
    100 {
    101   while(1)
    102   {
    103     char *my_argv[64];
    104       struct passwd *pwd=getpwuid(getuid());
    105       char hostname[256]={''};
    106       char cwd[256]={''};
    107       getcwd(cwd,256);
    108       GetHostName(hostname,256);
    109       printf("[%s@%s %s]#",pwd->pw_name,hostname,basename(cwd));
    110       fflush(stdout);
    111       char buf[1024];
    112       buf[0]='';
    113 
    114       int count=read(0,buf,sizeof(buf));
    115       buf[count-1]='';
    116       my_argv[0]=buf;    
    117     pid_t id=fork();
    118     if(id==0)
    119     {
    120       //child
    121      
    122       if(strncmp(buf,"cd",2)==0) 
    123       {
    124         exit(1);
    125       }
    126       BuildCommand(my_argv,buf);
    127      execvp(my_argv[0],my_argv);
    128      printf("if the process has some problem ,I should run here
    ");
    129      exit(0);
    130     }
    131     else
    132     {
    133       //father
    134       int status=0;
    135       wait(&status);
    136       if(status==256)
    137       {
    138         my_argv[0]+=3;
    139         chdir(my_argv[0]);
    140       }
    141     }
    142   }
    143   return 0;
    144 }

    通过gethostname获取主机名,通过getcwd获得当前工作目录,通过getpwuid获得当前登录的用户信息

    这样命令提示符就完成了;

    • 普通命令的实现

    普通命令的实现并不困难,我的目的是让子进程来执行命令,也就是通常的让fork产生的子进程执行exec族函数,然后自己死掉,通过父进程回收资源,循环并创建新的子进程,这就是shell的大框架,其中用execvp函数来实现命令的执行,函数原型就不多说了,查手册就能查到,简单解释一下参数,第一个参数是命令行的字符串,第二是参数是一个字符串数组,从上到下依次存放,命令,参数(可能有多个,一个参数占一个下标),最后用NULL占据一个下标表示结束。

    • cd命令的实现

    cd命令的实现有些问题,不是普通命令的实现,就是说简单的使用execvp是不能实现的,因为就算子进程改变了目录之后也会把自己杀死,父进程和子进程之间是不通的(就是说父进程和子进程并不共享环境变量,子进程修改了当前工作目录的环境变量对父进程也没有什么影响),后来使用system来执行系统调用,也失败,因为更多时候shell会产生一个子进程来执行命令,因为shell本身执行会有风险,可能会因为用户的错误操作把自己挂掉,所以使用子进程来执行命令,而这样和刚才的结果是一样的并不会影响到父进程,最后采用了chdir函数,他可以改变当前进程的环境变量中的工作目录(就是专门change dir用的),让父进程来执行,fork出来的子进程会有一份父进程环境变量的拷贝,这就完成了改变目录,并将结果传递了下来

     1  else
     2     {
     3       //father
     4       int status=0;
     5       wait(&status);
     6       if(status==256)
     7       {
     8         my_argv[0]+=3;
     9         chdir(my_argv[0]);
    10       }
    11     }
    • 管道符的实现

    管道符的实现很简单,平常我们执行命令的时候,都是结果自动输出到电脑屏幕上,这说明一般命令的输出是write在标准输出stdout的,而我们输出命令的参数是通过键盘,这说明命令的输入来源是标准输入stdin,如果我们关闭了,标准输出和标准输入,而是通过一个管道,让结果写进管道,然后让参数从管道中读取(简单的说就是让管道的两段代替标准输入和标准输出),管道符就实现了

     1 void Pipe(char ** my_argv,char *buf)
     2 {
     3   int fd[2];
     4   pipe(fd);
     5   pid_t id2=fork();
     6   if(id2==0)
     7   {
     8     close(1);
     9     dup(fd[1]);
    10     close(fd[1]);
    11     close(fd[0]);
    12     execvp(my_argv[0],my_argv);
    13   }
    14   else
    15   {
    16     waitpid(id2,NULL,0);
    17     close(0);
    18     dup(fd[0]);
    19     close(fd[0]);
    20     close(fd[1]);
    21     BuildCommand(my_argv,buf);
    22     execvp(my_argv[0],my_argv);
    23   }
  • 相关阅读:
    变量和数据类型
    Manager 多进程之间的数据共享
    多进程之间的数据传输 Pipe
    多进程
    消费者 生产者
    queue 队列优先级
    Python 最难的问题
    threading 多线程
    线程进程概述
    倒计时器 小玩意
  • 原文地址:https://www.cnblogs.com/lenomirei/p/5616797.html
Copyright © 2011-2022 走看看