自主Shell命令行解释器

news2025/2/4 15:06:07

什么是命令行

我们一直使用的"ls","cd","pwd","mkdir"等命令,都是在命令行上输入的,我们之前对于命令行的理解:

命令行是干啥的?是为我们做命令行解释的。

命令行这个东西实际上是我们登入时,系统默认创建的,我们最终输入的命令都交给他了。 

感性理解

可是,在我们看来,这个命令行究竟该如何理解呢?

想象一下,你走进一家餐厅,坐下来后,你会对服务员说:“请给我来一份牛排,五分熟,加一些胡椒。” 这里的“你”就是用户,“服务员”就是命令行界面(Command Line Interface,简称CLI),而“牛排,五分熟,加一些胡椒”就是你通过命令行输入的命令。

  1. 命令行界面(CLI)

    • 命令行界面就像餐厅里的服务员,它等待你告诉它要做什么。在计算机中,命令行界面是一个程序,它等待你输入命令来告诉计算机执行什么任务。

  2. 输入命令

    • 就像你对服务员说“来一份牛排”,你在命令行中输入命令,比如ls(列出目录内容)、cd(改变目录)、pwd(显示当前目录)或mkdir(创建新目录)。

  3. 执行命令

    • 命令行界面接收你的命令后,就像服务员去厨房告诉厨师你的点餐一样,命令行界面会将你的命令发送给操作系统,操作系统会找到相应的程序来执行这个命令。

  4. 反馈结果

    • 执行完毕后,就像服务员把牛排端给你一样,命令行界面会显示命令的执行结果,比如列出文件列表、显示当前路径或确认目录创建成功。

  5. 交互性

    • 你可以不断地对命令行界面说话(输入命令),它也会不断地回应你(显示结果),这种一问一答的方式就是命令行界面的交互性。

  6. 灵活性

    • 就像你可以告诉服务员你的特殊要求一样,命令行界面允许你通过组合不同的命令和选项来完成复杂的任务。

所以,命令行界面就像是你的个人助理,你通过它来告诉计算机你想要做什么,它则负责将你的命令翻译成计算机可以理解和执行的指令。这种方式虽然看起来不如图形界面直观,但它非常强大和灵活,可以让有经验的用户快速、高效地完成各种任务。

这里我们只能感性的理解命令行,下面,我们来深刻理解一下,到底什么是命令行:我们用实现一个自主的Shell来理解!!!

实现原理

考虑下面这个与 shell 典型的互动:

1 [root@localhost epoll]# ls
2 client.cpp readme.md server.cpp utility.h
3 [root@localhost epoll]# ps
4 PID TTY TIME CMD
5 3451 pts/0 00:00:00 bash
6 3514 pts/0 00:00:00 ps

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

所以要写一个 shell,需要循环以下过程:

  1. 获取命令行

  2. 解析命令行

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

  4. 替换子进程(execvp)

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

根据这些思路,和我们前面的学的技术,就可以自己来实现一个 shell 了。

深刻理解(实现)

命令行其实就是一门语言,里面有许多词法语法分析,但是我们今天并不考虑。我们将最基本的谈谈,就足以理解了。

在我们用户登入的时候,会出现一个类似如下的命令行:

lfz@hcss-ecs-ff0f:~/lesson/lesson17/myshell$

我们发现其组成是:用户名+@+主机名+当前工作目录,这些我们如果想要的话是都可以通过系统调用进行获取的,我们今天暂时不用这些系统调用,我们只要在Shell启动的时候,打印出类似的字符串就OK了:

这时候,我们需要写基本的几个接口:

获取用户名的函数:(使用环境变量获取)

//获取用户名
const char *GetUserName()
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

获取主机名的函数:

//获取主机名
const char *GetHostName()
{
    const char* hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

获取当前路径的函数:

//获取当前路径
const char *GetPwd()
{
    const char* pwd = getenv("PWD");
    return pwd == NULL ? "None" : pwd;
}

我们发现打印出来的和真正命令行的有点不一样,因为这是Ubuntu系统,我就临时export了一个HOSTNAME,而且当前路径是完整显示,这个问题我们稍后解决,主要是为了便于和真正的命令行区分。 

对于真正的命令行,接着会开在这里,这是在等待用户输入:

我们在命令行输入ls -a -l其实本质是被命令行读取成字符串:"ls -a -l" ,我们可以定义一个字符数组:

#define COMMAND_SIZE 1024


char commandline[COMMAND_SIZE];

我们接下来采用C/C++混编,我们像字符串我们不使用STL当中的数据结构(string),因为我们后面用到的许多接口都是系统调用,而系统调用都是C式的系统调用,如果我们用纯C++去写很多代码时,就可能要适配一下C接口,比如说这个C接口是指针,上层使用的是string,那么这时候,这个接口就需要适配一下,而且有许多格式适配的要求在里面。

我们对于命令行读取,可以使用fgets实现:

fgets 是 C 标准库中的一个函数,用于从指定的文件流中读取一行文本。它将读取的文本存储在用户提供的字符数组中,是文件输入操作中非常常用的函数。

char *fgets(char *str, int n, FILE *stream);
//ls -a -l->"ls -a -l"
    char *c = fgets(commandline,sizeof(commandline),stdin);
    if(c==NULL)
    {
        //失败
        return 1;
    }
    //获取成功
    printf("echo %s\n",commandline);//用于回显,测试我们写的有无bug

我们测试发现: 

这里应该注意的是:我们printf的时候加了一个"\n",那么就不应该有空行的,不然不加"\n"的话,那么下一条命令行应该是紧紧跟在"-al"后面的,这是因为:我们进行命令行输入的时候,按了回车键,我们不想让整个命令行带回车,我们可以将字符数组的最后一个元素设置为0:

commandline[strlen(commandline)-1]=0;

清理"\n": 

这里没有strlen为0越界的情况,因为我们起码是要按一次回车键的,strlen最少是1的,这个问题我们不需要担心!!!

接下来,我们往优化的方向去实现与修改: 

我们先来认识一个C函数:

snprintf 是 C 语言中一个非常有用的函数,用于将格式化的数据写入字符串,并且可以指定最大写入长度,从而避免缓冲区溢出的安全风险。它是 sprintf 函数的安全替代品。

int snprintf(char *str, size_t size, const char *format, ...);

参数:

  • str:指向字符数组的指针,用于存储生成的字符串。

  • size:指定 str 数组的最大长度,包括结尾的空字符 \0

  • format:格式字符串,指明了后续参数如何格式化。

  • ...:可变参数,根据 format 字符串中的格式说明符,提供相应的数据。

返回值:

  • 成功时,snprintf 返回写入字符串中的字符数,不包括结尾的空字符 \0

  • 如果输出被截断(即 size 小于所需空间),则返回一个大于或等于 size 的值。

  • 如果发生错误,返回一个负值。

我们可以进行不同功能的分开实现,而且Shell是一个死循环,不停的在等待用户输入

1.输出命令行提示符:

//命令行格式
#define FORMAT "[%s@%s %s]# "

//初始化命令行
bool MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
    return true;
}

