Linux:自主shell命令行解释器附源码

上篇文章:Linux:进程程序替换


前言

本篇文章主要用于串联之前的Linux文章内容,加深学习印象

目录

前言

实现原理

输出命令行

获取用户输入

特殊处理:要让我们输入命令时命令本身不带\n

解析字符串

执行命令

内建命令

获得实时路径

优化

记录退出码

环境变量

优化环境变量

源码


实现原理

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束。所以要写一个shell,需要循环以下过程:

1.获取命令行

2.解析命令行

3.建立一个子进程 (fork)

4.替换子进程 (execvp)

5.父进程等待子进程退出 (wait)

输出命令行

命令行示例:

获得用户名等信息使用getenv:

此时myshell.c:

复制代码
  1 #include "myshell.h"
  2 
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <unistd.h>
  6 
  7 // 命令行相关
  8 static char username[32]; // 用户名
  9 static char hostname[64]; // 主机名
 10 static char cwd[256];  // 当前路径名
 11                                                                     
 12 static void GetUserName()                     
 13 {                                             
 14     char *_username = getenv("USER");         
 15     strcpy(username,(_username ?_username : "None"));
 16 }                                             
 17                                               
 18 static void GetHostName()                     
 19 {                                             
 20     char *_hostname = getenv("HOSTNAME");     
 21     strcpy(hostname,(_hostname ?_hostname : "None"));
 22 }                                             
 23                                               
 24 static void GetCwdName()                      
 25 {                                             
 26     char *_cwd = getenv("PWD");               
 27     strcpy(cwd,(_cwd ?_cwd : "None"));        
 28 }                                             
 29                                               
 30 void PrintPrompt() // 提示符                  
 31 {                                             
 32     GetUserName();  
 33     GetHostName();
 34     GetCwdName();
 35     printf("[%s@%s %s]# ", username, hostname, cwd);
 36     fflush(stdout);
 37 }
 38 
 39 void Bash()
 40 {
 41     while(1)
 42     {
 43         // 第一步:输出命令行
 44         PrintPrompt();
 45         sleep(1);
 46 
 47     }
 48 }

main.c

复制代码
  1 #include "myshell.h"
  2 
  3 int main()
  4 {
  5     Bash();
  6     return 0;                                                       
  7 }

myshell.h

复制代码
    1 #pragma 
    2 
    3 #include <stdio.h>
    4 
    5 void Bash();  

此时的输出样例,没有换行,路径显示为绝对路径

获取用户输入

首先要明确,在等待用户输入时,不能使用scanf输入,因为我们打入的ls -a -l实际上是一个字符串"ls -a -l",而scanf以空格作为分隔符。所以我们使用fgetc:

特殊处理:要让我们输入命令时命令本身不带\n

见下方代码,因为stdin带有\n,但是我们要让自己输入的命令字符串不带\n,\n应该由printf中的内容提供

修改后:

此处commandline不可能等于零,因为在fgets中至少会带有一个\n

结果:

解析字符串

char *argv[]做为命令行参数

解析命令是重复进行的事件,所以先将全局变量做初始清空

**接口strtok:**可以将传入的字符串进行你的要求切割(一次返回一个token)

第一次,传入字符串本身,第二次之后,如果还想切割后续字符串,参数为NULL->把正在切割的字符串记录下来->使用static变量即可

测试下方代码:

为空时显示为1:

再修改代码:

执行命令

复制代码
 75 void Execute()
 76 {
 77     pid_t id = fork();
 78     if(id < 0)
 79     {
 80         perror("fork");
 81         return;
 82     }
 83     else if(id == 0)
 84     {
 85         // child
 86         // 程序替换
 87         execvp(argv[0], argv);
 88         exit(1);
 89     }
 90     else{
 91         // father
 92         pid_t rid = waitpid(id, NULL, 0);
 93         (void)rid;
 94     }
 95 }                                        

内建命令

我们的cd等命令并没有作用,原因是通过fork函数,这些命令都由子进程执行,所以只会影响子进程而不是父进程,所以应该通过内建命令(bash内部的函数)由父进程执行。

获得实时路径

根据上述运行结果,发现提示符相关的内容并没有根据路径的变化而变化,原因是我们使用getenv获取环境变量的方式,并不会实时改变。

所以我们不采用环境变量,使用getcwd获得

优化

记录退出码

