秒懂Linux之制作简易shell

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

一.全部代码

 二.自定义shell


一.全部代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>


#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
#define STREND '\0'

#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)

//写几个与重定向相关的宏定义与全局变量
#define NoneRedir  -1
#define StdinRedir  0
#define StdoutRedir 1
#define AppendRedir 2

int redir_type = NoneRedir;
char *filename = NULL;



char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; // for test
int lastcode = 0;

const char* HostName()
{
    char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "None";
}

const char* UserName()
{
    char *hostname = getenv("USER");
    if(hostname) return hostname;
    else return "None";
}

const char *CurrentWorkDir()
{
    char *hostname = getenv("PWD");
    if(hostname) return hostname;
    else return "None";
}

char *Home()
{
    return getenv("HOME");
}

int Interactive(char out[], int size)
{
    // 输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin);
    out[strlen(out)-1] = 0; //'\0', commandline是空串的情况?
    return strlen(out);
}



void checkRedir(char in[])
{
    //我们先列出可能会出现的重定向指令
    // ls -a -l
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    // cat < log.txt
    
    //初始化最开始的状态:无文件,无重定向
    redir_type = NoneRedir;
    filename = NULL;
    //从后往前进行扫描指令
    int pos = strlen(in) - 1;
    while( pos >= 0 )
    {
        if(in[pos] == '>')
        {
            //获取到追加指令
            if(in[pos-1] == '>')
            {
                //标记为追加方式
                redir_type = AppendRedir;
                //在追加指令处变为\0,让fgets获取到\0为止
                in[pos-1] = STREND;
                //再准备获取后面的文件
                pos++;
                //遇到空格就跳过直到遇到文件名
                IgnSpace(in, pos);
               // while(in[pos]==' ') pos++;
                //通过起始地址加偏移量保存文件
                filename = in+pos;
                break;
            }
            else
            {
                //标记为输出方式
                redir_type = StdoutRedir;
                in[pos++] = STREND;
                IgnSpace(in, pos);
               // while(in[pos]==' ') pos++;
                filename = in+pos;
                break;
            }
        }
        else if(in[pos] == '<')
        {   
            //标记为输入方式
            redir_type = StdinRedir;
            in[pos++] = STREND;
            IgnSpace(in, pos);
           // while(in[pos]==' ') pos++;
            filename = in+pos;
            break;
        }
        //在没遇到重定向前一直遍历
        else
        {
            pos--;
        }
    }
}
void Split(char in[])
{
    //在分割前检测指令是否有重定向,如果有就该标记标记,该保存保存  
    checkRedir(in);
    int i = 0;
    argv[i++] = strtok(in, SEP); // "ls -a -l"
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
    if(strcmp(argv[0], "ls") ==0)
    {
        argv[i-1] = (char*)"--color";
        argv[i] = NULL;
    }
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
       
        int fd = -1;
				//如果为重定向输入
        if(redir_type == StdinRedir)
        {   
						//从文件获取输入的内容,而非键盘
            fd = open(filename, O_RDONLY);
						//输入重定向
            dup2(fd, 0);
        }
				//如果为重定向输出
        else if(redir_type == StdoutRedir)
        {
						//从向文件中输出内容,而非显示器
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC,0666);
						//输出重定向
            dup2(fd, 1);
        }
				//追加
        else if(redir_type == AppendRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND,0666);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        } 
        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
    //printf("run done, rid: %d\n", rid);
}

