【Linux篇】自主Shell命令行解释器
📌 个人主页: 孙同学_
🔧 文章专栏:Liunx
💡 关注我,分享经验,助你少走弯路!
文章目录
- 1. 获取用户名的接口
- 2. 等待用户输入接口
- 3. 将上述代码进行面向对象式的封装
- 4. 命令行解析
- 5. 执行命令
- 6. 路径切割
- 7. 解决cd命令路径不变
- 8. 解决cd后环境变量未发生变化
- 9. echo命令
- 10. 获取环境变量
- 11. 总结
- 12.代码实现
1. 获取用户名的接口
通过环境变量来获取
我们需要用到的接口getenv



//获取用户名
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;
}
2. 等待用户输入接口
当我们没有输入时,我们会发现命令行会卡在这里等待我们输入

我们也让我们自己的命令行能等待输入

我们可以采用fgets以文件形式读取一行,也可以使用gets读取一行字符串
我们接下来进行C/C++混编的方式,因为我们后面会用到系统调用,而这些系统调用都是用C写的,如果我们纯用C++来实现的话可能会要适配某些接口。
我们下来用fgets实现



效果展示:

我们会发现最后多了一个空行,这里为什么会多一个空行呢?因为我们在输入完字符串后还按了一次回车,我们不想让它有这一行空行该怎么办?我们在输入字符串后后面还会有个\n,比如我们输入的是"ls -a -l"最后再按一次回车就变成了"ls -a -l \n",我们只需要输入完之后把最后的\n置为0就好了

效果展示:

🔖小tips: 这里会不会求出的字符串长度为0,然后再-1发生越界呢?答案是不会的,因为我们最后至少还要敲一次回车键,所以这个字符串的最小长度为1。
3. 将上述代码进行面向对象式的封装
我们先认识一个新的接口snprintf


//制作命令行提示符
void MakeCommandline(char com_prompt[], int size)
{snprintf(com_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());//我们想让"[%s@%s %s]# "以后能随便调整,所以我们define一下
}//打印命令行提示符
void PrintCommandline()
{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 \n"const char* c = fgets(out, size, stdin);//从标准输入里获取,放到out当中if (c == NULL) return 1;out[strlen(out) - 1] = 0;//清理\n if (strlen(out) == 0) return false; //对于我们用户来说,有可能获取到的字符串的长度为0,为0直接return false return true; //否则return true
}int main()
{//printf("[%s@%s %s]# ",GetUserName(),GetHostName(),GetPwd());//1. 打印命令行提示符PrintCommandline();//2.获取用户输入char commandline[COMMAND_SIZE];//定义一个数组if (GetCommandline(commandline, sizeof(commandline)))//如果获取成功{printf("echo %s\n", commandline);//回显一下我们输入的内容,用作测试}return 0;
}
我们在shell中可以一直输入,我们的程序输入一次就结束了,所以shell永远不退出。我们应当不断地获取用户输入。

4. 命令行解析
我们在传字符串的时候不能“ls -a -l”整体传入,我们要将传入的字符串进行变形,将这一个字符串拆成“ls” "-a" "-l"。而且我们的命令行也不能在shell中直接替换,而要创建子进程。我们将字符串切成这样那么如何快速的找到每一个元素呢?命令行参数表
将打散的字符串以NULL结尾放到g_argv[]里面。在这里又来认识一个新的接口strtok

这个接口第一次切的时候第一个参数传的是要分割的字符串的起始地址,如果要接着切的话第一个参数就必须传NULL,当切除完毕返回值就为空表示没有字符串了。
分隔符是const char*所以我们不能传单引号,而要传双引号