复制代码
122 int CheckBuiltinAndExecute()
123 {
124     int ret = 0;
125     if(strcmp(argv[0], "cd") == 0)
126     {
127         // 内建命令
128         ret = 1;
129         if(argc == 2)
130         {
131             chdir(argv[1]);
132         }
133     }
134     else if(strcmp(argv[0], "echo") == 0)
135     {
136         ret = 1;
137         if(argc == 2)
138         {
139             if(argv[1][0] == '$')
140             {
141                 if(strcmp(argv[1], "$?") == 0)
142                 {
143                     printf("%d\n", lastcode);                            
144                     lastcode = 0;
145                 }
146                 else 
147                 {
148                     // env
149                 }
150             }
151             else 
152             {
153                 printf("%s\n", argv[1]);
154             }
155         }
156     }
157     return ret;

环境变量

优化环境变量

将上述的环境变量放在bash中,启动时从系统env拷贝所有环境变量的指针到自定义的_environ数组中。

源码

myshell.c

复制代码
#include "myshell.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>

// 提示符相关
static char username[32]; // 用户名
static char hostname[64]; // 主机名
static char cwd[256];  // 当前路径名
static char commandline[256];
// 命令行相关
static  char *argv[64];
static int argc = 0;
static const char *sep = " ";
// 与退出码相关
static int lastcode = 0;
// 环境变量相关
static char **_environ;
static int envc = 0;

static void GetUserName()
{
    char *_username = getenv("USER");
    strcpy(username,(_username ?_username : "None"));
}

static void GetHostName()
{
    char *_hostname = getenv("HOSTNAME");
    strcpy(hostname,(_hostname ?_hostname : "None"));
}

static void GetCwdName()
{
    //char *_cwd = getenv("PWD");
    //strcpy(cwd,(_cwd ?_cwd : "None"));
    char _cwd[256];
    getcwd(_cwd, sizeof(_cwd));
    if(strcmp(_cwd, "/") == 0)
    {
        strcpy(cwd, _cwd);
    }
    else 
    {
        int end = strlen(_cwd) - 1;
        while(end >= 0)
        {
            if(_cwd[end] == '/')
            {
                strcpy(cwd, &_cwd[end+1]);
                break;
            }
            end--;
        }
    }
}

static void PrintPrompt() // 提示符
{
    GetUserName();
    GetHostName();
    GetCwdName();
    printf("[%s@%s %s]# ", username, hostname, cwd);
    fflush(stdout);
}

static void GetCommandLine()
{
    if(fgets(commandline, sizeof(commandline), stdin) != NULL)
    {
        commandline[strlen(commandline) - 1] = 0;
        //printf("debug: %s\n", commandline);
    }
}

static void ParseCommandLine()
{
    // 清空
    argc = 0;
    memset(argv, 0, sizeof(argv));
    // 判空
    if(strlen(commandline) == 0)
        return;
    // 解析
    argv[argc] = strtok(commandline, sep);
    while((argv[++argc] = strtok(NULL, sep)));

    //printf("argc: %d\n", argc);
    //int i = 0;
    //for(; argv[i]; i++)
    //{
    //    printf("argv[%d]: %s\n", i, argv[i]);
    //}
}

void Execute()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return;
    }
    else if(id == 0)
    {
        // child
        // 程序替换
        execvp(argv[0], argv);
        exit(1);
    }
    else{
        // father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        (void)rid;
        lastcode = WEXITSTATUS(status);
    }
}

// 1: yes
// 0: no,普通命令让后续执行
int CheckBuiltinAndExecute()
{
    int ret = 0;
    if(strcmp(argv[0], "cd") == 0)
    {
        // 内建命令
        ret = 1;
        if(argc == 2)
        {
            chdir(argv[1]);
        }
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        ret = 1;
        if(argc == 2)
        {
            if(argv[1][0] == '$')
            {
                if(strcmp(argv[1], "$?") == 0)
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else 
                {
                    // env
                }
            }
            else 
            {
                printf("%s\n", argv[1]);
            }
        }
    }
    else if(strcmp(argv[0], "env") == 0)
    {
        ret = 1;
        int i = 0;
        for(; i < envc; i++)
        {
            printf("%s\n", _environ[i]);
        }
    }
    else if(strcmp(argv[0], "export") == 0)
    {
        ret = 1;
        if(argc == 2)
        {
            char *mem = (char*)malloc(strlen(argv[1])+1);
            strcpy(mem,argv[1]);
            _environ[envc++] = mem;
            _environ[envc] = NULL;
        }
    }

    return ret;
}

static void LoadEnv()
{
    extern char **environ;
    for(envc = 0; environ[envc]; envc++)
    {
        _environ[envc] = environ[envc];
    }
    _environ[envc] = NULL;
}

void Bash()
{
    // 环境变量相关,由bash维护,从系统配置文件读,我们从系统bash拷贝
    static char *env[64];
    _environ = env;

    // 获取环境变量
    LoadEnv();

    while(1)
    {
        // 第一步:输出命令行
        PrintPrompt();

        // 第二步:等待用户输入,获取用户输入
        GetCommandLine();

        // 第三步:解析字符串
        ParseCommandLine();
        if(argc == 0)
            continue;
        // 第四步:内建命令
        if(CheckBuiltinAndExecute())
        {
            continue;
        }

        // 第五步:执行命令
            Execute();

    }
}

main.c

复制代码
#include "myshell.h"

int main()
{
    Bash();
    return 0;
}

myshell.h

复制代码
#include "myshell.h"

int main()
{
    Bash();
    return 0;
}
[xxx404@VM-16-10-centos test2_2]$ cat myshell.h
#pragma 

#include <stdio.h>

void Bash();

本章完。

相关推荐
袁袁袁袁满17 分钟前
Linux云服务器如何判断系统是否发生过异常断电?
linux·运维·服务器
一只鹿鹿鹿20 分钟前
智慧水利一体化建设方案
大数据·运维·开发语言·数据库·物联网
学不完的27 分钟前
haproxy
linux·运维·https·负载均衡·haproxy
LCG元1 小时前
STM32MP1边缘网关:Linux系统下Modbus转MQTT协议转换实战
linux·stm32·嵌入式硬件
cyber_两只龙宝2 小时前
Nginx--企业高性能web服务器高级配置详解
linux·运维·nginx·云原生
如若1233 小时前
AutoDL云服务器 NVIDIA 570驱动 EGL渲染修复全记录
运维·服务器·python
i建模3 小时前
Omarchy设置防火墙
linux·运维
晚秋大魔王4 小时前
泰拉瑞亚手机版服务器部署
运维·服务器·泰拉瑞亚
敲代码的哈吉蜂4 小时前
高可用集群Keepalived
运维·服务器·网络·数据库
S-码农4 小时前
Linux ——条件变量
linux·开发语言