自主Shell命令行解释器

什么是命令行

我们一直使用的"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;
// }


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分析哲学:从 语言解剖到 思想澄清的哲学探险

分析哲学&#xff1a;从 语言解剖 到 思想澄清 的哲学探险 第一节&#xff1a;分析哲学的基本概念与公式解释 【通俗讲解&#xff0c;打比方来讲解&#xff01;】 分析哲学&#xff0c;就像一位 “语言侦探”&#xff0c;专注于 “解剖语言”&#xff0c;揭示我们日常使用的语…

自定义数据集 使用paddlepaddle框架实现逻辑回归

导入必要的库 import numpy as np import paddle import paddle.nn as nn 数据准备&#xff1a; seed1 paddle.seed(seed)# 1.散点输入 定义输入数据 data [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6…

QtCreator在配置Compilers时,有一个叫ABI的选项,那么什么是ABI?

问题提出 QtCreator在配置Compilers时,有一个叫ABI的选项,那么什么是ABI&#xff1f; ABI&#xff08;Application Binary Interface&#xff09;介绍 ABI&#xff08;Application Binary Interface&#xff0c;应用二进制接口&#xff09;是指应用程序与操作系统或其他程序…

[STM32 标准库]EXTI应用场景 功能框图 寄存器

一、EXTI 外部中断在嵌入式系统中有广泛的应用场景&#xff0c;如按钮开关控制&#xff0c;传感器触发&#xff0c;通信接口中断等。其原理都差不多&#xff0c;STM32会对外部中断引脚的边沿进行检测&#xff0c;若检测到相应的边沿会触发中断&#xff0c;在中断中做出相应的处…

Maven jar 包下载失败问题处理

Maven jar 包下载失败问题处理 1.配置好国内的Maven源2.重新下载3. 其他问题 1.配置好国内的Maven源 打开⾃⼰的 Idea 检测 Maven 的配置是否正确&#xff0c;正确的配置如下图所示&#xff1a; 检查项⼀共有两个&#xff1a; 确认右边的两个勾已经选中&#xff0c;如果没有请…

【JavaScript】Web API事件流、事件委托

目录 1.事件流 1.1 事件流和两个阶段说明 1.2 事件捕获 1.3 事件冒泡 1.4 阻止冒泡 1.5 解绑事件 L0 事件解绑 L2 事件解绑 鼠标经过事件的区别 两种注册事件的区别 2.事件委托 案例 tab栏切换改造 3.其他事件 3.1 页面加载事件 3.2 页面滚动事件 3.2 页面滚…

Spring Cloud工程搭建

目录 工程搭建 搭建父子工程 创建父工程 Spring Cloud版本 创建子项目-订单服务 声明项⽬依赖 和 项⽬构建插件 创建子项目-商品服务 声明项⽬依赖 和 项⽬构建插件 工程搭建 因为拆分成了微服务&#xff0c;所以要拆分出多个项目&#xff0c;但是IDEA只能一个窗口有一…

neo4j入门

文章目录 neo4j版本说明部署安装Mac部署docker部署 neo4j web工具使用数据结构图数据库VS关系数据库 neo4j neo4j官网Neo4j是用ava实现的开源NoSQL图数据库。Neo4作为图数据库中的代表产品&#xff0c;已经在众多的行业项目中进行了应用&#xff0c;如&#xff1a;网络管理&am…

selenium记录Spiderbuf例题C03

防止自己遗忘&#xff0c;故作此为记录。 鸢尾花数据集(Iris Dataset) 这道题牵扯到JS动态加载。 步骤&#xff1a; &#xff08;1&#xff09;进入例题&#xff0c;需要找到按钮规律。 flip_xpath: str r"//li/a[onclickgetIrisData({});]" &#xff08;2&…

自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数

导入必要的库&#xff1a; import numpy as np import torch import torch.nn as nn import torch.optim as optim from sklearn.metrics import precision_score, recall_score, f1_score 准备数据&#xff1a; class1_points np.array([[1.9, 1.2],[1.5, 2.1],[1.9, 0.5]…

如何运行Composer安装PHP包 安装JWT库

1. 使用Composer Composer是PHP的依赖管理工具&#xff0c;它允许你轻松地安装和管理PHP包。对于JWT&#xff0c;你可以使用firebase/php-jwt这个库&#xff0c;这是由Firebase提供的官方库。 安装Composer&#xff08;如果你还没有安装的话&#xff09;&#xff1a; 访问Co…

《Linux服务与安全管理》| 数据库服务器安装和配置

《Linux服务与安全管理》| 数据库服务器安装和配置 目录 《Linux服务与安全管理》| 数据库服务器安装和配置 任务一&#xff1a; 安装PostgreSQL数据库&#xff0c;设置远程登录&#xff0c;客户端可以成功登录并操作数据库。 任务二&#xff1a; 安装MySQL数据库&#xf…

【贪心算法篇】:“贪心”之旅--算法练习题中的智慧与策略(一)

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;贪心算法篇–CSDN博客 文章目录 一.贪心算法1.什么是贪心算法2.贪心算法的特点 二.例题1.柠…

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, &…

Python从0到100(八十七):CNN网络详细介绍及WISDM数据集模型仿真

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

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

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

实践网络安全:常见威胁与应对策略详解

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在数字化转型的浪潮中&#xff0c;网络安全的重要性已达到前所未有的高度。无论是个人用户、企业&#xff0c;还是政府机构…

web-SQL注入-CTFHub

前言 在众多的CTF平台当中&#xff0c;作者认为CTFHub对于初学者来说&#xff0c;是入门平台的不二之选。CTFHub通过自己独特的技能树模块&#xff0c;可以帮助初学者来快速入门。具体请看官方介绍&#xff1a;CTFHub。 作者更新了CTFHub系列&#xff0c;希望小伙伴们多多支持…

LabVIEW如何高频采集温度数据?

在LabVIEW中进行高频温度数据采集时&#xff0c;选择合适的传感器&#xff08;如热电偶或热电阻&#xff09;和采集硬件是关键。下面是一些建议&#xff0c;帮助实现高效的温度数据采集&#xff1a; 1. 传感器选择&#xff1a; 热电偶&#xff08;Thermocouple&#xff09;&am…

Deep Sleep 96小时:一场没有硝烟的科技保卫战

2025年1月28日凌晨3点&#xff0c;当大多数人还沉浸在梦乡时&#xff0c;一场没有硝烟的战争悄然打响。代号“Deep Sleep”的服务器突遭海量数据洪流冲击&#xff0c;警报声响彻机房&#xff0c;一场针对中国关键信息基础设施的网络攻击来势汹汹&#xff01; 面对美国发起的这场…