1. 程式人生 > >Linux--實現一個簡易的Shell(可以獲取命令提示符,支援管道)

Linux--實現一個簡易的Shell(可以獲取命令提示符,支援管道)

Shell的實現過程

1.從標準輸入獲取命令列;
2.對使用者輸入的命令列進行解析,解析出要執行的指令和引數;
3.建立一個子程序;
4.子程序進行程式替換,父程序等待;
5.當子程序執行完畢,父程序從wait中返回,繼續下一次迴圈。

shell命令提示符

(1)一般的shell輸入命令之前都會有命令提示符:

[root@localhost 程序替換]# 

root為使用者名稱,localhost為主機名,後面跟著的是當前目錄的相對路徑;
(2)所以在自己的Shell裡面也可以輸出這個命令提示符:
①獲取使用者名稱
getpwuid根據傳入的使用者ID,返回指向passwd的結構體,該結構體初始化了裡面的所有成員.使用者的ID通過getuid( )函式獲得。

//passwd結構體
struct passwd
{
      char *pw_name;       /* 使用者名稱*/
      char *pw_passwd;     /* 密碼.*/
      __uid_t pw_uid;      /* 使用者ID.*/
      __gid_t pw_gid;      /*組ID.*/
      char *pw_gecos;      /*真實名*/
      char *pw_dir;        /* 主目錄.*/
      char *pw_shell;      /*使用的shell*/
};

用於獲取使用者名稱:


        struct passwd *pwd
; //定義一個passwd型別的指標pwd pwd = getpwuid(getuid()); pwd->pw_name;

②獲取主機名:(通過gethostname函式)


//獲得主機名
char hostname[100] ={0};
gethostname(hostname,sizeof(hostname));   //hostname裡面存放的就是主機名

③獲取相對路徑
getcwd( )函式用於獲取當前目錄的絕對路徑,但是shell裡命令提示符的後面是相對路徑,路徑使用/進行分隔,所以最後一個/後面的內容為相對路徑。

//char * strtok ( char * str, const char * delimiters );

Parse函式用於路徑的切分,將路徑按照”/”進行切分,將切分後的結果儲存在字串陣列output裡面。

void Parse(char* input, char* output[])//用於將路徑進行切分,最後一個/後面為當前路徑
{
    char* p = strtok(input,"/");
    int i = 0;
    while(p!= NULL)
    {
        output[i++] = p;
        p=strtok(NULL,"/");
    }
}

main函式中獲取相對路徑的有關程式碼:

 char path[100] = {0};
 getcwd(path,sizeof(path)); //getcwd獲得的是絕對路徑(path裡面存放的是絕對路徑)

 char* path2[100] = {0};                                                                                                             
 Parse(path,path2);  //Parse函式用於切分路徑,將路徑按照"/"進行切分

 char* path3 = NULL;  //path裡面為絕對路徑,將path按照/進行分割,分割的結果儲存在path2裡面,path3裡面儲存最後一個字串,即相對路>徑
 int i = 0;
 while(path2[i] !=NULL)
 {                                                                                                                             
       path3= path2[i];
       i++;
 }
從標準輸入獲取命令

定義一個buf陣列用於存放輸入的指令(指令是字串的形式);
在main函式中編寫如下程式碼,用於獲取命令列:

    char buf[1024] = {0};
    fgets(buf);
對使用者輸入的命令列進行解析

通過ParseArg函式對指令按照空格進行切分,將切分出來的單獨的字串放入output數組裡面。(切分用到字串處理函式strtok)

void ParseArg(char* input, char* output[])  //用於對輸入的命令進行切分
{
    char* p = strtok(input," ");
    int output_size = 0;
    while(p != NULL)
    {
        output[output_size++] = p;
        p = strtok(NULL," ");
    }
    output[output_size] = NULL;
}

在main函式中編寫如下程式碼:

//解析字串,解析出指令和引數
char* argv[100] = {};
ParseArg(buf,argv);  //ParseArg函式用於對命令列進行切分,buf存放從標準輸入獲取的指令,argv存放解析出來的命令引數
建立一個子程序,子程序進行程式替換,父程序等待

用Exec函式進行封裝,Exec函式裡面建立一個子程序,子程序進行程式替換,父程序進行等待(父程序不關心子程序的退出狀態,所以可以採用wait(NULL))

