模拟shell小程序

news/2024/7/20 1:44:17 标签: 小程序, linux, 服务器

接下来利用我们当前的知识,撰写一个简单的shell外壳程序。

1.shell原理

shell的原理是实际上就是运行了一个父进程,然后创建出子进程,最后使用进程替换调用,替换成其他程序。

2.shell实现

2.1.死循环

首先一个shell一旦运行起来就不会关闭(除非关闭终端窗口),因此一定是一个死循环。

int main()
{
	while(1)
    {
        //...
    }
    return 0}

2.2.提示字符串

运行一个shell有出现一个类似[user@myshell]$的输入提示符,由于不能换行,因此我们还需要使用fflush()进行刷新。

#include <stdio.h>
int main()
{
	while(1)
    {
        printf("[user@myshell]$ ");
        fflush(stdout);
    }
    return 0;
}

2.3.获取用户输入

#include <stdio.h>
#include <string.h>

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

int main()
{
	while(1)
    {
        //1.打印提示信息
        printf("[user@myshell]$ ");
        fflush(stdout);
    
        //2.获取 user 的输入
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串
        {
            //出错就直接跳出循环
            continue;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//即使用户输入字符串超出范围,也能保证获取到一个完整的 C 风格字符串
    }

    return 0;
}

2.4.解析用户输入

#include <stdio.h>
#include <string.h>

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

#define SIZE 32
char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组

#define SEP " "//分隔符

int main()
{
	while(1)
    {
        //1.打印提示信息
        printf("[user@myshell]$ ");
        fflush(stdout);
    
        //2.获取 user 的输入
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串
        {
            //出错就直接跳出循环
            continue;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//即使用户输入字符串超出范围,也能保证获取到一个完整的 C 风格字符串

        //3.命令行字符解析
        //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
        g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符

        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
        {
            g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
        }
        while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
    }

    return 0;
}

2.5.创建并且替换子进程

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

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

#define SIZE 32
char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组

#define SEP " "//分隔符

int main()
{
	while(1)
    {
        //1.打印提示信息
        printf("[user@myshell]$ ");
        fflush(stdout);
    
        //2.获取 user 的输入
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串
        {
            //出错就直接跳出循环
            continue;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//即使用户输入字符串超出范围,也能保证获取到一个完整的 C 风格字符串

        //3.命令行字符解析
        //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
        g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符

        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
        {
            g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
        }
        while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL

        //4.fork() 创建子进程,execvp() 替换子进程
        pid_t id = fork();
        if(id == 0)//child
        {
            printf("子进程 run:\n");
            execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
            exit(1);//没替换成功就会异常退出
        }
        //father
        int status;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        if(ret > 0) 
            printf("exit code: %d\n", WEXITSTATUS(status));
    }
    return 0;
}

2.6.debug

有一个小小的bug我们没有解决,就是使用cd命令的时候,没有办法切入到对应的目录,这是为什么呢?

3.shell拓展

3.1.用户名、主机名、目录获取

上面我们只是生硬显示了user充当用户名字,实际上我们可以通过环境变量获取执行shell的用户的名字,还可以获取在不同环境下的系统主机名字,使用这两个环境变量的内容填充到提示字符串中,目录也可以这样操作。

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

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

#define SIZE 32
char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组

#define SEP " "//分隔符

const char* GetUserName()//获取用户名字
{
    const char* userName = getenv("USER");
    if(userName) 
        return userName;
    else
        return "none";
}

const char* GetHostName()//获取主机名字
{
    const char* hostName = getenv("HOSTNAME");
    if(hostName)
        return hostName;
    else
        return "none";
}

const char* GetPwd()//获取路径地址
{
    const char* pwd = getenv("PWD");
    if(pwd)
        return pwd;
    else
        return "none";
}

int main()
{
	while(1)
    {
        //1.打印提示信息
        printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
        fflush(stdout);
    
        //2.获取 user 的输入
        memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
        if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
        {
            //出错就直接跳出循环
            continue;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n

        //3.命令行字符解析
        //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
        g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符

        int index = 1;
        if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
        {
            g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
        }
        while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL

        //4.fork() 创建子进程,execvp() 替换子进程
        pid_t id = fork();
        if(id == 0)//child
        {
            printf("子进程 run:\n");
            execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
            exit(1);//没替换成功就会异常退出
        }
        //father
        int status;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        if(ret > 0) 
            printf("exit code: %d\n", WEXITSTATUS(status));
    }
    return 0;
}

3.2.封装步骤

既然上面的用户名、主机名、路径都进行了函数封装,那么我也可以选择将部分的代码进行分钟,变得更有逻辑。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h> 
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

#define SIZE 32
char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组

#define SEP " "//分隔符

const char* GetUserName(void)//获取用户名字
{
    const char* userName = getenv("USER");
    if(userName) 
        return userName;
    else
        return "none";
}

const char* GetHostName(void)//获取主机名字
{
    const char* hostName = getenv("HOSTNAME");
    if(hostName)
        return hostName;
    else
        return "none";
}

const char* GetPwd(void)//获取路径地址
{
    const char* pwd = getenv("PWD");
    if(pwd)
        return pwd;
    else
        return "none";
}

void Print(void)
{
    printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
    fflush(stdout);
}

bool GetUserCommand()
{
    memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
    if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
    {
        //出错就直接跳出循环
        return false;
    }
    cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
    return true;
}

void AnalyzeStr(void)
{
        //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
        g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符

        int index = 1;

        //处理特殊的命令情况
        if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
        {
            g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
        }

        while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
}

void RunCommand(void)
{
    pid_t id = fork();
    if(id == 0)//child
    {
        printf("子进程 run:\n");
        execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
        exit(1);//没替换成功就会异常退出
    }
    else
    {
        //father
        int status;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        if(ret > 0) 
        printf("exit code: %d\n", WEXITSTATUS(status));
    }
}

int main()
{
	while(1)
    {
        //1.打印提示信息
        Print();
    
        //2.获取 user 的输入
        if(!GetUserCommand())
            continue;

        //3.命令行字符解析
        AnalyzeStr();

        //4.fork() 创建子进程,execvp() 替换子进程
        RunCommand();
    }
    return 0;
}

3.3.debug

如果使用了上面的shell程序,很快就会发现两个bug

3.3.1.无法直接输入换行

在系统默认运行的shell中,允许用户不断回车,而我们的程序如果直接回车就会退出,这个问题的解决思路很简单,只需要在获取字符的时候,查看用户输入的字符长度是否为0即可,若是就让GetUserCommand()返回false,执行continue语句即可。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h> 
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

#define SIZE 32
char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组

#define SEP " "//分隔符

const char* GetUserName(void)//获取用户名字
{
    const char* userName = getenv("USER");
    if(userName) 
        return userName;
    else
        return "none";
}

const char* GetHostName(void)//获取主机名字
{
    const char* hostName = getenv("HOSTNAME");
    if(hostName)
        return hostName;
    else
        return "none";
}

const char* GetPwd(void)//获取路径地址
{
    const char* pwd = getenv("PWD");
    if(pwd)
        return pwd;
    else
        return "none";
}

void Print(void)
{
    printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
    fflush(stdout);
}

bool GetUserCommand()
{
    memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
    if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
    {
        //出错就直接跳出循环
        return false;
    }
    cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
    if(strlen(cmd_line) == 0)
        return false;
    return true;
}

void AnalyzeStr(void)
{
        //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
        g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符

        int index = 1;

        //处理特殊的命令情况
        if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
        {
            g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
        }

        while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
}
void RunCommand(void)
{
    pid_t id = fork();
    if(id == 0)//child
    {
        printf("子进程 run:\n");
        execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
        exit(1);//没替换成功就会异常退出
    }
    else
    {
        //father
        int status;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        if(ret > 0) 
        printf("exit code: %d\n", WEXITSTATUS(status));
    }
}

int main()
{
	while(1)
    {
        //1.打印提示信息
        Print();
    
        //2.获取 user 的输入
        if(!GetUserCommand())
            continue;

        //3.命令行字符解析
        AnalyzeStr();

        //4.fork() 创建子进程,execvp() 替换子进程
        RunCommand();
    }
    return 0;
}

3.3.2.无法切换对应目录

还可以发现一个比较严重的问题,我们无法使用cd命令切换目录,这是为什么呢?原因也很简单,cd程序替换的是子进程,该目录只让子进程进行了切换命令,而我们希望的是父进程自己执行cd,修改父进程的工作路径。因此,我们需要调用一些库函数,更改父进程的工作环境。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h> 
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

#define SIZE 32
char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组

#define SEP " "//分隔符

const char* GetUserName(void)//获取用户名字
{
    const char* userName = getenv("USER");
    if(userName) 
        return userName;
    else
        return "none";
}

const char* GetHostName(void)//获取主机名字
{
    const char* hostName = getenv("HOSTNAME");
    if(hostName)
        return hostName;
    else
        return "none";
}

const char* GetPwd(void)//获取路径地址
{
    const char* pwd = getenv("PWD");
    if(pwd)
        return pwd;
    else
        return "none";
}

void Print(void)//打印提示字符
{
    printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
    fflush(stdout);
}

bool GetUserCommand(void)//获取用户命令字符串
{
    memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
    if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
    {
        //出错就直接跳出循环
        return false;
    }
    cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
    if(strlen(cmd_line) == 0)
        return false;
    return true;
}

void AnalyzeStr(void)//拆分解析用户字符串
{
    //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
    g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符

    int index = 1;

    //处理特殊的命令情况
    if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
    {
        g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
    }

    while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
}

bool DoBuildin(void)//判断是否内建命令并且执行
{
    //内建命令
    if(strcmp(g_argv[0], "cd") == 0)       
    {   
        printf("父进程 run:\n");
        //更改路径的命令
        char* path = NULL;
        if(g_argv[1] == NULL)
        {
            path = ".";
        }
        else
        {
            path = g_argv[1];
        }
        chdir(path);
        return true;
    }
    return false;
}

void RunCommand(void)//运行用户命令
{
    pid_t id = fork();
    if(id == 0)//child
    {
        printf("子进程 run:\n");
        execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
        exit(1);//没替换成功就会异常退出
    }
    else
    {
        //father
        int status;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        if(ret > 0) 
        printf("exit code: %d\n", WEXITSTATUS(status));
    }
}

int main()
{
	while(1)
    {
        //1.打印提示信息
        Print();
    
        //2.获取 user 的输入
        if(!GetUserCommand())
            continue;

        //3.命令行字符解析
        AnalyzeStr();

        //4.处理内建命令
        if(DoBuildin())
            continue;

        //5.fork() 创建子进程,execvp() 替换子进程
        RunCommand();
    }
    return 0;
}

这种只能由父进程来执行的命令也叫做“内建命令”,这类环境变量有很多。

3.3.3.无法更改环境变量

还有一个比较尴尬的问题,我们发现使用cd指令后,提示字符串的地址无法及时更新,也就是没有更新对应的环境变量。如果更新呢?可以使用系统提供的接口getcwd()来获取调用该接口进程所在的路径。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h> 
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define NUM 1024
char cmd_line[NUM];//保存完整命令字符串

#define SIZE 32
char* g_argv[SIZE];//保存打散后的命令字符串,是一个数组

#define SEP " "//分隔符

char cwd[1024];//环境变量

const char* GetUserName(void)//获取用户名字
{
    const char* userName = getenv("USER");
    if(userName) 
        return userName;
    else
        return "none";
}

const char* GetHostName(void)//获取主机名字
{
    const char* hostName = getenv("HOSTNAME");
    if(hostName)
        return hostName;
    else
        return "none";
}

const char* GetPwd(void)//获取路径地址
{
    const char* pwd = getenv("PWD");
    if(pwd)
        return pwd;
    else
        return "none";
}

void Print(void)
{
    printf("[%s@%s %s]$ ", GetUserName(), GetHostName(), GetPwd());
    fflush(stdout);
}

bool GetUserCommand()
{
    memset(cmd_line, '\0', sizeof(cmd_line));//设定初始值
    if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL)//获取字符串,不能使用 scanf()
    {
        //出错就直接跳出循环
        return false;
    }
    cmd_line[strlen(cmd_line) - 1] = '\0';//将用户输入的字符串去掉 \n
    if(strlen(cmd_line) == 0)
        return false;
    return true;
}

void AnalyzeStr(void)
{
    //虽然可以自己写一个算法解析,但是我们可以使用一些现有的接口
    g_argv[0] = strtok(cmd_line, SEP);//第一次调用需要传入原始字符串和分隔符

    int index = 1;

    //处理特殊的命令情况
    if(strcmp(g_argv[0], "ls") == 0)//如果输入的命令是 ls 就进行一些特殊处理
    {
        g_argv[index++] = "--color=auto";//增加颜色参数,让输出带有颜色
    }

    while(g_argv[index++] = strtok(NULL, SEP));//第二次还想要解析原始字符串,就需要传入 NULL
}

bool DoBuildin()
{
    //内建命令
    if(strcmp(g_argv[0], "cd") == 0)       
    {   
        printf("父进程 run:\n");
        //更改路径的命令
        char* path = NULL;
        if(g_argv[1] == NULL)
        {
            path = ".";
        }
        else
        {
            path = g_argv[1];
        }
        chdir(path);
        
        char tmp[1024];
        getcwd(tmp, sizeof(tmp));//获取当前进程所在的地址(工作目录)
        sprintf(cwd, "PWD=%s", tmp);//组合环境变量
        putenv(cwd);//设置环境变量
        return true;
    }
    return false;
}

void RunCommand(void)
{
    pid_t id = fork();
    if(id == 0)//child
    {
        printf("子进程 run:\n");
        execvp(g_argv[0], g_argv);//自动在环境变量中搜索 g_argv[0] 的文件,执行 g_argv 数组存储的指令
        exit(1);//没替换成功就会异常退出
    }
    else
    {
        //father
        int status;
        pid_t ret = waitpid(id, &status, 0);//阻塞等待
        if(ret > 0) 
        printf("exit code: %d\n", WEXITSTATUS(status));
    }
}

int main()
{
	while(1)
    {
        //1.打印提示信息
        Print();
    
        //2.获取 user 的输入
        if(!GetUserCommand())
            continue;

        //3.命令行字符解析
        AnalyzeStr();

        //4.处理内建命令
        if(DoBuildin())
            continue;

        //5.fork() 创建子进程,execvp() 替换子进程
        RunCommand();
    }
    return 0;
}

http://www.niftyadmin.cn/n/5204668.html

相关文章

【资深硬件工程师总结-千兆以太网设计指南】

文章目录 01通用PCB布线指南02标志焊盘中的接地过孔区示例03EMI注意事项04ESD注意事项 资深硬件工程师总结-千兆以太网设计指南 本应用笔记旨在帮助客户使用Microchip的10/100/1000 Mbps以太网器件系列设计PCB。本文档提供有关PCB布线的建 议&#xff0c; PCB 布线是保持信号完…

第十一章 目标检测中的NMS(工具)

精度提升 众所周知&#xff0c;非极大值抑制NMS是目标检测常用的后处理算法&#xff0c;用于剔除冗余检测框&#xff0c;本文将对可以提升精度的各种NMS方法及其变体进行阶段性总结。 总体概要&#xff1a; 对NMS进行分类&#xff0c;大致可分为以下六种&#xff0c;这里是依…

编程语言发展史:汇编语言的出现和发展

一、汇编语言的出现 随着计算机硬件的发展&#xff0c;机器语言变得越来越复杂&#xff0c;难以被人类程序员理解和编写。因此&#xff0c;出现了更高级别的编程语言&#xff0c;这些语言使用类似英语的语法&#xff0c;使程序员能够更容易地编写和维护程序。 其中一种高级语…

hp惠普Victus Gaming Laptop 15-fa1025TX/fa1005tx原装出厂Win11系统ISO镜像

光影精灵9笔记本电脑原厂W11系统22H2恢复出厂时开箱状态一模一样 适用型号&#xff1a;15-fa1003TX&#xff0c;15-fa1005TX&#xff0c;15-fa1007TX&#xff0c;15-fa1025TX 链接&#xff1a;https://pan.baidu.com/s/1fBPjed1bhOS_crGIo2tP1w?pwduzvz 提取码&#xff1a…

高斯Filter 和 Bilateral Filter

参考链接&#xff1a; Python | Bilateral Filtering - GeeksforGeeks 高斯Filter&#xff1a; 高斯模糊后的图像中的每个像素的强度是由它周围的像素的加权平均得到的&#xff0c;这个权重就是高斯函数的值&#xff0c;它取决于像素之间的距离。具体来说&#xff1a; 通常会导…

C++电脑组装项目(涉及知识点:多态)

需求&#xff1a; #include <iostream> #include "Computer.h" #include "AbstractCpu.h" #include "AbstractMemory.h" #include "AbstractVideoCard.h" #include "IntelCpu.h" #include "IntelMemory.h" …

面试题-7

1.v-if和v-show的区别&#xff1f; 都可以控制元素的显示和隐藏 1.v-show是控制元素的display值来让元素显示和隐藏,v-if显示隐藏时把DOM元素整个添加和删除 2.v-if有一个局部编译/卸载的过程,切换这个过程中会适当的销毁和重建内部的事件监听和子组件&#xff1b; v-show只…

所有产品都值得用AI再做一遍,让AGI与品牌营销双向奔赴

微软 CEO Satya Nadella 曾经说过&#xff1a;“所有的产品都值得用 AI 重做一遍。” AI 大模型的出现&#xff0c;开启了一个全新的智能化时代&#xff0c;重新定义了人机交互。这让生成式 AI 技术变得「触手可得」&#xff0c;也让各行业看到 AGI 驱动商业增长的更大可能性。…