在Linux下面開發一個mini版的shell

一.shell的原理

Linux系統提供給用戶的最重要的系統程序是Shell命令語言解釋程序。它不屬於內核部分,而是在覈心之外,以用戶態方式運行。
其基本功能是解釋並執行用戶打入的各種命令,實現用戶與Linux核心的接口。系統初啓後,核心爲每個終端用戶建立一個進程去
執行Shell解釋程序,shell的簡單定義就是命令行解釋器。
我現在打印一下當前進程的父進程。
在這裏插入圖片描述
編譯運行後,打印出來的就是父進程,通過ps aux來查看該父進程的狀態。我們可以很明顯的看到當前test對應的進程------56042進程就是bash進程,而bash就是一個具體的shell。shell會f先ork一個子進程,它最後會被替換成我們敲入指令對應的代碼和數據。
在這裏插入圖片描述

二.整體思路

  1. 打印一個提示符,讓用戶輸入一個指令。
  2. 解析輸入的指令,找到對應的可執行程序。
  3. 創建子進程,子進程程序替換(替換成你敲入的命令),來加載可執行程序。
  4. 父進程進行進程等待,等待子進程結束。
  5. 子進程結束,父進程從wait中返回,循環執行1.

1.打印一個提示符,讓用戶輸入一個指令。
我們知道每次輸入指令的時候,都會有一個提示符,我在這裏爲了簡便暫時將這個提示符寫死,不隨着用戶所在路徑而變化。這個部分非常的簡單,一個簡單的printf就可以解決問題。

printf("[zhaotiedan@localhost myshell]$");

2. 解析輸入的指令,找到對應的可執行程序。
首先要讀取用戶輸入的指令,我一開始使用的輸入函數是scanf,但是它是一個遇到空格就返回的函數,這就代表假如我輸入的是ls -l指令,那麼這個函數就會以分兩次返回,一次返回ls,一次返回-l。所以我在這裏選用了gets函數,一次讀一行數據。

char *strtok(char str[], const char *delim);
//str是要分解的字符串,delim是字符串中用來分解的字符

我在這裏來分裝一個方法Split,在裏面使用strtok這個函數,將切分出來的結果依次放入一個字符串數組裏面,在main函數裏只需拿一個返回值來接收就ok了。

int Split(char input[],char* output[])
//input表示待切分的命令
//output表示切分結果
{
    char* p=strtok(input," ");//用空格來分解input
    int i=0;
    while(p!=NULL)
    {
        output[i]=p;
        i++;
        p=strtok(NULL," ");
    }
    output[i]=NULL;
    return 0;
}

char *argv[1024]={0};
int n=Split(command,argv);

這個時候我就能到達這樣的效果了
在這裏插入圖片描述
那我們現在再詳細的剖析一下strtok函數的執行過程。
分析得到,第一次調用strtok時,strtok返回的是指向ls(待切分指令的第一個字符串)的指針,當打印時,本來ls後面應該打印空格,但是我們可以很明顯的看到是因爲讀取到‘\0’才調用結束的。那麼,可以得出一個結論,是strtok將字符串的最後一個空格替換成了’\0’,所以strtok函數具有破壞原始字符串的功能。
第二次調用strtok時,strtok返回的是指向-的指針,而後面都會從上次切分的位置開始往後切分,所以strtok可以保存上次的切分結果。
第三次調用時,strtok返回指向/的指針
最後一次調用,srtok返回NULL;
在這裏插入圖片描述
總結:strtok必須循環調用,並且第一次調用和後續調用時傳的參數是不一樣的。並且使用的時候會導致線程不安全
strtok調用難,而且調用風險這麼大,所以最科學的方法是基於boost的方法。Boost是爲C++語言標準庫提供擴展的一些C++程序庫的總稱,它是一個可移植、提供源代碼的C++庫,作爲標準庫的後備,是C++標準化進程的開發引擎之一,是爲C++語言標準庫提供擴展的一些C++程序庫的總稱。像C++中的智能指針就是基於這個庫來使用的。

3-5創建子進程,子進程程序替換,來加載可執行程序。

void CreateProcess(char* argv[],int n)
 {
   (void)n;
   //1.創建子進程     
   int ret=fork();    
   //2.父進程進行進程等待,子進程進行程序替換
   if(ret>0)          
   {                  
     //father         
     wait(NULL);      
   }                  
   else if(ret==0)    
   {                  
     //child          
     ret=execvp(argv[0],argv);//敲入的指令只有一個名字,沒有路徑,得去path中找
     //出錯的判定if可省略,如果替換成功,肯定不會再執行後面這些代碼
     perror("exec");  
     exit(0);         
   }                  
   else{              
     perror("fork");  
   }                                                                                
 }

三.源代碼

#include <stdio.h>                                                                 
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/wait.h>
 #include <string.h>
 
 //input--待切分命令
 //output--表示切分u結果(字符串數組)
 //返回值爲數組中有效的指令個數
 int Split(char input[],char* output[])
 {
   //藉助strtok
   char* p=strtok(input," ");//以空格來切分,返回的是指向第一個字符串的指針
   int i=0;
   while(p!=NULL)
   {
     //字符串放入output
     output[i]=p;
     i++;
     p=strtok(NULL," ");
   }
   output[i]=NULL;//必須以空指針來結尾
   return i;                                                      
 }                                                                
                                                                  
 void CreateProcess(char* argv[],int n)                           
 {                                                                
   (void)n;                                                       
   //1.創建子進程                                                 
   int ret=fork();                                                
   //2.父進程進行進程等待,子進程進行程序替換                     
   if(ret>0)                
  {
     //father
     wait(NULL);
   }
   else if(ret==0)
   {
     //child
     ret=execvp(argv[0],argv);//敲入的指令只有一個名字,沒有路徑,得去path中找
     //出錯的判定if可省略,如果替換成功,肯定不會再執行後面這些代碼
     perror("exec");
     exit(0);
   }
   else{
     perror("fork");
   }
 }
 
 int main()
 {
   while(1)
   {
     //1.打印一個提示符,用緩衝區刷新輸出到顯示器上
     printf("[zhaotiedan@localhost myshell]$");
     //fflush(stdout);
     //2.用戶輸入一個指令,遇到回車讀取結束
     char command[1024]={0};                                                        
     //scanf("%s",command);//scanf遇到空格會返回,所以不能用它
     gets(command);//一次讀一行數據
     //3.解析指令,把要執行哪個程序識別出來,哪些是命令,哪些是參數
     char *argv[1024]={0};//放切分結果
     int n=Split(command,argv);
    //4.創建子進程,進行程序替換
     CreateProcess(argv,n);
   }
   return 0;
 }

四 .功能展示

  • ls

在這裏插入圖片描述

  • ls -l

在這裏插入圖片描述

四.缺陷和改進點

  1. 要做到提示符隨用戶當前所在路徑變換而變化。
  2. 要支持cd命令,因爲cd修改的是子進程的路徑,對父進程沒有影響。需要讓父進程直接支持cd(而不是創建子進程和程序替換)
  3. 需要支持定義別名。
  4. 需要支持管道。
  5. 需要支持重定向。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章