Linux之实现简易的shell

1.打印提示符并获取命令行

我们在使用shell的时候,发现我们在输入命令是,前面会有:有用户名版本当前路径等信息,这里我们可以用环境变量去获取:

Linux之实现简易的shell_第1张图片

  1 #include 
  2 #include 
  3 
  4 const char* getUsername()
  5 {
  6     const char* name = getenv("USER");
  7     if(name) return name;
  8     else return "none";
  9 }
 10 
 11 const char* getHostname()
 12 {
 13     const char* hostname = getenv("HOSTNAME");
 14     if(hostname) return hostname;
 15     else return "none";
 16 }
 17 
 18 const char* getCwd()
 19 {
 20     const char* cwd = getenv("PWD");
 21     if(cwd) return cwd;
 22     else return "none";
 23 }
 24 
 25 int main()
 26 {
 27    printf("%s@%s %s\n",getUsername(),getHostname(),getCwd());                                                                                                                                                 
 28     return 0;
 29 }

写。

看到我们打印出来的是绝对路径, 而shell显示的相对路径, 但为了区分先这样不去裁剪. 

 1 #include 
  2 #include 
  3 #include 
  4                                                                                                            
  5 #define NUM 1024                                                                                           
  6                                                                                                            
  7 const char* getUsername()                                                                                  
  8 {                                                                                                          
  9     const char* name = getenv("USER");                                                                     
 10     if(name) return name;                                                                                  
 11     else return "none";                                                                                    
 12 }                                                                                                          
 13                                                                                                            
 14 const char* getHostname()                                                                                  
 15 {                                                                                                          
 16     const char* hostname = getenv("HOSTNAME");                                                             
 17     if(hostname) return hostname;                                                                          
 18     else return "none";                                                                                    
 19 }                                                                                                          
 20                                                                                                            
 21 const char* getCwd()                                                                                       
 22 {                                                                                                          
 23     const char* cwd = getenv("PWD");                                                                       
 24     if(cwd) return cwd;                                                                                    
 25     else return "none";                                                                                    
 26 }                                                                                                          
 27                                                                                                            
 28 int getUsercommand(char* command, int num)                                                                 
 29 {                                                                                                          
 30     printf("[%s@%s %s]",getUsername(),getHostname(),getCwd()); 
 31     char* r = fgets(command,num,stdin);//最终还是会输入\n
 32     if(r == NULL) return 1;
 33                          
 34     command[strlen(command)-1] = '\0';//去除输入的换行
 35     return 0;            
 36 }                        
 37                          
 38 int main()               
 39 {                        
 40     char usercommand[NUM];
 41     //1.打印提示符并且获取命令字符串
 42     getUsercommand(usercommand,sizeof(usercommand));
 43     //2.                 
 44     //3.                                                                                                                                                                                                      
 45     printf("%s",usercommand);//回显命令,用于测试
 46     return 0;
 47 }

 由于用scanf接收的遇到空格就会停止读取, 所以用fgets, 而且用户输入完命令一定会输入回车, 所以把最后一个回车符删掉.


2.解析命令行

我们在输入命令时, 可能不仅仅只是一段,比如说:"ls -a -l "。但是命令行解释器内部在解析指令时应该传递的是"ls" "-a" "-l"这样的多个个字符串, 所以我们还需要以空格来分割字符串。

    1 #include 
    2 #include 
    3 #include 
    4 
    5 #define DEBUG 1
    6 #define NUM 1024
    7 #define SIZE 64
    8 #define SEP " "

   41 void commandSplit(char* in, char* out[])
   42 {
   43     int argc = 1;
   44     out[0] = strtok(in,SEP);
W> 45     while(out[argc++] = strtok(NULL,SEP));//报警不需要处理
   46 
   47 #ifdef DEBUG 
   48     for(int i = 0; out[i]; i++)
   49         printf("%d:%s\n",i,out[i]);
   50 #endif
   51 }
   52 
   53 int main()
   54 {
   55     char usercommand[NUM];
   56     char* argv[SIZE];
   57     //1.打印提示符并且获取命令字符串
   58     getUsercommand(usercommand,sizeof(usercommand));
   59     //2.分割字符串
   60     commandSplit(usercommand, argv); 
   61     //3.                                                                                                                                                                                                    
   62     return 0;
   63 }            

Linux之实现简易的shell_第2张图片


3.执行对应的命令 

