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("[%s@%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("[%s@%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;


GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