//打印命令行提示符
void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}
//1.输出命令行提示符
        PrintCommandPrompt();

2.获取用户输入的命令:

我们可以使用Ctrl+删除键来删除已经输入的数据!!!

//用户输入是否成功
bool GetCommandLine(char *out, int size)
{
    //ls -a -l->"ls -a -l"
    char *c = fgets(out, size, stdin);
    if (c == NULL)
    {
        //失败
        return false;
    }
    //获取成功
    out[strlen(out) - 1] = 0;
    if (strlen(out) == 1)
    {
        return false;
    }
    return true;
}
//2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if (!GetCommandLine(commandline, COMMAND_SIZE))
        {
            continue; //输入有问题就重新输入
        }

分享一下我的查bug的痛苦,就是下面这个,大家以后也可以多多注意一下:


main 函数中的 if(!GetCommandLine(commandline,COMMAND_SIZE));

这里的分号 ; 会导致 if 语句立即结束,不会执行 continue。修改为 if(!GetCommandLine(commandline,COMMAND_SIZE)) continue;


3.命令行分析"ls -a -l"->"ls" "-a" "-l":

为的是未来要使用程序替换,这种形式的参数,方便调用!!!

我们将一个字符串进行拆分有很多做法,我们使用比较原生的方式,首先,我们要拆分成什么形式?我们拆分成多个元素后,又改如何快速的找到对应的元素呢?找到每一个元素?

我们可以定义一个全局的g_argv[ ](全局的默认是初始化为0的),我们就为我们的代码提供了一个全局的命令行参数表,我们知道,Shell默认要获取用户的输入,然后将用户的输入的信息构建成一张命令行参数表,这张表是在Shell内部维护的。我们就可以通过这种方式实现。(就是将一个字符串变成一个指针数组):

我们可以使用一个函数接口:

strtok 是 C 语言标准库中的一个函数,用于分割字符串。它搜索字符串,找出由分隔符(也称为“delimiters”)分隔的部分,并返回指向下一个“token”(分隔的部分)的指针。strtok 函数通常用于将一行文本分解成单独的单词或命令参数。

char *strtok(char *str, const char *delim);

第一次截取了ls,而且strtok只能切一次,切成功的话,返回下一个字串的起始地址,要切历史字串的话,需要传NULL/nullptr,而不是继续commandline!!!

//命令行分析
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc = 0;
    //"ls -a -l"->"ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline, SEP); // 截取了ls,strtok只能切一次,切成功的话,返回下一个字串的起始地址
    while (g_argv[g_argc++] = strtok(NULL, SEP)); // 最后会切到NULL

    // 修正:确保g_argc在最后一次分割后不递增
    g_argc--;
   

    return g_argv>0 ? true : false;

}

在内部定义宏并不是说这个宏是局部的,而是为了:所见即所得,就这个接口自己要用!!!只是一种代码风格。 

//3.命令行分析"ls -a -l"->"ls" "-a" "-l",未来要使用程序替换,这种形式的参数,方便调用!!!
        if(!CommandParse(commandline))
        {
            continue;//如果解析失败,不执行以下代码了,解析成功才可执行!!!
        }

4.执行命令 

我们不想让自己去执行任务,因为我自己也有要执行的代码,这时候,我们要创建子进程来帮忙了。我们创建出来派他做两件事:

  1. 做我的一部分事情;
  2. 去执行全新的程序(进程替换)。 

我的事情已经ok了,这时候子进程就去执行新的程序吧:exec()类接口调用。

//执行命令
int Execute()
{
    pid_t id = fork();//创建子进程
    if(id == 0)
    {
        //child
        execvp(g_argv[0],g_argv);
        exit(1);
    }
    //father
    pid_t rid = waitpid(id,NULL,0);
    (void)rid;//rid使用一下,让编译器不报警
    return 0;
}
//4.执行命令
        Execute();

现在,我们就可以完成命令的执行了!!!

我们现在来完善上面的路径问题:(这里我们使用C++接口)

// / | /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH)return SLASH;
    auto pos = dir.rfind(SLASH);//从后向前找
    if(pos == std::string::npos)return "BUG?";
    return dir.substr(pos+1);//+1是不想要体现"/"
}

//初始化命令行
bool MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
    return true;
}

这里还是体现指针的位置要认真注意。

4.1内建命令

上面只是一个补充,接下来,我们执行该程序发现,我们"cd ..",并没有达到我们预期的效果:

因为目前说有的命令都是子进程去执行的,所以我们执行"cd .."时,是子进程把自己的pwd出路径改了,父进程没有改呀!!!

我们在真正的命令行当中cd执行后,更改的是父进程bash的路径,往后再创建其他子进程,就会在新路径下运行了,因为所以的子进程的PCB都是拷贝自父进程的!!!所以遇到cd这样的命令,是不能够让子进程去执行的,而是要让父进程自己亲自执行,自己亲自执行之后,把自己的路径切掉,不要再创建子进程了,这种命令,我们称之为:内建命令。  

在类 Unix 系统中,像 cd(更改当前目录)、exit(退出 shell)、set(设置环境变量)等命令通常不通过创建子进程来执行,而是直接在当前 shell 进程中执行。这些命令被称为“内建命令”(built-in commands)或“shell 内建”(shell builtins)。