int BuildinCmd()
{
    int ret = 0;
    // 1. 检测是否是内建命令, 是 1, 否 0
    if(strcmp("cd", argv[0]) == 0)
    {
        // 2. 执行
        ret = 1;
        char *target = argv[1]; //cd XXX or cd
        if(!target) target = Home();
        chdir(target);
        char temp[1024];
        getcwd(temp, 1024);
        snprintf(pwd, SIZE, "PWD=%s", temp);
        putenv(pwd);
 //       putenv(pwd);
    }

    else if(strcmp("export", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1])
        {
            strcpy(env, argv[1]);
            putenv(env);
        }
    }
    else if(strcmp("echo", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1] == NULL) {
            printf("\n");
        }
        else{
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else{
                    char *e = getenv(argv[1]+1);
                    if(e) printf("%s\n", e);
                }
            }
            else{
                printf("%s\n", argv[1]);
            }
        }
    }
    return ret;
}

int main()
{
    while(1)
    {
        char commandline[SIZE];
        // 1. 打印命令行提示符,获取用户输入的命令字符串
        int n = Interactive(commandline, SIZE);
        if(n == 0) continue;
        // 2. 对命令行字符串进行切割
        Split(commandline);
        // 3. 处理内建命令
        n = BuildinCmd();
        if(n) continue;
        // 4. 执行这个命令
        Execute();
    }
    return 0;
}

 二.自定义shell

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
   
   #define SIZE 1024
   const char* HostName()
   {
       char *hostname = getenv("HOSTNAME");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char* UserName()
   {
       char *hostname = getenv("USER");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char *CurrentWorkDir()
   {
       char *hostname = getenv("PWD");
       if(hostname) return hostname;
       else return "None";
   }
   
   
   int main()
   {
   
       // 输出提示符并获取用户输入的命令字符串"ls -a -l"
      printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
      char commandline[SIZE];
      scanf("%s", commandline);
      printf("test: %s\n", commandline);                                                                                                                                                                                                                         
   
      return 0;
   
   }

首先我们先来模拟一下最开始的命令行及其输入~

已经有点味道了~ 就是功能有点单一,只能输入输出~

我们再执行一次模拟写入指令后发现当前缓冲区只能获取ls,后面遇到空格就刷新出来了。那要如何获取完整的指令呢?(不获取到完整的指令又怎么知道用户具体要干啥呢?)

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
   
   #define SIZE 1024
   const char* HostName()
   {
       char *hostname = getenv("HOSTNAME");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char* UserName()
   {
       char *hostname = getenv("USER");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char *CurrentWorkDir()
   {
       char *hostname = getenv("PWD");
       if(hostname) return hostname;
       else return "None";
   }
   
   
   int main()
   {
   
       // 输出提示符并获取用户输入的命令字符串"ls -a -l"
      printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
      char commandline[SIZE];
      fgets(commandline,SIZE,stdin);
      
      commandline[strlen(commandline)-1] = 0;    
  
      printf("test: %s\n", commandline);                                                                                                                                                                                                                         
   
      return 0;
   
   }

这里我们借助函数fgets来获取行内容~因为fgets结尾会自带/n(换行),所以我们把结尾处的'/n'给换掉~

既然能够获取完整的输入命令,接下来我们就需要以空格为分隔符拆除若干个字符串存放在argv数组中~

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
   
   #define SIZE 1024
   #define MAX_ARGC 64
   #define SEP " "
   const char* HostName()
   {
       char *hostname = getenv("HOSTNAME");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char* UserName()
   {
       char *hostname = getenv("USER");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char *CurrentWorkDir()
   {
       char *hostname = getenv("PWD");
       if(hostname) return hostname;
       else return "None";
   } 

  int main()                                                                                                                                                                                                         {                                                                                                                                                                                                                                                                                                                                                                                                                                         
     // 输出提示符并获取用户输入的命令字符串"ls -a -l"                                                                                                                              
     printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());                                                                                               
     char commandline[SIZE];                                                                                                                                                                                                                                                                                                                 
     fgets(commandline,SIZE,stdin);                                                                                                                                      
     commandline[strlen(commandline)-1] = 0;                                                                                                                        
                                                                                                                                                                             
                                                                                                                         
     //对命令行字符串进行切割                                                                                                                                             
     char* argv[MAX_ARGC];                                                                                                                                                   
     int i = 0;                                                                                                                                                      
     argv[i++] = strtok(commandline,SEP);
     while(argv[i++]=strtok(NULL,SEP));                                                                                                                                                                                                                                                                      
     for(int i = 0;argv[i];i++)                                                                                                                              
     {                                                                                                                                                      
     printf("argv[%d]: %s\n",i,argv[i]);                                                                                                                
     }                                                                                                                                                                                                                                                                                                                                                                   
     return 0;   
     
}

我们利用strtok进行字符串按空格(SEP)进行分割,若要继续分割则需要把参数换成NULL~

然后利用分割到最后剩下NULL的特性把所有输入命令都挨个存储到argv数组中~

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
   
   #define SIZE 1024
   #define MAX_ARGC 64
   #define SEP " "
   const char* HostName()
   {
       char *hostname = getenv("HOSTNAME");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char* UserName()
   {
       char *hostname = getenv("USER");
       if(hostname) return hostname;
       else return "None";
   }
   
   const char *CurrentWorkDir()
   {
       char *hostname = getenv("PWD");
       if(hostname) return hostname;
       else return "None";
   } 

  int main()                                                                                                                                                                                                         {                                                                                                                                                                                                                                                                                                                                                                                                                                         
     // 输出提示符并获取用户输入的命令字符串"ls -a -l"                                                                                                                              
     printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());                                                                                               
     char commandline[SIZE];                                                                                                                                                                                                                                                                                                                 
     fgets(commandline,SIZE,stdin);                                                                                                                                      
     commandline[strlen(commandline)-1] = 0;                                                                                                                        
                                                                                                                                                                             
                                                                                                                         
     //对命令行字符串进行切割                                                                                                                                             
     char* argv[MAX_ARGC];                                                                                                                                                   
     int i = 0;                                                                                                                                                      
     argv[i++] = strtok(commandline,SEP);
     while(argv[i++]=strtok(NULL,SEP));  
    //执行命令                                                                                                                                                                                                                                                                    
    pid_t id = fork();
    if(id == 0)    
   {        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);   
   }    
    int status = 0;
    pid_t rid = waitpid(id, NULL, 0);
    printf("run done,rid:%d\n", rid);                                                                                                                                                                                                                                                                                                                                                                 
    return 0;   
     
}

我们创建一个子进程,然后把存储好命名行指令的数组argv通过execvp函数进行进程替换~

就好比我们输入了ls -a -l,那么它们就会作为替换进程所需要执行的参数,通过这些参数找到真正的ls -a -l指令进而去调用~

最后再套上无限循环就可以一直使用我们的自定义shell了~

我们发现了一个问题:为什么cd指令无法生效呢? 明明其他命令都生效了~

因为我们这些命令最终都是子进程在执行的,所以当前执行cd命令的是子进程而不是当前的bash,

而子进程一执行就退出了,切换路径还有什么意义?所以是要让bash去切换才有用,然后我们才能看到路径的变化~

这种命令称为内建命令,不需要由子进程去执行,让其父进程去执行~

 

我们对内建指令cd进行特殊处理,如何获取到的指令中有cd那就标记为内建指令,并记录后面要去的路径,然后用chdir改变子进程的路径。若无则为NULL,cd直接到家目录下。

不过当前有一个问题,用pwd可以查到更新后的路径,但shell中打印的路径却没有更新~然后就是直接按回车会有空串的问题~

我们记录获取字符串的长度,如果空串0那就不要继续执行了~

至于路径变化我们只需要做到环境变量的更新就好了~

我们设置一个大小为SIZE的数组pwd,用来存储环境变量PWD~

通过snprintf函数把获取到的路径存储进pwd数组中,再通过putenv把这个环境变量进行更新或添加~

不过还是有点瑕疵,当执行cd ..的时候路径也会变成 .. 但这个明明是上一级路径~

我们可以借助函数getcwd来获取当前路径,因为路径我们已经切换过了,所以根据获取更新后的路径作为环境变量~

为什么我们在自定义shell中导入新的环境变量会看不到呢?因为子进程会退出,没必要~所以export也是内建命令

如果这样修改会发现env只有在第一次才可以查到新的环境变量,后面就看不到了。

因为argv是每一次都要去获取指令的,你虽然在这一次输入指令export被成功导入,但下一次的命令就没有了,这样就会覆盖argv,argv表又回归原状,找不到原来有export指令的表了,自然就找不到环境变量了

所以我们再定义一个关于环境变量的数组emv,然后导出的时候有env这个表在就不会因为argc覆盖问题而看不了环境变量了

我们再来获取一下进程的退出码~

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
    //printf("run done, rid: %d\n", rid);
}

然后对内建命令echo进行编写 

 else if(strcmp("echo", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1] == NULL) {
            printf("\n");
        }
        else{
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else{
                    char *e = getenv(argv[1]+1);
                    if(e) printf("%s\n", e);
                }
            }
            else{
                printf("%s\n", argv[1]);
            }
        }
    }

我们最后再来实现一个重定向的指令~

若有对重定向不了解的友友建议参考这篇文章:秒懂Linux之文件-CSDN博客

我们输入把重定向指令分割为两部分,在判断为重定向指令后获取重定向前面的内容以及获取重定向后面的文件~

void checkRedir(char in[])
{
    //我们先列出可能会出现的重定向指令
    // ls -a -l
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    // cat < log.txt
    
    //初始化最开始的状态:无文件,无重定向
    redir_type = NoneRedir;
    filename = NULL;
    //从后往前进行扫描指令
    int pos = strlen(in) - 1;
    while( pos >= 0 )
    {
        if(in[pos] == '>')
        {
            //获取到追加指令
            if(in[pos-1] == '>')
            {
                //标记为追加方式
                redir_type = AppendRedir;
                //在追加指令处变为\0,让fgets获取到\0为止
                in[pos-1] = STREND;
                //再准备获取后面的文件
                pos++;
                //遇到空格就跳过直到遇到文件名
                IgnSpace(in, pos);
               // while(in[pos]==' ') pos++;
                //通过起始地址加偏移量保存文件
                filename = in+pos;
                break;
            }
            else
            {
                //标记为输出方式
                redir_type = StdoutRedir;
                in[pos++] = STREND;
                IgnSpace(in, pos);
               // while(in[pos]==' ') pos++;
                filename = in+pos;
                break;
            }
        }
        else if(in[pos] == '<')
        {   
            //标记为输入方式
            redir_type = StdinRedir;
            in[pos++] = STREND;
            IgnSpace(in, pos);
           // while(in[pos]==' ') pos++;
            filename = in+pos;
            break;
        }
        //在没遇到重定向前一直遍历
        else
        {
            pos--;
        }
    }
}
void Split(char in[])
{
    //在分割前检测指令是否有重定向,如果有就该标记标记,该保存保存  
    checkRedir(in);
    int i = 0;
    argv[i++] = strtok(in, SEP); // "ls -a -l"
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
    if(strcmp(argv[0], "ls") ==0)
    {
        argv[i-1] = (char*)"--color";
        argv[i] = NULL;
    }
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
       
        int fd = -1;
				//如果为重定向输入
        if(redir_type == StdinRedir)
        {   
						//从文件获取输入的内容,而非键盘
            fd = open(filename, O_RDONLY);
						//输入重定向
            dup2(fd, 0);
        }
				//如果为重定向输出
        else if(redir_type == StdoutRedir)
        {
						//从向文件中输出内容,而非显示器
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC,0666);
						//输出重定向
            dup2(fd, 1);
        }
				//追加
        else if(redir_type == AppendRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND,0666);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        } 
        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
    //printf("run done, rid: %d\n", rid);
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值