//命令行分析
bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0; //每次进来初始化为0//"ls -a -l" => "ls" "-a" "-l"g_argv[g_argc++] = strtok(commandline, SEP);while (g_argv[g_argc++] = strtok(nullptr, SEP));//再次想切的话传commandline就不对了,再要切历史字符串就得把它设为nullptr,再次切的话分隔符依旧是SEP,如果切成了返回的就是下一个字符串的起始地址//为什么可以这样切呢?因为再次切字串时,它一直切一直切最后就会会变成NULL,切成NULL首先会把g_argv数组置为NULL,符合命令行参数表的设定,NULL也会作为while的条件判断,最后就直接结束了//并且g_argc也会统计出命令行参数有多少个g_argc--;//因为NULL也被统计到了里面return true;
}//测试形成的表结构
void PrintArgv()
{for (int i = 0; i < g_argc; i++){printf("argv[%d]->%s\n", i, g_argv[i]);}printf("argc:%d", g_argc);
}
5. 执行命令
由于我们当前的进程还有自己的任务,所以我们将执行命令交给子进程来完成,那么就需要程序替换,execvp

//执行命令
int Execute()
{pid_t id = fork();if (id == 0){//子进程execvp(g_argv[0], g_argv);exit(1);}//父进程pid_t rid = waitpid(id, nullptr, 0);return 0;
}
6. 路径切割
系统的路径名只有一个,我们自己写的会跟一长串

所以我们对路径进行切割。 C++中有个命令rfind从后向前找,substr截字符串

//路径切割
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);
}
7. 解决cd命令路径不变
到目前我们会发现我们执行cd命令时路径不发生改变,因为目前所有的命令都是子进程执行的,子进程改变路径时改的是自己的pwd,父进程bash的环境变量并没有改变,我们真正要改的是父进程的路径,因为把父进程的路径改了往后再创建子进程所有的子进程就会在新的路径下执行,因为所有的子进程的PCB都是拷贝父进程的PCB,因此cd这样的命令不能让子进程去执行,而要让父进程亲自执行,这种命令叫做内建命令
如何让bash亲自去执行呢?我们先来认识一个新的接口chdir



//处理cd命令
bool cd()
{if (g_argc == 1) //表明只是cd,没有带任何参数{std::string home = GetHome();//将home的路径拿过来if (home.empty()) return true;//如果是空就相当于环境变量获取失败了chdir(home.c_str());//走到这里不为空,就把当前路径切换成家路径了}else{std::string where = g_argv[1];//"cd -" / "cd ~"if (where == "-"){}else if (where == "~"){}else{chdir(where.c_str());}}
}//检测并处理内建命令
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if (cmd == "cd")//如果是内建命令{cd();return true;//是内建命令}return false;//否则不是内建命令
}
8. 解决cd后环境变量未发生变化
我们再切换路径后会发现路径变了,但是环境变量中的路径并没有变。原因是路径发生变化后环境变量没有进行刷新,所以我们要将新的路径更新到环境变量中。这里我们来认识一个系统调用getcwd,获取当前进程的工作路径。


//获取当前路径
const char* GetPwd()
{//const char* pwd = getenv("PWD");const char* pwd = getcwd(cwd, sizeof(cwd));if (pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);//获得环境变量putenv(cwdenv);//将环境变量导给当前进程}return pwd == NULL ? "None" : pwd;
}
9. echo命令
echo命令也是内建命令,我们可以用它echo "hello"在屏幕上打印,echo $?查看上个进程的退出码,echo $PATH查看环境变量。


//处理echo命令
void Echo()
{if (g_argc == 2)//意思是echo后面必须得跟东西{//echo "heool world"//echo $?//echo $PATHstd::string opt = g_argv[1];if (opt == "$?")//输出上一个程序退出的退出码{std::cout << lastcode << std::endl;lastcode = 0;//lastcode清零}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;}}
}
10. 获取环境变量
shell启动时需要从系统中获取环境变量,但是我们还做不到从配置文件中读,今天我们直接从父shell中拿就可以了,我们自己维护一张环境变量表,然后将表导进环境变量空间就行。
我们又用到一个接口environ