void Exec(char* argv[])  //建立子程序執行程序程式替換
{
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork()");
    }
    else if(pid == 0)
    {
        //child
        execvp(argv[0],argv);
        //execvp失敗會執行下面的語句
        perror("execvp()");
        exit(0);
    }
    else
    {
        //parent                                                                                                                      
        wait(NULL);
    }

Shell簡易版

#include<stdio.h>                                                                                                                  
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
#include<pwd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>

void Parse(char* input, char* output[])//用於將路徑進行切分,最後一個/後面為當前路徑
{
    char* p = strtok(input,"/");
    int i = 0;
    while(p!= NULL)
    {
        output[i++] = p;
        p=strtok(NULL,"/");
    }
}

void ParseArg(char* input, char* output[])  //用於對輸入的命令進行切分
{
    char* p = strtok(input," ");
    int output_size = 0;
    while(p != NULL)
    {
        output[output_size++] = p;
        p = strtok(NULL," ");
    }
    output[output_size] = NULL;
}

void Exec(char* argv[])  //建立子程序執行程序程式替換
{
    pid_t pid = fork();
    if(pid == -1)                                                                                                                       
    {
        perror("fork()");
    }
    else if(pid == 0)
    {
            execvp(argv[0],argv);
        //execvp失敗會執行下面的語句
        perror("execvp()");
        exit(0);
    }
    else
    {
        //parent
        wait(NULL);
    }
 }

int main()
{
    while(1)
    {
        //首先顯示shell提示
        //通過getuid獲得使用者的id,然後通過getpwuid通過使用者的id查詢使用者的passwd資料來獲取系統登入的使用者名稱
        //獲得當前路徑
        char path[100] = {0};
        getcwd(path,sizeof(path)); //getcwd獲得的是絕對路徑(path裡面存放的是絕對路徑)
        char* path2[100] = {0};                                                                                                             
        Parse(path,path2);

        char* path3 = NULL;  //path裡面為絕對路徑,將path按照/進行分割,分割的結果儲存在path2裡面,path3裡面儲存最後一個字串,即相對路>徑
        int i = 0;                                                                                                                      
        while(path2[i] !=NULL)
        {
            path3= path2[i];
            i++;
        }

        struct passwd *pwd;
        pwd = getpwuid(getuid());

        //獲得主機名
        char hostname[100] ={0};
        gethostname(hostname,sizeof(hostname));
        printf("[%[email protected]%s %s MyShell]",pwd->pw_name,hostname,path3);
        fflush(stdout);

        //從標準輸入讀取字串
        char buf[1024] = {0};
        gets(buf);

        //解析字串,解析出指令和引數
        char* argv[100] = {};
        ParseArg(buf,argv);

        //子程序執行exec函式,將這個過程封裝在Exec函式裡
         Exec(argv);
    }
    return 0;
}

執行結果:(注意到自己編寫的shell的命令提示符後面會帶上MyShell,這是為了與自己的shell進行區分)
這裡寫圖片描述

讓Shell支援管道

管道的實現方法

1.管道”|”是將”|”左邊指令的執行結果作為”|”右邊的輸入。
2.我們可以將一條帶有”|”的指令進行分割,將管道左邊指令的輸出結果重定向到管道的輸入(也就是管道的寫端),將標準輸入重定向至管道的讀端。讓兩個程序分別執行兩邊重定向後的指令。

管道的具體實現

(1)首先判斷一條指令是否包含管道(指令已經存放至char*的數組裡面,且該陣列以NULL作為結束標誌)

int IsPipe(char* argv[])
{                                                                                                                                       
    int i = 0;
    while(argv[i] != NULL)
    {
        if(strcmp(argv[i], "|") == 0)
        {
            return i+1;     //i的位置是管道,則i+1就是管道的下一個命令
        }
        ++i;
    }
    return 0;
}

(2)將管道左右兩邊的指令進行切分,切分後的分別放入兩個char*數組裡,並保證每個陣列以NULL作為結束標誌

void ParsePipe(char* input[], char* output1[],char* output2[])//用於將input按照|進行切分,最後一個後面為當前路徑                    

