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();

本章完。

相关推荐
何以不说话2 小时前
堡垒机jumpserver
运维·sql
开开心心就好2 小时前
开源免费高速看图工具,支持漫画大图秒开
linux·运维·服务器·安全·ruby·symfony·1024程序员节
D11_2 小时前
[特殊字符]️ 5379工具箱 - 全部网站链接汇总
服务器·百度·阿里云·typescript·编辑器
花间相见2 小时前
【AI开发】—— Ubuntu系统使用nvm管理Node.js多版本,版本切换一键搞定(实操完整版)
linux·ubuntu·node.js
PPPPPaPeR.2 小时前
从零实现一个简易 Shell:理解 Linux 进程与命令执行
linux·开发语言·c++
Byte不洛3 小时前
Linux 多线程:生产者消费者模型、阻塞队列与条件变量详解
linux·多线程·并发编程·pthread·生产者消费者模型
小Pawn爷3 小时前
13.virtualbox安装ubuntu
linux·运维·ubuntu
乾元3 小时前
暗网情报:自动化采集与情感分析在威胁狩猎中的应用
运维·网络·人工智能·深度学习·安全·架构·自动化
VekiSon3 小时前
Linux内核驱动——Ubuntu 网络启动环境配置与操作
linux·arm开发·嵌入式硬件·ubuntu