//初始化环境变量表
void InitEnv()
{extern char** environ;//声明一个环境变量所对应的信息memset(g_env, 0, sizeof(g_env));//将表中的信息全部置为0g_envs = 0;//本来要从配合文件中来//今天从父shell中来//1. 获取环境变量for (int i = 0; environ[i]; i++){g_env[i] = (char*)malloc(strlen(environ[i]) + 1);//1.2拷贝strcpy(g_env[i], environ[i]);//把父进程环境变量里的值拷贝给g_envg_envs++;}g_env[g_envs] = NULL;//2.导入环境变量for (int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;//3.clean清理
}
11. 总结
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束。所以要写一个shell,需要循环以下过程:
- 获取命令行
- 解析命令行
- 建立⼀个子进程(fork)
- 替换子进程(execvp)
- 父进程等待子进程退出(wait)
12.代码实现
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unordered_map>#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "
#define MAXARGC 128//命令行参数表
char* g_argv[MAXARGC];//全局的命令行参数表
int g_argc = 0;//环境变量表
#define MAX_ENVS 100
char* g_env[MAX_ENVS];//正常情况下shell启动时应该从配置文件中读取环境变量来填充这张环境变量表,也就是说当shell启动时就应该拿环境变量表来初始化它
int g_envs = 0;//环境变量的个数//别名映射表
std::unordered_map<std::string,std::string> alias_list;//定义一个cwd
char cwd[1024];
char cwdenv[1024];//当前工作路径的env//最新程序的退出码 last exit cide
int lastcode = 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 = getenv("PWD");const char* pwd = getcwd(cwd,sizeof(cwd));if(pwd != NULL){snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);//获得环境变量putenv(cwdenv);//将环境变量导给当前进程}return pwd == NULL ? "None" : pwd;
}//获取家目录
const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "" : home;
}//初始化环境变量表
void InitEnv()
{extern char** environ;//声明一个环境变量所对应的信息memset(g_env, 0, sizeof(g_env));//将表中的信息全部置为0g_envs = 0;//本来要从配合文件中来//今天从父shell中来//1. 获取环境变量for(int i = 0;environ[i];i++){//1.1申请空间g_env[i] = (char*)malloc(strlen(environ[i])+1);//1.2拷贝strcpy(g_env[i],environ[i]);//把父进程环境变量里的值拷贝给g_envg_envs++;}g_env[g_envs] = NULL;//2.导入环境变量for(int i = 0;g_env[i];i++){putenv(g_env[i]);}environ = g_env;//3.clean清理
}//处理cd命令
bool cd()
{if(g_argc == 1) //表明只是cd,没有带任何参数{std::string home = GetHome();//将home的路径拿过来if(home.empty()) return true;//如果是空就相当于环境变量获取失败了chdir(home.c_str());//走到这里不为空,就把当前路径切换成家路径了}else{std::string where = g_argv[1];//"cd -" / "cd ~"if(where == "-"){}else if(where == "~"){}else{chdir(where.c_str());}}return true;
}//处理echo命令
void Echo()
{if(g_argc == 2)//意思是echo后面必须得跟东西{//echo "heool world"//echo $?//echo $PATHstd::string opt = g_argv[1];if(opt == "$?")//输出上一个程序退出的退出码{std::cout << lastcode <<std::endl;lastcode = 0;//lastcode清零}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;}} }//路径切割
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);
}//制作命令行提示符
void MakeCommandline(char com_prompt[],int size)
{//snprintf(com_prompt, size, FORMAT,GetUserName(),GetHostName(),GetPwd());//我们想让"[%s@%s %s]# "以后能随便调整,所以我们define一下snprintf(com_prompt, size, FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());//我们想让"[%s@%s %s]# "以后能随便调整,所以我们define一下
}//打印命令行提示符
void PrintCommandline()
{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 \n"const char *c = fgets(out,size,stdin);//从标准输入里获取,放到out当中if(c == NULL) return 1;out[strlen(out)-1] = 0;//清理\n if(strlen(out) == 0) return false; //对于我们用户来说,有可能获取到的字符串的长度为0,为0直接return false return true; //否则return true
}//命令行分析bool CommandParse(char* commandline)
{
#define SEP " "g_argc = 0; //每次进来初始化为0//"ls -a -l" => "ls" "-a" "-l"g_argv[g_argc++] = strtok(commandline,SEP);while((bool)(g_argv[g_argc++] = strtok(nullptr,SEP)));//再次想切的话传commandline就不对了,再要切历史字符串就得把它设为nullptr,再次切的话分隔符依旧是SEP,如果切成了返回的就是下一个字符串的起始地址//为什么可以这样切呢?因为再次切字串时,它一直切一直切最后就会会变成NULL,切成NULL首先会把g_argv数组置为NULL,符合命令行参数表的设定,NULL也会作为while的条件判断,最后就直接结束了//并且g_argc也会统计出命令行参数有多少个g_argc--;//因为NULL也被统计到了里面return g_argc > 0 ? true : false;
}//测试形成的表结构
void PrintArgv()
{for(int i = 0;i < g_argc; i++){printf("argv[%d]->%s\n",i,g_argv[i]);}printf("argc:%d\n",g_argc);
}//检测并处理内建命令
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd")//如果是内建命令{cd(); return true;//是内建命令}else if(cmd == "echo"){Echo();return true;//内建命令执行完后return true}else if(cmd == "export"){}else if(cmd == "alias")//说明是一个别名{//std::string nickname = g_argv[1];// alisa_list.insert(k,v);}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;
}
int main()
{//shell启动时需要从系统中获取环境变量//我们的环境变量信息应该从父shell中统一来InitEnv();while(true){//printf("[%s@%s %s]# ",GetUserName(),GetHostName(),GetPwd());//1. 打印命令行提示符PrintCommandline();//2.获取用户输入char commandline[COMMAND_SIZE];//定义一个数组if(!GetCommandline(commandline,sizeof(commandline)))//如果获取失败continue重新获取continue; // printf("echo %s\n",commandline);//回显一下我们输入的内容,用作测试//3."ls -a -l" -> "ls" "-a" "-l"//命令行分析if(!CommandParse(commandline))//分析commandline中的命令行continue;//只有解析成功后才往下走,否则就继续//PrintArgv();// 检查别名 直接将commandline替换成别名//4.检测并处理内建命令if(CheckAndExecBuiltin())continue;//若为内建命令就不用创建子进程了,由bash亲自执行//5. 执行命令Execute();}return 0;
}
👍 如果对你有帮助,欢迎:
- 点赞 ⭐️
- 收藏 📌
- 关注 🔔
相关文章:
【Linux篇】自主Shell命令行解释器
📌 个人主页: 孙同学_ 🔧 文章专栏:Liunx 💡 关注我,分享经验,助你少走弯路! 文章目录 1. 获取用户名的接口2. 等待用户输入接口3. 将上述代码进行面向对象式的封装4. 命令行解析5.…...
Tomcat 部署 Jenkins.war 详细教程(含常见问题解决)
在Tomcat中部署Jenkins.war文件是一个相对简单的过程,以下是详细步骤: 1. 准备工作 确保已安装JDK:Jenkins需要Java环境,建议安装JDK 8或更高版本。 下载Jenkins.war:https://pan.quark.cn/s/c4fd7711a1b3 下载Tomc…...
我的创作纪念日-一周年
目录 机缘 收获 日常 成就 憧憬 机缘 时光荏苒,转行计算机已经是第5个年头了。从Python入门,到C入土,兜兜转转,发现自己也只是初窥门径,习得皮毛。我从6年前开始潜水CSDN,学习各路大佬的技术经验&…...
多线程代码案例 - 1
目录 单例模式 1. 饿汉模式 2. 懒汉模式 单例模式与多线程 问题1 问题2 问题3 完! 单例模式 单例模式是一种设计模式。 设计模式,是我们在编写代码时候的一种软性的规定,也就是说,我们遵守设计模式,代码的下限…...
display:none与visibility的区别
1. 是否占据空间 display: none:元素完全从文档流中移除,不占据任何布局空间。后续元素会“填补”它的位置。visibility:hidden:元素仍占据布局空间,但内容不可见(类似透明占位符)。 2.渲染与性能 displ…...
算法 | 基于蜣螂优化算法求解带时间窗的车辆路径问题(VRPTW)研究(附matlab代码)
基于蜣螂优化算法求解带时间窗的车辆路径问题(VRPTW)研究 🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏🍏 摘要 带时间窗的车辆路径问题(VRPTW)是物流配送中的核心优化难题。本文提出一种基于蜣螂优化算法(Dung Beetle Optimizer, DBO)的求解方法,通过…...
开发体育赛事直播系统主播认证功能技术实现方案
该体育直播系统系统由东莞梦幻网络科技开发,使用 ThinkPHP 作为后端,Vue.js 作为 PC/H5 端框架,Java 和 Objective-C 分别用于安卓和 iOS 开发。 1、前端实现 (Vue.js) <template><div class"anchor-certification">…...
国产三维CAD「皇冠CAD」在汽车零部件领域建模教程:刹车片
本教程深度融合三维皇冠CAD(CrownCAD)的MBD(Model-Based Definition)设计理念,通过参数化建模、智能约束管理、动态装配验证等功能,实现数据驱动设计,精准解决了汽车制动系统中精密制动组件的设…...
基于指针的线程池
使用原线程池 当 push 和 pop的对象过大时,消耗时延过高,需优化线程池 采用 std::move() unique_ptr的方法,能极大的减少时延, 实际就是避免了多次拷贝,直接使用指针。 代码实现 ThreadPool…...
GitHub与Gitee各是什么?它们的区别与联系是什么?
李升伟 整理 GitHub 介绍 GitHub 是一个基于 Git 的代码托管平台,主要用于版本控制和协作开发。它支持多人协作,提供代码托管、问题跟踪、代码审查、项目管理等功能。GitHub 是全球最大的开源社区,许多知名开源项目都在此托管。 主要功能&…...
SpringMvc获取请求数据
基本参数 RequestMapping("save5") ResponseBody public User save5(String name, int age) {User user new User();user.setName(name);user.setAge(age);return user; } 在url中将name与age进行编写,通过框架可以提取url中的name与age,这…...
简述竞赛经历在考研复试中的作用
文章目录 前言拟录取情况baichuicxyAshy佬齐总结回首展望 前言 随着就业形式的不断变化,竞赛奖项在就业中能起到的作用在逐步减弱。想拿到头部大厂实习面试资格,最低要区域赛银牌起步,当然这也仅仅是面试资格。 那么,竞赛经历在…...
大语言模型开发框架——LangChain
什么是LangChain LangChain是一个开发由语言模型驱动的应用程序的框架,它提供了一套工具、组件和接口,可以简化构建高级语言模型应用程序的过程。利用LangChain可以使应用程序具备两个能力: 上下文感知 将语言模型与上下文(提示…...
Nginx 核心配置详解与性能优化最佳实践
1.什么是 Nginx? Nginx 是一个高性能的 Web 服务器和反向代理服务器。它轻量、高效,被广泛用于现代 Web 开发中。 2.为什么前端需要了解 Nginx? ★ 了解 本地开发:可以模拟生产环境 部署前端项目:作为静态文件服务器…...
机器学习的一百个概念(7)独热编码
前言 本文隶属于专栏《机器学习的一百个概念》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…...
用LLama factory时报类似Process 2504721 got signal: 1的解决方法
之前用nohup来远程跑LLama factory微调脚本,是没有问题的,但今天发现运行类似下面这个命令时, nohup llamafactory-cli train examples/train_qlora/qwen_lora.yaml 只要一关闭ssh session,就会终止训练,报类似&…...
解决 Git 通过 SSH 克隆仓库时自动转换为 HTTPS 的问题
解决 Git 通过 SSH 克隆仓库时自动转换为 HTTPS 的问题 在使用 Git 通过 SSH 协议克隆私有仓库时,如果遇到类似以下错误: fatal: unable to access https://itlab.stack.net:stack.git/: Could not resolve host: gitlab.stack这通常是因为 Git 配置错…...
从实用的角度聊聊Linux下文本编辑器VIM
本文从实用的角度聊聊Vim的常用命令。何为实用?我举个不实用的例子大家就明白了,用vim写代码。;) “vim是从 vi 发展出来的一个文本编辑器。代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用,和Emacs并列成…...
多电机显示并排序
多电机显示并排序 要实现根据后端传递过来的驱动电机数据的数量来显示不同数量的数据列表,我们可以使用 Vue 的 v-for 指令来遍历 driveMotorData 数组,并为每个驱动电机生成一个数据列表。这样,无论后端传来多少个驱动电机的数据࿰…...
佳能imageRUNNER 2206N基本参数及管理员密码
基本参数: 产品类型 激光数码复合机 颜色类型 黑白 涵盖功能 复印/打印/扫描 速度类型 低速 最大原稿尺寸 A3 复印/打印方式 激光静电转印方式 感光材料 OPC 显影系统 干式单组分显影 定影…...
社交类 APP 设计:打造高用户粘性的界面
在当今数字化时代,社交类APP已成为人们日常生活中不可或缺的一部分。然而,随着市场竞争的加剧,如何通过设计提升用户粘性成为社交类APP成功的关键。本文将从设计的关键要素、用户界面优化、功能创新、个性化体验以及持续优化等方面࿰…...
数据编排与Dagster:解锁现代数据管理的核心工具
在数据驱动的时代,如何高效管理复杂的数据管道、确保数据质量并实现团队协作?本文深入探讨数据编排的核心概念,解析其与传统编排器的差异,并聚焦开源工具Dagster如何以“资产为中心”的理念革新数据开发流程,助力企业构…...
网络安全中的“后门”:概念、类型、作用与攻防技术
目录 什么是后门? 后门的常见类型 2.1 按植入方式分类 2.2 按功能分类 后门在安全测试中的作用 后门的玩法与免杀技术 4.1 常见后门技术 4.2 如何实现免杀(Bypass AV) 如何检测和防御后门? 总结 1. 什么是后门ÿ…...
AI | 大模型入门介绍
以下是关于AI大模型中蒸馏、量化、MoE和MHA技术的介绍: 1. 模型蒸馏(Model Distillation) • 定义:模型蒸馏是一种将大型复杂模型(教师模型)的知识转移到小型简单模型(学生模型)的技…...
Jmeter的压测使用
Jmeter基础功能回顾 一、创建Jmeter脚本 1、录制新建 (1)适用群体:初学者 2、手动创建 (1)需要了解Jmeter的常用组件 元件:多个类似功能组件的容器(类似于类) 各元件作用 组件…...
kubernetes》》k8s》》Deployment》》ClusterIP、LoadBalancer、Ingress 内部访问、外边访问
Nginx部署 K8s 集群内外访问服务的方式 节点 Kubernetes 集群中的服务器(指单台) 集群 Kubernetes 管理的一组服务器的集合 边界路由器 为局域网和Internet路由数据包的路由器,执行防火墙保护局域网络 集群网络 遵循Kubernetes网络模型实现集…...
Transformer 通关秘籍8:词向量如何表示近义词?
上一节已经完成了 token 到词向量的转换。那么,使用转换后的词嵌入向量便可以表示 token 之间的语义了吗?便可以表示两个单词是否是近义词,是否是反义词了吗? 是的。 接下来先通过一个例子,来直观地理解一下词嵌入向…...
Vue + Scss项目中实现自定义颜色主题的动态切换
当时面试的时候遇到面试官问的一个问题如何实现自定义颜色主题切换,当时我做的只是elementUIPlus提供的暗黑和默认主题切换 theme.scss // 增加自定义主题类型 $themes: (light: (/* 原有配置保持不变 */),dark: (/* 原有配置保持不变 */),custom: () // 空映射…...
搭建qemu环境
1.安装qemu apt install qemu-system2.编译内核 设置gcc软链接sudo ln -s arm-linux-gnueabihf-gcc arm-linux-gccsudo ln -s arm-linux-gnueabihf-ld arm-linux-ldsudo ln -s arm-linux-gnueabihf-nm arm-linux-nmsudo ln -s arm-linux-gnueabihf-objcopy arm-linux-objc…...
【MVC简介-产生原因、演变历史、核心思想、组成部分、使用场景】
MVC简介 产生原因: MVC(Model-View-Controller)模式诞生于20世纪70年代,由Trygve Reenskaug在施乐帕克研究中心(Xerox PARC)为Smalltalk语言设计,目的是解决图形用户界面(GUI&…...