{
    int i = 0;
    int size1 = 0;
    int size2 = 0;
    int ret = IsPipe(input);   //ret是input陣列中管道"|"的下一個位置

    while(strcmp(input[i], "|")!=0)
    {
        output1[size1++] = input[i++];
    }
    output1[size1] = NULL;  //將分割出來的兩個char*陣列都以NULL結尾

    int j = ret;//j指向管道的後面那個字元
    while(input[j] != NULL)
    {
        output2[size2++] = input[j++];
    }           
    output2[size2] = NULL;
}

(3)執行包含管道的指令(利用execvp函式,首先建立一個程序,讓子程序建立一個管道,再創讓子程序建一個子程序(孫子程序),讓孫子程序執行管道左邊的指令,並將輸出結果重定向入管道,讓子程序從執行管道右邊的指令)

void ExecvPipe(char* argv1[],char* argv2[])  //argv1為左邊指令及引數的陣列argv2則為存放管道右邊指令及引數的陣列
{
    pid_t pid = fork();
    if(pid == -1)                                                                                                                       
    {                                                                                                                           
        perror("fork()");
    }
    else if(pid == 0)
    {
        //child,建立一個管道
        int fd[2];
        int ret = pipe(fd);

        if(fork() == 0 )
        {
            //子程序建立一個子程序,用於執行管道之前的命令
            close(fd[0]); //孫子程序關閉掉讀端
            dup2(fd[1],1);  //將標準輸出作為管道的標準輸入
            execvp(argv1[0],argv1);
        }
        wait(NULL);
        close(fd[1]); //子程序關閉掉寫端

        dup2(fd[0],0);   //將標準輸入重定向至管道的讀端
        execvp(argv2[0],argv2);
        //execvp失敗會執行下面的語句
        perror("execvp()");
        exit(0);    
    }
    else
    {
        //parent
        wait(NULL);                                                                                                                     
    }
}

(4)整體程式碼

#include<stdio.h>                                                                                                                       
#include<unistd.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
#include<pwd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>

//用於將路徑進行切分,最後一個/後面為當前路徑
void Parse(char* input, char* output[])
{
    char* p = strtok(input,"/");
    int i = 0;
    while(p!= NULL)
    {
        output[i++] = p;
        p=strtok(NULL,"/");
    }
}

//用於對輸入的命令進行切分
void ParseArg(char* input, char* output[])  
{
    char* p = strtok(input," ");
    int output_size = 0;
    while(p != NULL)
    {
        output[output_size++] = p;
        p = strtok(NULL," ");
    }
    output[output_size] = NULL;
}

void Exec(char* argv[])  //建立子程序執行程序程式替換
{
    pid_t pid = fork();
    if(pid == -1)                                                                                                                       
    {
        perror("fork()");
    }
    else if(pid == 0)
    {
         //child                                                                                                                         
        execvp(argv[0],argv);
        //execvp失敗會執行下面的語句
        perror("execvp()");
        exit(0);
    }
    else
    {
        //parent
        wait(NULL);
    }
}

//判斷指令是否包含管道
int IsPipe(char* argv[])
{
    int i = 0;
    while(argv[i] != NULL)
    {
        if(strcmp(argv[i], "|") == 0)
        {
            return i+1; //i是管道,則i+1就是管道的下一個命令
        }
        ++i;
    }
    return 0;
}

void ParsePipe(char* input[], char* output1[],char* output2[])//用於將input按照|進行切分,最後一個後面為當前路徑                    

{
    int i = 0;
    int size1 = 0;
    int size2 = 0;                                                                                                                      
    int ret = IsPipe(input);

    while(strcmp(input[i], "|")!=0)
    {
        output1[size1++] = input[i++];
    }
     output1[size1] = NULL;

     int j = ret;//j指向管道的後面那個字元
     while(input[j] != NULL)
     {
         output2[size2++] = input[j++];
     }

     output2[size2] = NULL;
}


