秒懂Linux之制作简易shell

目录

一.全部代码

二.自定义shell


一.全部代码

cpp 复制代码
#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 " "
    
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 Split(char 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)
    {
        // 让子进程执行命名
        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);
    }
    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();
    }
   // for(int i=0; argv[i]; i++)
   // {
   //     printf("argv[%d]: %s\n", i, argv[i]);
   // }
    return 0;
}

二.自定义shell

cpp 复制代码
    #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,后面遇到空格就刷新出来了。那要如何获取完整的指令呢?(不获取到完整的指令又怎么知道用户具体要干啥呢?)

cpp 复制代码
    #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数组中~

cpp 复制代码
    #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数组中~

cpp 复制代码
    #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覆盖问题而看不了环境变量了

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

cpp 复制代码
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进行编写

cpp 复制代码
 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]);
            }
        }
    }

相关推荐
CoolTiger、1 小时前
【Vmware16安装教程】
linux·虚拟机·vmware16
jiao000011 小时前
数据结构——队列
c语言·数据结构·算法
m0_741768851 小时前
使用docker的小例子
运维·docker·容器
学习3人组2 小时前
CentOS 中配置 OpenJDK以及多版本管理
linux·运维·centos
厨 神2 小时前
vmware中的ubuntu系统扩容分区
linux·运维·ubuntu
Karoku0662 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
迷迭所归处3 小时前
C++ —— 关于vector
开发语言·c++·算法
geek_Chen013 小时前
虚拟机共享文件夹开启后mnt/hgfs/下无sharefiles? --已解决
linux·运维·服务器
leon6253 小时前
优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序
开发语言·算法·matlab
CV工程师小林3 小时前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先