内建命令的特点:

  1. 直接执行:内建命令直接在 shell 进程中执行,不需要创建新的子进程。

  2. 修改 shell 状态:这些命令通常修改 shell 的内部状态,如当前目录、环境变量、别名等。

  3. 效率:由于不需要创建新的进程,执行效率较高。 

为什么需要内建命令:

  1. 状态修改:某些命令需要修改 shell 自身的状态(如 cd 修改当前目录),这些状态是进程级的,不能通过子进程来修改父进程的状态。

  2. 避免资源浪费:如果每个命令都创建一个子进程,会导致大量的进程创建和销毁,消耗系统资源。内建命令避免了这种资源浪费。

  3. 安全性:直接在 shell 进程中执行可以避免潜在的安全问题,如子进程的权限提升等。

所以我们接下来要做的是根据我们提取,解析出来的命令,我们要判断这个命令是否是内建命令,所以我们在shell当中应该存在一个接口:检测并处理内建命令:

实现内建命令:

在实现一个简单的命令行解释器时,可以通过以下步骤来处理内建命令:

  1. 命令解析:将用户输入的命令行解析成命令和参数。(已经在g_argv表中保存了)

  2. 内建命令检查:检查解析出的命令是否为内建命令。

  3. 执行内建命令:如果是内建命令,直接在当前进程中执行相应的操作

  4. 执行外部命令:如果不是内建命令,通过 forkexec 创建的子进程来执行外部程序。

我们要怎么才能够让父进程亲自执行呢,所以我们要来认识一个接口:

在C语言中,chdir()函数用于改变当前进程的工作目录。它是一个标准的POSIX函数,定义在<unistd.h>头文件中。

#include <unistd.h>

int chdir(const char *path);
  • path:目标目录的路径,可以是绝对路径(如/home/user/documents),也可以是相对路径(如../projects)。

我们就可以简单实现cd命令:

//检测并处理内建命令
bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        if(g_argc == 1)
        {
            std::string home = GetHome();
            if(home.empty()) return true;
            chdir(home.c_str());
        }
        else
        {
            std::string where = g_argv[1];
            if(where == "-")
            {
                // Todu
            }
            else if(where == "~")
            {
                // Todu
            }
            else
            {
                chdir(where.c_str());
            }
        }
        return true;
    }
    return false;
}

我们执行之后发现:

我们是实现了cd的功能,但是我们的命令行显示的路径并没有跟着变化!!!

我们cd命令执行完毕后,程序进入循环,然后进行命令行构建和打印,构建命令行打印的时候,对应的输出命令行是需要获取用户名/主机名/当前工作路径。

这时候,我们应该思考一个问题: 当一个进程他的当前工作路径发生变化的时候,那么系统当中还存在一个环境变量叫做PWD,所以是我的进程的当前工作路径先变,环境变量再变,还是环境变量先变,我的当前工作路径再变呢?我们需要搞清楚顺序!!!

其实是我们的进程的路径先变了,然后系统/我的Shell再把环境变量更新了,更新工作一般由Shell来做,但是我们现在的代码还没有做这个工作(也就是文章到这里我们自主的Shell还没有更新环境变量的能力手段),所以我们发现我们当前进程的工作路径他发生变化了,但是环境变量却没有变化,所以我们每次从环境变量获取,都是获取到老的路径,所以我们采用环境变量去获取路径的GetPwd()就不太好了,这时候我们应该使用系统调用了:

在C语言中,getcwd()函数用于获取当前工作目录(Current Working Directory,CWD)。它是一个标准的POSIX函数,定义在<unistd.h>头文件中。通过getcwd(),你可以获取当前进程的工作目录路径,并将其存储到一个字符串中。

#include <unistd.h>

char *getcwd(char *buf, size_t size);
  • buf:一个字符数组,用于存储当前工作目录的路径。

  • sizebuf的大小(以字节为单位),确保足够存储路径字符串。

const char* pwd = getcwd(cwd,sizeof(cwd));

//cwd是我们为了测试在全局定义的:char pwd[1024]

我们测试就可以发现:

虽然我们解决了命令行的路径问题,但是我们执行env的时候,发现环境变量的PWD并没有随着发生改变,真正的Shell是会发生改变的,我们可以调整以下代码:

//获取当前路径
const char *GetPwd()
{
    //const char* pwd = getenv("PWD");
    const char* pwd = getcwd(cwd,sizeof(cwd));
    if(pwd != nullptr)
    {
        snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",pwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

接下来,我们可以优化:命令封装,将cd命令封装成一个接口:

//command
bool Cd()
{
    if(g_argc == 1)
        {
            std::string home = GetHome();
            if(home.empty()) return true;
            chdir(home.c_str());
        }
        else
        {
            std::string where = g_argv[1];
            if(where == "-")
            {
                // Todu
            }
            else if(where == "~")
            {
                // Todu
            }
            else
            {
                chdir(where.c_str());
            }
        }
        return true;
}

我们之前有认识到:

echo $?

用于显示最近一个程序执行后的退出码。我们内建命令不考虑,因为他只能是成功的,而我们一个命令是否执行成功,我们要看Execute()执行的指定命令,而且我们在真正的Shell中使用echo命令可以打印环境变量,还有字符串,所以echo本质上也是属于一个内建命令。这时,我们应该要清楚:

在我们Linux系统当中,有的命令即是内建,又是外置命令,这么理解吧:这个命令被实现了两次Shell自己内部一份,应用级的系统层面上也有一份,就像cd,echo,我们可以which命令可以找到外置命令的路径,但无法找到内建命令的路径: 

$ which echo
$ which /bin/echo
/bin/echo

为什么要这样呢?其实磁盘上的这种内建命令的镜像(上面这种)跟我们没关系,我们未来写代码的时候,还需要让我们的系统支持一个东西叫做Shell脚本,所以他为了能够支持Shell脚本,很多命令在磁盘上要有对应的二进程的文件存在,这样Shell脚本在写的时候就会让子进程执行任何命令,不然只有Shell自己认识Shell命令,磁盘当中没有对应的命令,比如说cd命令的话,那么脚本的子进程就无法执行这样的cd命令了。

某些命令既作为内建命令又作为外置命令存在,是为了在性能优化和脚本兼容性之间取得平衡。内建命令提供了快速执行和对Shell内部状态的直接操作,而外置命令则确保了脚本在子进程中的可执行性和跨平台兼容性。这种设计使得Shell脚本能够更高效地运行,同时保持了系统的灵活性和通用性。

今天我们不做区分,我们写的是Shell,我们认为,我们今天要执行的cd/echo/....都是内建命令的用法,我们先来退出码的实现:对Execute()接口的丰富:

//执行命令
int Execute()
{
    pid_t id = fork();//创建子进程
    if(id == 0)
    {
        //child
        execvp(g_argv[0],g_argv);
        exit(1);
    }
    int status = 0;
    //father
    pid_t rid = waitpid(id,&status,0);
    //(void)rid;//rid使用一下,让编译器不报警
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);//拿到退出码
    }
    return 0;
}

 除了echo,我们还有一个export导环境变量,export内建命令接口的实现需要实现,说到环境变量,我们知道Shell内部维护了两张表:命令行参数表,还有环境变量表:

