【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.…...
leetcode 2873. 有序三元组中的最大值 I
欢迎关注更多精彩 关注我,学习常用算法与数据结构,一题多解,降维打击。 文章目录 题目描述题目剖析&信息挖掘解题思路方法一 暴力枚举法思路注意复杂度代码实现 方法二 公式拆分动态规划思路注意复杂度代码实现 题目描述 [2873] 有序三元…...
深度学习 Deep Learning 第14章 自编码器
深度学习 Deep Learning 第14章 自编码器 内容概要 本章深入探讨了自编码器(Autoencoders),这是一种用于特征学习和降维的神经网络架构。自编码器通过编码器和解码器两个部分,将输入数据映射到一个内部表示(编码&…...
全国产传感器的可靠性、MTBF计算、极限测试与加速寿命测试
全国产传感器的可靠性是指传感器在规定条件下和规定时间内完成规定功能的能力。它是衡量传感器性能的重要指标之一,直接影响传感器的使用寿命和系统稳定性。武汉利又德的小编来和大家分享一下关于全国产传感器的可靠性以及MTBF计算、极限测试与加速寿命测试的小知识…...
【算法中的数学】裴蜀定理(Bézout’s Identity)总结
裴蜀定理(Bzout’s Identity)总结 裴蜀定理是数论中的一个重要定理,描述了整数线性组合与最大公约数(GCD)之间的关系。 1. 裴蜀定理的内容 对于任意两个整数 a a a 和 b b b,设它们的最大公约数为 d …...
unity点击button后不松开通过拖拽显示模型松开后模型实例化
using System.Collections; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI;[RequireComponent(typeof(Button))] // 确保脚本挂在Button上 public class DragButtonSpawner : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandle…...
计算机网络复习 吉林大学
1、信息交换的三种方式:电路交换,分组交换,报文交换。 从通信资源的分配角度来看,交换就是按照某种方式动态地分配传输线路的资源。 电路交换:(星形结构替代全连接) 电话交换机接通电话的方式…...
超级好用的小软件,连接电脑和手机。
将手机变成电脑摄像头的高效工具Iriun Webcam是一款多平台软件,能够将手机摄像头变成电脑的摄像头,通过简单的设置即可实现视频会议、直播、录制等功能。它支持Windows、Mac和Linux系统,同时兼容iOS和Android手机,操作简单&#x…...
【JavaScript】十三、事件监听与事件类型
文章目录 1、事件监听1.1 案例:击关闭顶部广告1.2 案例:随机点名1.3 事件监听的版本 2、事件类型2.1 鼠标事件2.1.1 语法2.1.2 案例:轮播图主动切换 2.2 焦点事件2.2.1 语法2.2.2 案例:模拟小米搜索框 2.3 键盘事件2.3.1 语法2.3.…...
微服务架构技术栈选型避坑指南:10大核心要素深度拆解
微服务架构的技术栈选型直接影响系统的稳定性、扩展性和可维护性。以下从10大核心要素出发,结合主流技术方案对比、兼容性评估、失败案例及优化策略,提供系统性选型指南。 1. 服务框架与通信 关键考量点 扩展性:框架需支持定制化扩展&#x…...
虚拟试衣间微信小程序解决方案
目录 项目名称: 云尚衣橱 核心功能模块: 技术栈选型: 架构设计概览: 详细功能点实现思路: 数据库设计 (MongoDB 示例): 开发步骤建议: 关键注意事项和挑战: 项目名称: 云尚衣橱 核心功能模块: 用户系统 (User System) 我的衣柜 (My Wardrobe) 虚拟试衣间 (Vir…...
C++STL——容器-vector(含部分模拟实现,即地层实现原理)(含迭代器失效问题)
目录 容器——vector 1.构造 模拟实现 2.迭代器 模拟实现: 编辑 3.容量 模拟实现: 4.元素的访问 模拟实现 5.元素的增删查改 迭代器失效问题: 思考问题 【注】:这里的模拟实现所写的参数以及返回值,都是…...
MoLe-VLA:通过混合层实现的动态跳层视觉-语言-动作模型实现高效机器人操作
25年3月来自南京大学、香港理工、北大和香港科技大学的论文“MoLe-VLA: Dynamic Layer-skipping Vision Language Action Model via Mixture-of-Layers for Efficient Robot Manipulation”。 多模态大语言模型 (MLLM) 在理解复杂语言和视觉数据方面表现出色,使通用…...
《数字图像处理》教材寻找合作者
Rafael Gonzalez和Richard Woods所著的《数字图像处理》关于滤波器的部分几乎全错,完全从零开始写,困难重重。关于他的问题已经描述在《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》。 现寻找能够共同讨论、切磋、…...
uni-app 框架 调用蓝牙,获取 iBeacon 定位信标的数据,实现室内定位场景
背景:最近需要对接了一个 叫 iBeacon 定位信标 硬件设备,这个设备主要的作用是,在信号不好的地方,或者室内实现定位,准确的找到某个东西。就比如 地下停车场,商城里,我们想知道这个停车场的某个…...
Java面试黄金宝典29
1. 什么是普通索引和唯一性索引 定义: 普通索引:是最基本的索引类型,它为数据表中的某一列或多列建立索引,以加快数据的查询速度。它不限制索引列的值重复,允许存在多个相同的值。唯一性索引:在普通索引的基…...
C语言常见3种排序
主要是三种排序方法:冒泡排序、选择排序、插入排序。 文章目录 一、冒泡排序 1.代码: 2.工作原理: 3.具体过程: 二、选择排序 1.代码 2. 工作原理 3.具体过程: 三、插入排序 1.代码 2.工作原理 3.具体过程 总结 一、…...
Nyquist插件基础:LISP语法-自定义函数
1 Nyquist插件基础:LISP语法-自定义函数 在 Nyquist 里,自定义函数能够让你把特定的操作封装起来,实现代码复用和逻辑模块化。下面详细介绍如何在 Nyquist 中定义和使用自定义函数。 1.1.1 1. 基本函数定义 在 Nyquist 中使用 defun 来定义…...
Visual-RFT:视觉强化微调
文章目录 速览摘要1. 引言2. 相关工作大型视觉语言模型(LVLMs)强化学习 3. 方法3.1. 初步带可验证奖励的强化学习DeepSeek R1-Zero和GRPO 3.2. Visual-RFT3.2.1. 视觉感知中的可验证奖励检测任务中的IoU奖励分类任务中的CLS奖励 3.2.2 数据准备 4. 实验4…...
快速入手-基于DRF的过滤、分页、查询配置(十五)
1、过滤需要安装插件 pip install django-filter 2、注册 INSTALLED_APPS [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages",…...
最新Spring Security实战教程(八)Remember-Me实现原理 - 持久化令牌与安全存储方案
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Micro麦可乐的博客 🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战 🌺《RabbitMQ》…...
gcc 链接顺序,静态库循环依赖问题
链接过程由链接器 ld 负责。通常 GCC 间接驱动之。 越底层的库,在链接命令行中的位置应越靠后。 文章目录 链接过程※ 但是对于静态库,链接器仅提取当前未解析符号所需的对象文件,未使用的对象文件会被丢弃。静态库(.a)…...
linux内核漏洞检测利用exp提权
案例一dirtycow(CVE-2016-5159) 有个前置知识就是 获取liunx的内核 hostnamectl uname -a 然后这个内核漏洞进行提权的步骤也是和手工win进行提权差不多 也是需要使用辅助工具在本地进行辅助检测 然后去nomi-sec/PoC-in-GitHub: &#…...
【学Rust写CAD】21 2D 点(point.rs)
源码 //matrix/point.rs use std::ops::Mul; use super::algebraic_units::{Zero, One}; use super::generic::Matrix;/// 点坐标结构体 #[derive(Debug, Clone, Copy, PartialEq)] pub struct Point<X, Y>(Matrix<X, Y, One, Zero, Zero, One>);impl<X, Y>…...
Jmeter的压测使用
Jmeter基础功能回顾 一、创建Jmeter脚本 1、录制新建 (1)适用群体:初学者 2、手动创建 (1)需要了解Jmeter的常用组件 元件:多个类似功能组件的容器(类似于类) 各元件作用 组件…...
C语言--统计输入字符串中的单词个数
输入 输入:大小写字母以及空格,单词以空格分隔 输出:单词个数 代码 如果不是空格且inWord0说明是进入单词的第一个字母,则单词总数加一。 如果是空格,证明离开单词,inWord 0。 #include <stdio.h&g…...
《雷神之锤 III 竞技场》快速求平方根倒数的计算探究
1. 《雷神之锤 III 竞技场》快速求平方根导数源代码 此处先列出其源代码,这段代码的目标是计算一个浮点数平方根的导数,也就是如下形式: f ( x ) 1 x f(x) \frac{1}{\sqrt{x}} f(x)x 1这段代码可以说非常难以理解,尤其是 …...
深入解析 Java 8 Function 接口:函数式编程的核心工具
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Java 8 引入的 java.util.function.Function 接口是函数式编程范式的核心组件之一,本文将全面解析其使用方法,并通过丰富的代码示例演…...
软件重构与项目进度的矛盾如何解决
软件重构与项目进度之间的矛盾可以通过明确重构目标与范围、采用渐进式重构策略、优化项目管理流程、提高团队沟通效率、建立重构意识文化等方式解决。其中,采用渐进式重构策略尤为关键。渐进式重构是指在日常开发过程中,以小步骤持续进行重构࿰…...
redis的geo结构实现[附近商铺]功能
先上结论 geo地理位置算出来是不准的 实现思路 redis6.2支持了经纬度数据格式 支持经纬度检索 需要将redis升级 否则会报错不支持命令 pom文件如果spring-data-redis是2.7.9的boot版本则要改一下支持geo: <dependency><groupId>org.springframework.boot</g…...
W3C XML Schema 活动
W3C XML Schema 活动 概述 W3C XML Schema(XML Schema)是万维网联盟(W3C)定义的一种数据描述语言,用于定义XML文档的结构和约束。XML Schema为XML文档提供了一种结构化的方式,确保数据的一致性和有效性。本文将详细介绍W3C XML Schema的活动,包括其发展历程、主要特点…...
深入解析C++类:面向对象编程的核心基石
一、类的本质与核心概念 1.1 类的基本定义 类是将**数据(属性)与操作(方法)**封装在一起的用户自定义类型,是面向对象编程的核心单元。 // 基础类示例 class BankAccount { private: // 访问控制string owner; …...
MySQL 复制与主从架构(Master-Slave)
MySQL 复制与主从架构(Master-Slave) MySQL 复制与主从架构是数据库高可用和负载均衡的重要手段。通过复制数据到多个从服务器,既可以实现数据冗余备份,又能分担查询压力,提升系统整体性能与容错能力。本文将详细介绍…...
2025年上软考——【数据库系统工程师】考前60天冲刺学习指南!!!
距离2025上半年“数据库系统工程师”考试已经不足两个月了,还没有准备好的小伙伴赶紧行动起来。为了帮助大家更好的冲刺学习,特此提供一份考前60天学习指南。本指南包括考情分析、学习规划、冲刺攻略三个部分,可以参考此指南进行最后的复习要…...
如果数据包的最后一段特别短,如何处理?
在处理TCP粘包/拆包时,如果最后一个数据段特别短(例如仅包含部分包头部或部分数据体),需要通过合理的缓冲区和协议设计来确保数据完整性。以下是具体处理方案: 1. 缓冲区管理:保留不完整数据 核心思想&…...
vue修饰符
在 Vue 中,修饰符是一种特殊的后缀,用于改变指令的默认行为 stop <template><div><h2>vue修饰符</h2><div class"box" click"boxClikc"><button click"btnClick">按钮</button&…...
Redis-06.Redis常用命令-列表操作命令
一.列表操作命令 LPUSH key value1 [value2]: LPUSH mylist a b c d: LRANGE key start stop: LRANGE mylist 0 -1: lrange mylist 0 2: d c b RPOP KEY:移除并返回最后一个元素 RPOP list a LLEN key…...
LTSPICE仿真电路:(二十四)MOS管推挽驱动电路简单仿真
1.Mos管驱动电路基本的拓扑 前面在十一篇的时候学习了MOS管的简单的应用, 这一篇继续补充MOS管的驱动电路。 这个电路应该是最基本的电路仿真,先看电路以及仿真结果,以下仿真结果的电压皆为信号发生器提供的波形图。 看仿真结果比较明了&a…...
GFS论文阅读笔记
文章目录 摘要一、引言二、设计总览2.1、假设2.2、接口2.3、架构2.4 单Master2.5 Chunk大小2.6 元数据2.7 一致性模型 3 系统交互3.1 租约和变更顺序3.2 数据流3.3 原子性的操作:Record append3.4 快照-SNAPSHOT 4. master操作4.1 namespace的管理与锁定4.2 副本的分…...
6. 王道_网络协议
1 网络协议和网络模型 2 TCP/IP协议族概览 2.1 四层模型的各层实体 2.2 协议数据单元的转换 2.3 常见协议以及分层 2.4 ifconfig 2.5 本地环回设备 3 以太网 3.1 以太网和交换机 3.2 以太网帧 MAC地址大小 48位 6字节 IP地址 32位 4字节 port 16位 2字节 3.3 ARP协议 4 IP协…...
《K230 从熟悉到...》颜色识别
《K230 从熟悉到...》颜色识别 颜色识别的基本原理 《庐山派 K230 从熟悉到...》颜色识别 颜色识别是计算机视觉中的重要组件,它允许算法在图像中检测、识别和分类不同颜色。 颜色识别的基本原理 颜色识别的核心是通过分析图像中像素点的颜色信息,从…...
实时内核稳定性 - scheduling while atomic
scheduling while atomic问题 根因:未成对使用获取cpu_id的函数[ 291.881071][ 0] [XW]: type=0x00000003 cpuid=4 time=1725877230 subj...
数据编排与Dagster:解锁现代数据管理的核心工具
在数据驱动的时代,如何高效管理复杂的数据管道、确保数据质量并实现团队协作?本文深入探讨数据编排的核心概念,解析其与传统编排器的差异,并聚焦开源工具Dagster如何以“资产为中心”的理念革新数据开发流程,助力企业构…...
stc8g1k08a定时读取内部1.2v电压值发送到串口1
1189mv #include "stc8g.h"void t0_timer_init(){EA 1;//总中断控制位,启用中断//启用定时器0中断ET0 1;//允许t0中断AUXR | 0x80; //定时器时钟1T模式 t0不频 t1 12分频TMOD & 0xF0; //设置定时器模式TL0 0xCD; //设置定时初始值 205TH0 0xD4; …...
前端开发时的内存泄漏问题
目录 🔍 什么是内存泄漏(Memory Leak)?🚨 常见的内存泄漏场景1️⃣ 未清除的定时器(setInterval / setTimeout)2️⃣ 全局变量(变量未正确释放)3️⃣ 事件监听未清除4️⃣…...
「青牛科技 」GC4931P/4938/4939 12-24V三相有感电机驱动芯片 对标Allegro A4931/瑞盟MS4931
芯片描述: • 芯片工作电压 4.7-36V ( GC4931P ) • 芯片工作电压 7.5-36V ( GC4938/4939 ) • 外置 mos 驱动, NN 结构,内置升压预驱 • QFN5X5-28 封装,带 ePAD 散热&#…...
2025 年山东危化品经营单位考试攻略分享
山东的考试在全省统一标准。理论考试深入考查危化品相关标准规范,如《危险化学品重大危险源辨识》等。对于危化品储存设施的设计与维护知识要求较高。实际操作考核注重在山东化工园区常见的作业场景,如大型储罐区的操作。 报名准备材料与其他省份类似…...
RustDesk 开源远程桌面软件 (支持多端) + 中继服务器伺服器搭建 ( docker版本 ) 安装教程
在需要控制和被控制的电脑上安装软件 github开源仓库地址 https://github.com/rustdesk/rustdesk/releases 蓝奏云盘备份 ( exe ) https://geek7.lanzouw.com/iPf592sadqrc 密码:4esi 中继服务器设置 使用docker安装 sudo docker image pull rustdesk/rustdesk-server sudo…...
CMake 中的置变量
在 CMake 中,变量是存储和传递信息的重要方式。以下是一些常用的 CMake 变量,以表格形式列出,包括它们的名称、含义和常见用途: 变量名称含义常见用途CMAKE_CURRENT_SOURCE_DIR当前处理的 CMakeLists.txt 文件所在的源代码目录的…...
前后端数据序列化:从数组到字符串的旅程(附优化指南)
🌐 前后端数据序列化:从数组到字符串的旅程(附优化指南) 📜 背景:为何需要序列化? 在前后端分离架构中,复杂数据类型(如数组、对象)的传输常需序列化为字符…...