void ExecvPipe(char* argv1[],char* argv2[])
{
    pid_t pid = fork();
    if(pid == -1)
    {                                                                                                                           
        perror("fork()");
    }                                                                                                                                   
    else if(pid == 0)
    {
        //child,建立一個管道
        int fd[2];
        int ret = pipe(fd);
        if(fork() == 0 )
        {
            //子程序建立一個子程序,用於執行管道之前的命令
            close(fd[0]); //孫子程序關閉掉讀端
            dup2(fd[1],1);  //將標準輸出作為管道的標準輸入
            execvp(argv1[0],argv1);
        }
        wait(NULL);

        close(fd[1]); //關閉掉寫端
        dup2(fd[0],0);
        execvp(argv2[0],argv2);
        //execvp失敗會執行下面的語句
        perror("execvp()");
        exit(0);    
    }
    else                                                                                                                                
    {
        //parent
        wait(NULL);
    }
}
int main()
{
    //獲得當前路徑
    while(1)
    {
        //首先顯示shell提示
        //通過getuid獲得使用者的id,然後通過getpwuid通過使用者的id查詢使用者的passwd資料來獲取系統登入的使用者名稱

        //獲得當前路徑
        char path[100] = {0};
        getcwd(path,sizeof(path)); //getcwd獲得的是絕對路徑(path裡面存放的是絕對路徑)
        char* path2[100] = {0};                                                                                                             
        Parse(path,path2);

        char* path3 = NULL;  //path裡面為絕對路徑,將path按照/進行分割,分割的結果儲存在path2裡面,path3裡面儲存最後一個字串,即相對路>徑                                                                                                                                      
        int i = 0;
        while(path2[i] !=NULL)
        {
            path3= path2[i];
            i++;
        }      

        struct passwd *pwd;
        pwd = getpwuid(getuid());
        //獲得主機名
        char hostname[100] ={0};
        gethostname(hostname,sizeof(hostname));
        printf("[%[email protected]%s %s MyShell]",pwd->pw_name,hostname,path3);
        fflush(stdout);

        //從標準輸入讀取字串
        char buf[1024] = {0};

        //解析字串,解析出指令和引數
        char* argv[100];
        ParseArg(buf,argv);

        //判斷解析出來的字串數組裡面是否含有管道                                                                                      
        int ret1 = IsPipe(argv);
        if(ret1 > 0)
        {
            //是管道
            char* argv1[10];
            char* argv2[10];
            ParsePipe(argv,argv1,argv2);
            ExecvPipe(argv1,argv2);
        }
        else
        {
            Exec(argv);
        }
    }
    return 0;


相關推薦

Linux--實現一個簡易Shell可以獲取命令提示符支援管道

Shell的實現過程 1.從標準輸入獲取命令列; 2.對使用者輸入的命令列進行解析,解析出要執行的指令和引數; 3.建立一個子程序; 4.子程序進行程式替換,父程序等待; 5.當子程序執行完畢,父程序從wait中返回,繼續下一次迴圈。 shell

實現一個簡單shell支援重定向

5.16更新:支援重定向 我們知道對於Linux,shell就是個命令列直譯器,當我們輸入相關的命令,會去執行相關的操作。 比如當我們輸入ls -a -l命令,shell就會打印出當前目錄的內容,這是如何實現的?shell自己就是一個程序,當我們輸入ls之類

18、OpenCV Python 簡單實現一個圖片生成類似抖音生成字母人像

gaussian int read 。。 str gray clas range TE 1 __author__ = "WSX" 2 import cv2 as cv 3 import numpy as np 4 5 def local_threshold(i

linux下nginx編譯安裝抄別人的方便檢視

原路徑:https://blog.csdn.net/youcijibi/article/details/75050993 正式開始前,編譯環境gcc g++ 開發庫之類的需要提前裝好。 如果是ububtu平臺初始安裝編譯安裝則使用如下指令:   apt-get install build-e

巧用margin/padding的百分比值實現高度自適應多用於佔位避免閃爍

一個基礎卻又容易混淆的css知識點 本文依賴於一個基礎卻又容易混淆的css知識點:當margin/padding取形式為百分比的值時,無論是left/right,還是top/bottom,都是以父元素的width為參照物的! 也許你會說,left/right以父元素的w

popopWindow 實現頂部篩選選單頂部不變底部陰暗思路記錄

先上效果:需要解決的問題:1.popopWindow 位置(此處為toolbar正下方)2.頂部不變,底部陰暗3.popopWindow 根據item個數適配高度,並設定最大height一、popopWindow 位置首先第一點,傳入的parent為toolbar。下面的lo

每天學習一個LINUX命令:passwd 修改設置用戶密碼 pass word 口令通過語

pass 更新 vpd 技術分享 oot 修改 時間 -o RoCE passwd命令用於設置用戶的認證信息,包括用戶密碼、密碼過期時間等。系統管理者則能用它管理系統用戶的密碼。只有root可以指定用戶名稱,一般用戶只能變更自己的密碼。 語法: passwd [參數] [

Linux下的簡易shell實現

Linux系統的shell作為作業系統的外殼,為使用者提供使用作業系統的介面。 它是命令語言、命令解釋程式及程式設計語言的統稱。 相當於bash的一個子程序,父程序等待,子程序進行程式替換。 shell充當一個橋樑:將使用者的命令翻譯給核心(kernel)處理;同時,將核心的

實現一個簡易的vue的mvvmdefineProperty

這是一個最近一年很火的面試題,很多人看到這個題目從下手,其實查閱一些資料後,簡單的模擬還是不太難的: vue不相容IE8以下是因為他的實現原理使用了 Object.defineProperty 的get和set方法,首先簡單介紹以下這個方法 我們看到控制檯

Linux下模擬實現一個微型shell

首先我們先看一下shell的執行過程:               shell從使用者讀入字串"ls",並建立一個新的程序,在那個程序中執行ls程式並等待那個程序結束。然後shell讀取新的一行輸入,建立一個新的程序,在這個程序中執行程式並等待這個程序結束。 所以我

【Android進階】如何寫一個很屌的動畫1---先實現一個簡易的自定義動畫框架

class MyView extends View { public void onDraw(Canvas canvas) { super.onDraw(canvas); invalidate(); } } 這樣一來,View每次繪製都是觸發下一次繪製,不過

Linux-C基礎知識學習:C語言作業-將5個學生成績儲存在一個數組中單獨實現一個計算平均成績的average函式 在main函式中獲取該函式返回的平均值並列印。

Linux基礎知識學習 C語言作業:將5個學生成績儲存在一個數組中,單獨實現一個計算平均成績的average函式, 在main函式中獲取該函式返回的平均值,並列印。 #include <s

用Python實現一個簡易的“聽歌識曲”demo

0. 背景   最近兩年,“聽歌識曲”這個應用在國內眾多的音樂類APP火熱上線,比如網易雲音樂,QQ音樂。使用者可以通過這個功能識別當前環境里正在播放的歌曲名字,聽起來很酷。其實“聽歌識曲”這個想法最早是由一家叫Shazam的國外公司提出的。   - 20

linux Shell程式設計--獲取命令執行返回結果

1.將命令執行的整型數字結果返回並賦值給變數。 使用反引號包裝命令字串 TAB上的` scarlett@scarlett-X550VXK:~$ line=`cat /etc/passwd| wc -

Androidstudio實現一個簡易的加法器——分享兩種方法實現日常作業練習

  Androidstudio實現一個簡易的加法器——分享兩種方法實現(日常作業練習)                               &

用java實現一個簡易編譯器1-詞法解析入門

new 概念 自加 我們 sta 數字 獲得 () 操作系統 本文對應代碼下載地址為: http://download.csdn.net/detail/tyler_download/9435103 視頻地址: http://v.youku.com/v_show/id_XMT

Linux基礎管理——文本處理命令組合解決大問題

文本處理命令 grep 正則表達式 擴展正則表達式 前言: 在服務器中對日誌進行處理是十分常見的工作,所以運維對於各種文本工具來查看、分析、統計,是必備的基本工。那麽學習正則表達式、grep、egrep、和tr、sort、uniq等常見的文件處理命令就十分有必要了。1、工具分類 文

使用 RxJS 實現一個簡易的仿 Elm 架構應用

inter bject compute 幫助 規律 returns date rgs 個人愛好 使用 RxJS 實現一個簡易的仿 Elm 架構應用

在Android中實現一個簡易的Http服務器

.get json data ESS public 瀏覽器 顯示 getmethod blank 最近遇到一個需求需要在App中創建一個Http服務器供供瀏覽器調用,用了下開源的微型Htpp服務器框架:NanoHttpd,項目地址:https://github.com/Na

實現一個簡易的express中間件

str 修改 clas middle 分享圖片 next 測試 inf 返回 代碼: // 通過閉包實現單例 const Middlewave = (function(){ let instance; class Middlewave{ c