Shell环境变量表是从父Shell继承而来的。在Unix和Linux系统中,环境变量是一个全局的变量集合,用于存储系统和用户配置信息,例如路径、用户主目录、语言设置等。当一个新进程(如子Shell)被创建时,它会从父进程(父Shell)继承环境变量的副本。

Shell启动的时候,从系统中获取环境变量,但是我们今天自己的shell是没有这一套的,没有从相关配置去读取,我做不到,因为那个配置文件是Shell脚本,这是要求我们新学一门语言的!!! 

 I can't do it !!!

我们的自主实现的shell的环境变量信息应该从父shell统一来。

我们要初始化我们的环境变量表就应该将系统的环境变量表拿出来,使用extern char** environ;

打开我们的shell程序,应该初始化,就是将父shell的环境变量表继承下来: (我们真正的Shell是通过配置文件来的。)

//环境变量表
char *g_env[MAXENVP];
int g_envs = 0;//环境变量的个数

 

//获取环境变量表
void InitEnv()
{
    extern char** environ;
    memset(g_env,0,sizeof(g_env));

    //本来要从配置文件来的,今天直接从父Shell来:
    //1.获取环境变量
    for(int i=0;environ[i];i++)
    {
        //1.1申请空间
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i],environ[i]);
        g_envs++;
    }
    g_env[g_envs++] = "HAHA=for_test";
    g_env[g_envs] = NULL;

    //2.导成环境变量
    for(int i=0;g_env[i];i++)
    {
        putenv(g_env[i]);
    }
    //environ是C语言提供的全局变量,这时候就可以指向父进程的环境变量表了,一旦fork之后,子进程就照样拿到全局变量environ,就可以把所有的环境变量给拿到了
    environ = g_env;
}

export命令用于将变量从当前Shell的局部变量提升为环境变量。这些环境变量会被存储在全局的环境变量表中,从而可以被当前Shell及其所有子进程继承。

在我们的自定义Shell中,环境变量表是全局的。这意味着所有在当前Shell中通过export命令设置的环境变量都会被存储在这个全局表中。这个全局表是通过environ变量来管理的。

environ是C语言标准库提供的一个全局变量,它是一个指向字符串数组的指针,每个字符串表示一个环境变量(格式为KEY=VALUE)。在自定义Shell中,我们可以将environ指向我们的全局环境变量表,这样就可以通过标准的C库函数(如getenv()putenv()等)来操作环境变量。

当父Shell调用fork()创建子进程时,子进程会继承父进程的环境变量表。这是因为fork()会复制父进程的内存空间(包括全局变量environ),因此子进程会自动获得父进程的环境变量表。

在子进程中,environ变量仍然指向父进程的环境变量表。因此,子进程可以通过标准的C库函数访问和使用这些环境变量。这种继承机制确保了子进程能够使用父进程设置的环境变量,从而保证了程序的正确运行。

我们的shell还需要支持重定向,还有管道的实现,这个我们等学的知识面更广了再来谈谈。

我们还有一个alias:

在Linux中,alias 是一个非常有用的Shell特性,用于为命令创建别名(Aliases)。别名允许用户为复杂的命令或命令组合定义一个简短的名称,从而简化命令行操作。别名通常用于提高工作效率,尤其是当需要频繁执行某些命令时。

比如说:" ls -l "就是被映射为" ll ",我们对命令起别名是使用alias:

alias 别名='命令'

这里的alias也是一个内建命令,我们可以使用hash表(别名映射表)来实现:

//别名映射表
std::unordered_map<std::string, std::string> alias_list;

更多的功能实现我们可以自己实现接口添加,以下是本篇的完整代码:

源代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <unordered_map> // hash

#define COMMAND_SIZE 1024

// 命令行格式
#define FORMAT "[%s@%s %s]# "

// 下面是Shell定义的全局数据

#define MAXARGC 128
#define MAXENVP 100

// 命令行参数/参数表
char *g_argv[MAXARGC];
int g_argc = 0;

// 环境变量表
char *g_env[MAXENVP];
int g_envs = 0; // 环境变量的个数

// 别名映射表
std::unordered_map<std::string, std::string> alias_list;

// last exit code
int lastcode = 0;

// for test
char cwd[1024];
char cwdenv[1024];

// last_cwd 用于记录上一次的工作目录
char last_cwd[1024] = {0};