创建子进程和进程替换, 为了不影响shell, 我们将大部分指令的执行让子进程去完成, 父进程只要阻塞等待子进程完成就好了。

    1 #include 
    2 #include 
    3 #include 
    4 #include 
    5 #include 
    6 #include 
    7 
    8 //#define DEBUG 1
    9 #define NUM 1024
   10 #define SIZE 64
   11 #define SEP " "
   12 
   13 const char* getUsername()
   14 {
   15     const char* name = getenv("USER");
   16     if(name) return name;
   17     else return "none";
   18 }
   19 
   20 const char* getHostname()
   21 {
   22     const char* hostname = getenv("HOSTNAME");
   23     if(hostname) return hostname;
   24     else return "none";
   25 }
   26 
   27 const char* getCwd()
   28 {
   29     const char* cwd = getenv("PWD");
   30     if(cwd) return cwd;
   31     else return "none";
   32 }
   33 
   34 int getUsercommand(char* command, int num)                                                                                             
   35 {
   36     printf("[%s@%s %s]",getUsername(),getHostname(),getCwd()); 
   37     char* r = fgets(command,num,stdin);//最终还是会输入\n
   38     if(r == NULL) return -1;
   39                                                                                                                                       
   40     command[strlen(command)-1] = '\0';//去除输入的换行
   41     return strlen(command);
   42 }
   43 
   44 void commandSplit(char* in, char* out[])
   45 {
   46     int argc = 1;
   47     out[0] = strtok(in,SEP);
W> 48     while(out[argc++] = strtok(NULL,SEP));//报警不需要处理
   49 
   50 #ifdef DEBUG 
   51     for(int i = 0; out[i]; i++)
   52         printf("%d:%s\n",i,out[i]);
   53 #endif
   54 }
   55 
   56 int execute(char* argv[])
   57 {
   58     pid_t id = fork();
   59     if(id < 0) return 1;
   60     else if(id == 0)
   61     {
   62         //child
   63         //exec commond
   64         execvp(argv[0],argv);
   65         exit(1);
   66     }
   67 
   68     else
   69     {
   70         //father
   71         pid_t rid = waitpid(id,NULL,0);
   72         if(rid < 0)
   73             printf("wait fail\n");
   74     }
   75 
   76     return 0;
   77 }
   78 
   79 int main()
   80 {
   81     while(1)
   82     {
   83         char usercommand[NUM];
   84         char* argv[SIZE];
   85         //1.打印提示符并且获取命令字符串
   86         int n = getUsercommand(usercommand,sizeof(usercommand));
   87         if(n <= 0) continue;//如果得到的是空串或者获取失败,不要往后执行
   88         //2.分割字符串
   89         commandSplit(usercommand, argv);
   90         //3.执行命令
   91         execute(argv);                                                                                                                 
   92     }
   93     return 0;
   94 }

 由于shell要一直运行, 所以要循环执行, 这里程序替换用execvp函数比较合适, 因为argv数组就是我们分割出的一个个命令的子串, argv[0]就是程序名, argv就是指令集. 父进程只进行wait即可. 

此外, getUsercommand函数可以优化一下, 返回的是输入的指令的长度, 如果接收失败(返回值为-1或者返回值是0只打印了空行)就不需要往下执行了, 直接continue进行下一轮.

Linux之实现简易的shell_第3张图片


4.特殊处理

 有一批命令, 不能让子进程执行, 必须让父进程自己执行, 这些命令叫内建命令.

1) cd指令

Linux之实现简易的shell_第4张图片

可以看到cd .. 之后并没有发生什么异常, 但是pwd之后发现路径没有发生变化. 

我们为什么能在linux中进入某个目录, 就是因为我们改变了shell的工作目录. 每个进程都有自己的工作目录, 我们想让父进程的工作目录发生改变, 但是程序替换之后都是子进程在执行cd .., 改变的都是子进程的工作目录, 子进程改变完了又被回收了, 父进程完全没发生变化, 所以cd应该实现成内建命令。

   79 void cd(const char* path)
   80 {
   81     chdir(path);
   82 }
   83 
   84 //1->yes,0->no
   85 int doBuildin(char* argv[])
   86 {
   87     if(strcmp(argv[0],"cd") == 0)
   88     {
   89         char* path = NULL;
W> 90         if(argv[1] == NULL) path = ".";
   91         else path = argv[1];
   92         cd(path);
   93         return 1;
   94     }
   95     else if(strcmp(argv[0],"ls")==0)
   96     {
   97         return 1;
   98     }
   99     return 0;                                                                                                                         
  100 }
  101 
  102 int main()
  103 {
  104     while(1)
  105     {
  106         char usercommand[NUM];
  107         char* argv[SIZE];
  108         //1.打印提示符并且获取命令字符串
  109         int n = getUsercommand(usercommand,sizeof(usercommand));
  110         if(n <= 0) continue;//如果得到的是空串或者获取失败,不要往后执行
  111         //2.分割字符串
  112         commandSplit(usercommand, argv);
  113         //3.检查是不是内建命令,是的话直接执行
  114         n = doBuildin(argv);
  115         if(n) continue;//是内建命令不用往后执行了
  116         //4.执行命令
  117         execute(argv);
  118     }
  119     return 0;
  120 }

所以在执行命令前先检查是不是内建命令, 用返回值接收, 如果是就直接执行并返回1, continue不往下执行, 如果不是就返回0, 执行命令. 

Linux之实现简易的shell_第5张图片 既然当前的工作目录改变了, 那么环境变量PWD也要改变: 

Linux之实现简易的shell_第6张图片

chdir改变当前工作目录, getcwd获取当前的工作路径, sprintf将tmp中的内容输出到cwd中, putenv将cwd导入环境变量. 

Linux之实现简易的shell_第7张图片


2) export命令 

 

Linux之实现简易的shell_第8张图片

创建一个数组env储存要导入的环境变量, 设置size指向导入到第几个环境变量.

如果argv[1]是空就直接返回, 否则就导入环境变量, 注意不能直接把argv[1]导入进去, 因为argv[1]随着指令的输入时刻在变化, 需要开辟额外的空间去存储.


3)echo指令

Linux之实现简易的shell_第9张图片

Linux之实现简易的shell_第10张图片


4)ls指令

我们执行的ls指令中不同的文件都有不同的颜色,所以对于ls我们可以在分割命令的时候加上一个“--color=auto”.

Linux之实现简易的shell_第11张图片

Linux之实现简易的shell_第12张图片


你可能感兴趣的:(linux,运维,服务器)