秒懂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]);
            }
        }
    }

相关推荐
StudyHappiness1 分钟前
MongoDB新版本,单节点安装
linux·运维·mongodb·kylin
言之。9 分钟前
【K-Means】
算法·机器学习·kmeans
微服务商城技术分享10 分钟前
通过Docker实现openGauss的快速容器化安装
运维·docker·容器
hummhumm41 分钟前
第 10 章 - Go语言字符串操作
java·后端·python·sql·算法·golang·database
运维佬1 小时前
在 Linux 系统上部署 Apache Solr
linux·apache·solr
Jeffrey_oWang1 小时前
软间隔支持向量机
算法·机器学习·支持向量机
编程墨客1 小时前
第03章 文件编程
linux·运维·服务器
命里有定数1 小时前
windows工具 -- 使用rustdesk和云服务器自建远程桌面服务, 手机, PC, Mac, Linux远程桌面 (简洁明了)
linux·运维·服务器·windows·ubuntu·远程工作
cleveryuoyuo1 小时前
进程的程序替换exec*函数和shell实现
linux·服务器
运维佬1 小时前
nginx配置负载均衡详解
运维·nginx·负载均衡