// 获取用户名
const char *GetUserName() {
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

// 获取主机名
const char *GetHostName() {
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

// 获取当前路径
const char *GetPwd() {
    const char *pwd = getcwd(cwd, sizeof(cwd));
    if (pwd != nullptr) {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", pwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

// 获取家目录
const char *GetHome() {
    const char *home = getenv("HOME");
    return home == NULL ? "None" : home;
}

// 获取环境变量表
void InitEnv() {
    extern char **environ;
    memset(g_env, 0, sizeof(g_env));

    // 从父Shell获取环境变量
    for (int i = 0; environ[i]; i++) {
        g_env[i] = strdup(environ[i]); // 使用 strdup 复制字符串
        g_envs++;
    }
    g_env[g_envs++] = strdup("HAHA=for_test"); // 添加一个测试环境变量
    g_env[g_envs] = NULL;

    // 将环境变量表赋给 environ
    environ = g_env;
}

// cd 命令
bool Cd() {
    if (g_argc == 1) {
        // 如果没有指定路径,默认切换到家目录
        std::string home = GetHome();
        if (home.empty()) {
            std::cerr << "cd: HOME not set" << std::endl;
            return false;
        }
        chdir(home.c_str());
    } else {
        std::string where = g_argv[1];
        if (where == "~") {
            // 切换到家目录
            std::string home = GetHome();
            if (home.empty()) {
                std::cerr << "cd: HOME not set" << std::endl;
                return false;
            }
            chdir(home.c_str());
        } else if (where == "-") {
            // 切换到上一次的工作目录
            if (last_cwd[0] != '\0') {
                chdir(last_cwd);
            } else {
                std::cerr << "cd: no previous directory saved" << std::endl;
                return false;
            }
        } else {
            // 切换到指定目录
            if (chdir(where.c_str()) != 0) {
                std::cerr << "cd: " << where << ": No such file or directory" << std::endl;
                return false;
            }
        }
    }

    // 更新 last_cwd 为当前目录
    getcwd(last_cwd, sizeof(last_cwd));
    return true;
}

// echo 命令
bool Echo() {
    if (g_argc == 2) {
        std::string opt = g_argv[1];
        if (opt == "$?") {
            std::cout << lastcode << std::endl;
            lastcode = 0; // 清0
        } else if (opt[0] == '$') {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if (env_value) {
                std::cout << env_value << std::endl;
            } else {
                std::cout << std::endl;
            }
        } else {
            std::cout << opt << std::endl;
        }
    }
    return true;
}

// export 命令
bool Export() {
    if (g_argc < 2) {
        std::cerr << "export: not enough arguments" << std::endl;
        return true;
    }
    for (int i = 1; i < g_argc; ++i) {
        std::string env_str = g_argv[i];
        size_t equal_pos = env_str.find('=');
        if (equal_pos == std::string::npos) {
            std::cerr << "export: invalid variable name" << std::endl;
            return true;
        }
        std::string key = env_str.substr(0, equal_pos);
        std::string value = env_str.substr(equal_pos + 1);
        char *env_entry = new char[key.size() + value.size() + 2];
        sprintf(env_entry, "%s=%s", key.c_str(), value.c_str());
        putenv(env_entry);
        g_env[g_envs++] = env_entry;
    }
    return true;
}

// alias 命令
bool Alias() {
    if (g_argc == 1) {
        // 显示所有别名
        for (const auto &entry : alias_list) {
            std::cout << entry.first << "=" << entry.second << std::endl;
        }
    } else if (g_argc == 2) {
        // 删除别名
        std::string nickname = g_argv[1];
        if (alias_list.find(nickname) != alias_list.end()) {
            alias_list.erase(nickname);
        } else {
            std::cerr << "alias: " << nickname << ": not found" << std::endl;
        }
    } else if (g_argc == 3) {
        // 添加别名
        std::string nickname = g_argv[1];
        std::string target = g_argv[2];
        alias_list[nickname] = target;
    } else {
        std::cerr << "alias: invalid arguments" << std::endl;
    }
    return true;
}

// 获取当前路径的父目录
std::string DirName(const char *pwd) {
    std::string dir = pwd;
    if (dir == "/") return "/";
    auto pos = dir.rfind('/');
    if (pos == std::string::npos) return "/";
    return dir.substr(0, pos);
}

// 初始化命令行
bool MakeCommandLine(char cmd_prompt[], int size) {
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
    return true;
}

// 打印命令行提示符
void PrintCommandPrompt() {
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}

// 获取用户输入的命令
bool GetCommandLine(char *out, int size) {
    char *c = fgets(out, size, stdin);
    if (c == NULL) {
        return false;
    }
    out[strcspn(out, "\n")] = 0; // 去掉换行符
    if (strlen(out) == 0) {
        return false;
    }
    return true;
}

// 命令行分析
bool CommandParse(char *commandline) {
    g_argc = 0;
    g_argv[g_argc++] = strtok(commandline, " ");
    while ((g_argv[g_argc++] = strtok(NULL, " ")));
    g_argc--;

    // 检查别名
    std::string cmd = g_argv[0];
    auto it = alias_list.find(cmd);
    if (it != alias_list.end()) {
        // 如果是别名,替换为实际命令
        std::string new_cmd = it->second;
        delete[] g_argv[0]; // 释放旧的命令
        g_argv[0] = new char[new_cmd.size() + 1];
        strcpy(g_argv[0], new_cmd.c_str());
    }

    return g_argc > 0;
}

// 打印解析后的命令和参数
void PrintArgv() {
    for (int i = 0; i < g_argc; ++i) {
        printf("g_argv[%d]->%s\n", i, g_argv[i]);
    }
}

// 检测并处理内建命令
bool CheckAndExecBuiltin() {
    std::string cmd = g_argv[0];
    if (cmd == "cd") {
        return Cd();
    } else if (cmd == "echo") {
        return Echo();
    } else if (cmd == "export") {
        return Export();
    } else if (cmd == "alias") {
        return Alias();
    }
    return false;
}
// 执行命令
int Execute() {
    pid_t id = fork();
    if (id == 0) {
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0) {
        lastcode = WEXITSTATUS(status);
    }
    return 0;
}

// 清理函数
void Clear() {
    for (int i = 0; i < g_envs; ++i) {
        free(g_env[i]);
    }
}

int main()
{
    //Shell启动的时候,从系统中获取环境变量
    InitEnv();

    while (1) //命令行不会停止,要不断获得用户输入
    {
        //1.输出命令行提示符
        PrintCommandPrompt();

        //2.获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if (!GetCommandLine(commandline, COMMAND_SIZE))
        {
            continue; //输入有问题就重新输入
        }

        //3.命令行分析"ls -a -l"->"ls" "-a" "-l",未来要使用程序替换,这种形式的参数,方便调用!!!
        if(!CommandParse(commandline))
        {
            continue;//如果解析失败,不执行以下代码了,解析成功才可执行!!!
        }

        //sub_4.检测并处理内建命令
        if(CheckAndExecBuiltin())
        {
            continue;
        }

        //4.执行命令
        Execute();
    }
    //清理函数
    Clear();
    return 0;
}





// #include <iostream>
// #include <cstdio>
// #include <cstring>
// #include <cstdlib>
// #include <unistd.h>
// #include <sys/types.h>
// #include <sys/wait.h>
// #include <string>
// #include <unistd.h>
// #include <unordered_map>//hash

// #define COMMAND_SIZE 1024

// //命令行格式
// #define FORMAT "[%s@%s %s]# "

// //下面是Shell定义的全局数据

// #define MAXARGC 128
// #define MAXENVP 100

// //命令行参数/参数表
// char *g_argv[MAXARGC];
// int g_argc = 0;

// //环境变量表
// char *g_env[MAXENVP];
// int g_envs = 0;//环境变量的个数

// //别名映射表
// std::unordered_map<std::string, std::string> alias_list;

// //last exit code
// int lastcode = 0;

// //for test
// char cwd[1024];
// char cwdenv[1024];


// //获取用户名
// const char *GetUserName()
// {
//     const char *name = getenv("USER");
//     return name == NULL ? "None" : name;
// }

// //获取主机名
// const char *GetHostName()
// {   
//     const char* hostname = getenv("HOSTNAME");
//     return hostname == NULL ? "None" : hostname;
// }

// //获取当前路径
// const char *GetPwd()
// {
//     //const char* pwd = getenv("PWD");
//     const char* pwd = getcwd(cwd,sizeof(cwd));
//     if(pwd != nullptr)
//     {
//         snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",pwd);
//         putenv(cwdenv);
//     }
//     return pwd == NULL ? "None" : pwd;
// }

// //获取家目录
// const char*GetHome()
// {
//     const char* home = getenv("HOME");
//     return home == NULL ? "None" : home;
// }

// //获取环境变量表
// void InitEnv()
// {
//     extern char** environ;
//     memset(g_env,0,sizeof(g_env));

//     //本来要从配置文件来的,今天直接从父Shell来:
//     //1.获取环境变量
//     for(int i=0;environ[i];i++)
//     {
//         //1.1申请空间
//         g_env[i] = (char*)malloc(strlen(environ[i])+1);
//         strcpy(g_env[i],environ[i]);
//         g_envs++;
//     }
//     g_env[g_envs++] = "HAHA=for_test";
//     g_env[g_envs] = NULL;

//     //2.导成环境变量
//     for(int i=0;g_env[i];i++)
//     {
//         putenv(g_env[i]);
//     }
//     //environ是C语言提供的全局变量,这时候就可以指向父进程的环境变量表了,一旦fork之后,子进程就照样拿到全局变量environ,就可以把所有的环境变量给拿到了
//     environ = g_env;
// }

// //command
// bool Cd()
// {
//     if(g_argc == 1)
//         {
//             std::string home = GetHome();
//             if(home.empty()) return true;
//             chdir(home.c_str());
//         }
//         else
//         {
//             std::string where = g_argv[1];
//             if (where == "~") 
//             {
//                 std::string home = GetHome();
//                 if (!home.empty())
//                 {
//                     chdir(home.c_str());
//                 }
//             } 
//             else if (where == "-") {
//             // 这里需要记录上一次的工作目录
//             // 假设有一个全局变量 `char* last_cwd` 用于存储上一次的工作目录
//             if (last_cwd != nullptr)
//             {
//                 chdir(last_cwd);
//             }
// }
//             else
//             {
//                 chdir(where.c_str());
//             }
//         }
//         return true;
// }

// bool Echo()
// {
//     if(g_argc == 2)
//         {
//             //echo "hello world"
//             //echo $?
//             //echo $PATH
//             std::string opt = g_argv[1];
//             if(opt == "$?")
//             {
//                 std::cout<<lastcode<<std::endl;
//                 lastcode = 0;//这里要注意清0!!!
//             }
//             else if(opt[0] == '$')
//             {
//                 std::string env_name = opt.substr(1);//去掉第一个字符剩下的就是环境变量的名字
//                 const char *env_value = getenv(env_name.c_str());
//                 if(env_value)//不为空
//                 {
//                     std::cout<<env_value<<std::endl;
//                 }
//             }
//             else
//             {
//                 std::cout<<opt<<std::endl;
//             }
//         }
//         return true;
// }

// bool Export()
// {
//     if (g_argc < 2) {
//         std::cerr << "export: not enough arguments" << std::endl;
//         return true;
//     }
//     for (int i = 1; i < g_argc; ++i) {
//         std::string env_str = g_argv[i];
//         size_t equal_pos = env_str.find('=');
//         if (equal_pos == std::string::npos) {
//             std::cerr << "export: invalid variable name" << std::endl;
//             return true;
//         }
//         std::string key = env_str.substr(0, equal_pos);
//         std::string value = env_str.substr(equal_pos + 1);
//         char* env_entry = new char[key.size() + value.size() + 2];
//         sprintf(env_entry, "%s=%s", key.c_str(), value.c_str());
//         putenv(env_entry);
//         g_env[g_envs++] = env_entry; // 将新环境变量添加到环境变量表
//     }
//     return true;
// }

// bool Alias()
// {
//     if (g_argc < 3) {
//         std::cerr << "alias: not enough arguments" << std::endl;
//         return true;
//     }
//     std::string nickname = g_argv[1];
//     std::string target = g_argv[2];
//     alias_list[nickname] = target; // 将别名映射添加到哈希表
//     return true;
// }

// // / | /a/b/c
// std::string DirName(const char *pwd)
// {
// #define SLASH "/"
//     std::string dir = pwd;
//     if(dir == SLASH)return SLASH;
//     auto pos = dir.rfind(SLASH);//从后向前找
//     if(pos == std::string::npos)return "BUG?";
//     return dir.substr(pos);//+1是不想要体现"/"
// }

// //初始化命令行
// bool MakeCommandLine(char cmd_prompt[], int size)
// {
//     snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
//     //snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());

//     return true;
// }

// //打印命令行提示符
// void PrintCommandPrompt()
// {
//     char prompt[COMMAND_SIZE];
//     MakeCommandLine(prompt, sizeof(prompt));
//     printf("%s", prompt);
//     fflush(stdout);
// }

// //用户输入是否成功
// bool GetCommandLine(char *out, int size)
// {
//     //ls -a -l->"ls -a -l"
//     char *c = fgets(out, size, stdin);
//     if (c == NULL)
//     {
//         //失败
//         return false;
//     }
//     //获取成功
//     out[strlen(out) - 1] = 0;
//     if (strlen(out) == 0)
//     {
//         return false;
//     }
//     return true;
// }

// //命令行分析
// bool CommandParse(char *commandline)
// {
// #define SEP " "
//     g_argc = 0;
//     //"ls -a -l"->"ls" "-a" "-l"
//     g_argv[g_argc++] = strtok(commandline, SEP); // 截取了ls,strtok只能切一次,切成功的话,返回下一个字串的起始地址
//     while (g_argv[g_argc++] = strtok(NULL, SEP)); // 最后会切到NULL

//     // 修正:确保g_argc在最后一次分割后不递增
//     g_argc--;
   

//     return g_argc>0 ? true : false;
// }

// //打印解析后的命令和参数
// void PrintArgv()
// {
//      for (int i = 0; i < g_argc; ++i) {
//             printf("g_argv[%d]->%s\n", i, g_argv[i]);
//         }
// }

// //检测并处理内建命令
// bool CheckAndExecBuiltin()
// {
//     std::string cmd = g_argv[0];
//     if(cmd == "cd")
//     {
//         return Cd();
//     }
//     else if(cmd == "echo")
//     {
//         return Echo();
//     }
//     else if(cmd == "export")
//     {
//         //找不到新增
//         //找到了覆盖
//         return Export();
//     }
//     else if(cmd == "alias")
//     {
//         return Alias();
//     }
//     return false;
// }

// //执行命令
// int Execute()
// {
//     pid_t id = fork();//创建子进程
//     if(id == 0)
//     {
//         //child
//         execvp(g_argv[0],g_argv);
//         exit(1);
//     }
//     int status = 0;
//     //father
//     pid_t rid = waitpid(id,&status,0);
//     //(void)rid;//rid使用一下,让编译器不报警
//     if(rid > 0)
//     {
//         lastcode = WEXITSTATUS(status);//拿到退出码
//     }
//     return 0;
// }

// //别名检查
// void CheakAlias()
// {
//     // 检测别名
//     for (int i = 0; i < g_argc; ++i) {
//         auto it = alias_list.find(g_argv[i]);
//         if (it != alias_list.end()) {
//             // 替换别名为实际命令
//             delete[] g_argv[i];
//             g_argv[i] = new char[it->second.size() + 1];
//             strcpy(g_argv[i], it->second.c_str());
//         }
//     }
// }

// //clean
// void Clear() {
//     for (int i = 0; i < g_envs; ++i) {
//         free(g_env[i]);
//     }
// }
// int main()
// {
//     //Shell启动的时候,从系统中获取环境变量
//     //但是我们今天自己的shell是没有这一套的,没有从相关配置去读取,我做不到,因为那个配置文件是Shell脚本,这是要求我们新学一门语言的!!!
//     //我们的自主实现的shell的环境变量信息应该从父shell统一来
//     InitEnv();

//     while (1) //命令行不会停止,要不断获得用户输入
//     {
//         //1.输出命令行提示符
//         PrintCommandPrompt();

//         //2.获取用户输入的命令
//         char commandline[COMMAND_SIZE];
//         if (!GetCommandLine(commandline, COMMAND_SIZE))
//         {
//             continue; //输入有问题就重新输入
//         }
//         //printf("echo %s\n", commandline); //用于回显,测试我们写的有无bug

//         //3.命令行分析"ls -a -l"->"ls" "-a" "-l",未来要使用程序替换,这种形式的参数,方便调用!!!
//         if(!CommandParse(commandline))
//         {
//             continue;//如果解析失败,不执行以下代码了,解析成功才可执行!!!
//         }

//         //PrintArgv();

//         //检测别名
//         // 检测别名
//         CheakAlias();

//         //sub_4.检测并处理内建命令
//         if(CheckAndExecBuiltin())
//         {
//             continue;
//         }

//         //4.执行命令
//         Execute();
//     }
//     //清理函数
//     Clear();
//     return 0;
// }


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2291840.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XCCL、NCCL、HCCL通信库

XCCL提供的基本能力 XCCL提供的基本能力 不同的XCCL 针对不同的网络拓扑&#xff0c;实现的是不同的优化算法的&#xff08;不同CCL库最大的区别就是这&#xff09; 不同CCL库还会根据自己的硬件、系统&#xff0c;在底层上面对一些相对应的改动&#xff1b; 但是对上的API接口…

【Redis】安装配置Redis超详细教程 / Linux版

Linux安装配置Redis超详细教程 安装redis依赖安装redis启动redis停止redisredis.conf常见配置设置redis为后台启动修改redis监听地址设置工作目录修改密码监听的端口号数据库数量设置redis最大内存设置日志文件设置redis开机自动启动 学习视频&#xff1a;黑马程序员Redis入门到…

【大数据技术】教程05:本机DataGrip远程连接虚拟机MySQL/Hive

本机DataGrip远程连接虚拟机MySQL/Hive datagrip-2024.3.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso写在前面 本文主要介绍如何使用本机的DataGrip连接虚拟机的MySQL数据库和Hive数据库,提高编程效率。 安装DataGrip 请按照以下步骤安装DataGrip软…

springboot 启动原理

目标&#xff1a; SpringBootApplication注解认识了解SpringBoot的启动流程 了解SpringFactoriesLoader对META-INF/spring.factories的反射加载认识AutoConfigurationImportSelector这个ImportSelector starter的认识和使用 目录 SpringBoot 启动原理SpringBootApplication 注…

llama.cpp GGUF 模型格式

llama.cpp GGUF 模型格式 1. Specification1.1. GGUF Naming Convention (命名规则)1.1.1. Validating Above Naming Convention 1.2. File Structure 2. Standardized key-value pairs2.1. General2.1.1. Required2.1.2. General metadata2.1.3. Source metadata 2.2. LLM2.2.…

使用Pytorch训练一个图像分类器

一、准备数据集 一般来说&#xff0c;当你不得不与图像、文本或者视频资料打交道时&#xff0c;会选择使用python的标准库将原始数据加载转化成numpy数组&#xff0c;甚至可以继续转换成torch.*Tensor。 对图片而言&#xff0c;可以使用Pillow库和OpenCV库对视频而言&#xf…

S4 HANA明确税金汇差科目(OBYY)

本文主要介绍在S4 HANA OP中明确税金汇差科目(OBYY)相关设置。具体请参照如下内容&#xff1a; 1. 明确税金汇差科目(OBYY) 以上配置点定义了在外币挂账时&#xff0c;当凭证抬头汇率和税金行项目汇率不一致时&#xff0c;造成的差异金额进入哪个科目。此类情况只发生在FB60/F…

深入理解linux中的文件(上)

1.前置知识&#xff1a; &#xff08;1&#xff09;文章 内容 属性 &#xff08;2&#xff09;访问文件之前&#xff0c;都必须打开它&#xff08;打开文件&#xff0c;等价于把文件加载到内存中&#xff09; 如果不打开文件&#xff0c;文件就在磁盘中 &#xff08;3&am…

Airflow:深入理解Apache Airflow Task

Apache Airflow是一个开源工作流管理平台&#xff0c;支持以编程方式编写、调度和监控工作流。由于其灵活性、可扩展性和强大的社区支持&#xff0c;它已迅速成为编排复杂数据管道的首选工具。在这篇博文中&#xff0c;我们将深入研究Apache Airflow 中的任务概念&#xff0c;探…

93,【1】buuctf web [网鼎杯 2020 朱雀组]phpweb

进入靶场 页面一直在刷新 在 PHP 中&#xff0c;date() 函数是一个非常常用的处理日期和时间的函数&#xff0c;所以应该用到了 再看看警告的那句话 Warning: date(): It is not safe to rely on the systems timezone settings. You are *required* to use the date.timez…

ChatGPT怎么回事?

纯属发现&#xff0c;调侃一下~ 这段时间deepseek不是特别火吗&#xff0c;尤其是它的推理功能&#xff0c;突发奇想&#xff0c;想用deepseek回答一些问题&#xff0c;回答一个问题之后就回复服务器繁忙&#xff08;估计还在被攻击吧~_~&#xff09; 然后就转向了GPT&#xf…

本地部署DeepSeek教程(Mac版本)

第一步、下载 Ollama 官网地址&#xff1a;Ollama 点击 Download 下载 我这里是 macOS 环境 以 macOS 环境为主 下载完成后是一个压缩包&#xff0c;双击解压之后移到应用程序&#xff1a; 打开后会提示你到命令行中运行一下命令&#xff0c;附上截图&#xff1a; 若遇…

2月3日星期一今日早报简报微语报早读

2月3日星期一&#xff0c;农历正月初六&#xff0c;早报#微语早读。 1、多个景区发布公告&#xff1a;售票数量已达上限&#xff0c;请游客合理安排行程&#xff1b; 2、2025春节档总票房破70亿&#xff0c;《哪吒之魔童闹海》破31亿&#xff1b; 3、美宣布对中国商品加征10…

WPF进阶 | WPF 动画特效揭秘:实现炫酷的界面交互效果

WPF进阶 | WPF 动画特效揭秘&#xff1a;实现炫酷的界面交互效果 前言一、WPF 动画基础概念1.1 什么是 WPF 动画1.2 动画的基本类型1.3 动画的核心元素 二、线性动画详解2.1 DoubleAnimation 的使用2.2 ColorAnimation 实现颜色渐变 三、关键帧动画深入3.1 DoubleAnimationUsin…

DeepSeek 遭 DDoS 攻击背后:DDoS 攻击的 “千层套路” 与安全防御 “金钟罩”

当算力博弈升级为网络战争&#xff1a;拆解DDoS攻击背后的技术攻防战——从DeepSeek遇袭看全球网络安全新趋势 在数字化浪潮席卷全球的当下&#xff0c;网络已然成为人类社会运转的关键基础设施&#xff0c;深刻融入经济、生活、政务等各个领域。从金融交易的实时清算&#xf…

本地部署DeepSeek-R1模型(新手保姆教程)

背景 最近deepseek太火了&#xff0c;无数的媒体都在报道&#xff0c;很多人争相着想本地部署试验一下。本文就简单教学一下&#xff0c;怎么本地部署。 首先大家要知道&#xff0c;使用deepseek有三种方式&#xff1a; 1.网页端或者是手机app直接使用 2.使用代码调用API …

DRM系列七:Drm之CREATE_DUMB

本系列文章基于linux 5.15 DRM驱动的显存由GEM&#xff08;Graphics execution management&#xff09;管理。 一、创建流程 创建buf时&#xff0c;user层提供需要buf的width,height以及bpp(bite per pixel)&#xff0c;然后调用drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &…

二叉树——429,515,116

今天继续做关于二叉树层序遍历的相关题目&#xff0c;一共有三道题&#xff0c;思路都借鉴于最基础的二叉树的层序遍历。 LeetCode429.N叉树的层序遍历 这道题不再是二叉树了&#xff0c;变成了N叉树&#xff0c;也就是该树每一个节点的子节点数量不确定&#xff0c;可能为2&a…

使用mybatisPlus插件生成代码步骤及注意事项

使用mybatisPlus插件可以很方便的生成与数据库对应的PO对象&#xff0c;以及对应的controller、service、ImplService、mapper代码&#xff0c;生成这种代码的方式有很多&#xff0c;包括mybatis-plus提供的代码生成器&#xff0c;以及idea提供的代码生成器&#xff0c;无论哪一…