极简shell制作

🌎自定义简单shell制作

(ps: 文末有完整代码)

文章目录:

自定义简单shell制作

    简单配置Linux文件

    自定义Shell编写

      命令行解释器
      获取输入的命令
      字符串分割
      子进程进行进程替换

      内建命令处理

        cd命令处理
        路径显示问题
        export命令处理
        echo 命令处理

    自定义Shell源码


前言:

  通过我们之前所学Linux知识以及C语言的知识,到目前为止,我们完全可以独立完成简易shell的制作,那么话不多说,开始今天的话题!

在这里插入图片描述


🚀简单配置Linux文件

  首先,再开始项目之前,需要先简单配置一下Linux文件,选择一个位置,创建本次项目的目录:

mkdir myshell#名字随意,这里方便区分命名myshell

在这里插入图片描述
  如图所示在该目录下,我们还需要创建 makefile文件C的源文件

touch makefile#或者 Makefile
touch myshell.c#其他名字都行,后缀是.c即可

  因为我们构建的是C语言项目,所以makefile内文件配置也很简单,使用vim(vim介绍及其使用)打开makefile文件:

vim makefile

配置makefile文件

cc=-std=c99
mybin:file.cgcc -o $@ $^ -g $(cc)
.PHONY:clean
clean:rm -f mybin 

  保存退出之后,就可以开始编写我们C语言代码啦,配置还是很简单的。


🚀自定义Shell编写

✈️命令行解释器

  首先,我们根据常用的shell行为分析:

在这里插入图片描述

  常用 shell 都有叫做 命令行解释器 的东西(上图红框),而命令行解释器其实就是 由不同的字符串所构成 的,可以拆分成三部分:

第一部分是用户,随后在@之后是主机名字符串,第三部分是 当前所处工作目录。

  我们曾经学过一个获取环境变量的接口 getenv

在这里插入图片描述
  因为上述三个部分皆可以在系统的环境变量中找到,所以我们可以使用 getenv 接口,将环境变量导出,拿到字符串作为我们自定义shell的命令行解释器:

#include<stdio.h>
#include<stdlib.h>char* HostName()//获取主机名
{char* hostname = getenv("HOSTNAME");//获取主机名环境变量if(hostname) return hostname;else return "None";
}char* UserName()//获取用户名
{char* hostname = getenv("USER");//从用户名环境变量的获取用户名if(hostname) return hostname;else return "None";
}char* CurrentWorkDir()//获取当前工作目录
{char* hostname = getenv("PWD");//获取当前路径if(hostname) return hostname;else return "None";
}int main()
{//命令行提示符编写printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());return 0;
}

效果展示:

在这里插入图片描述

  效果还是很不错的,有些细节仍需该进,会在后面慢慢改善。


✈️ 获取输入的命令

  有了命令行解释器,我们在 shell 上还有输入命令这一行为,那么我们自定义shell就需要接收输入的命令行字符串。

  那么我们需要考虑的就是输入命令的情况:1、单个命令输入。2、命令+选项输入。 其实他们的区别很明显,一种 字符串不带空格,一种字符串 带一个或多个空格,比如:

在这里插入图片描述
  使用C语言的scanf显然是行不通的,在这里我推荐使用 fgets 接口,可以接收输入的空格:

在这里插入图片描述
在这里插入图片描述

  返回值表示输入的字符串,前两个参数不用说,但是最后一个参数可以能很多人不太理解,我们在学C语言的时候,可能大家学过三个流:stdin、stdout、stderr 流:

在这里插入图片描述
  当然,不了解也不要紧,我们仅需要知道 我们输入需要从 stdin 流中获取即可,表示从标准输入内获取信息。

  函数第一个参数表示 接收字符串的位置,第二个参数表示 接收大小,我们定义一个数组,用来接收输入的命令行参数:

#define CMD_SIZE 1024//定义数组大小
char commandline[CMD_SIZE];//接收命令行参数的数组

  那么我们就需要把接收的命令行参数放入到 commandline数组里。在 Shell中,一行命令输入完成之后将直接生效。所以在命令输入完成之后,我们有必要给commandline数组结尾,也就是添加 ‘\0’:

int main()
{char commandline[CMD_SIZE];//命令行提示符编写printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());fgets(commandline, CMD_SIZE, stdin);//获取输入命令,将其放在commandline数组内commandline[strlen(commandline) - 1] = '\0';//结束本行命令,记得包含string头文件printf("cmd line: %s\n", commandline);return 0;
}

在这里插入图片描述

  将shell运行起来之后,我们输入的命令就可以被检测并输入到字符数组里面了。

  为了让代码更具可读性,我们可以将输出命令行解释器和输入命令接收操作封装在一个函数内,再在main函数调用:

void Interactive(char out[], int size)//接口封装
{printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());fgets(out, CMD_SIZE, stdin);out[strlen(out) - 1] = '\0';
}int main()
{char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //使用接口调用即可printf("cmd line: %s\n", commandline);return 0;
}

✈️ 字符串分割

  我们平时在shell 中输入的命令选项是不确定的,有时候有多个选项,有时候有一个选项,有时候没有选项,而shell会根据不同的选项来执行不同的动作。

  所以我们有必要将字符串切割,而我们之前在学习命令行参数的时候,提到过main函数参数有一个叫做 argv命令行参数表(const char* argv[]),那么我们就可以创建一个命令行参数表来接收每一个子串。

  那么如何切割字符串呢?这里有一个C语言的接口可供大家使用 strtok

在这里插入图片描述

  第一个参数表示 指向要分割的字符串第一次调用时需要指定这个参数以后的调用要继续分割同一个字符串,就应该把参数 str 设置为 NULL

  第二个参数表示 以什么字符或字符串为结尾进行切割,返回值表示 返回切割后的子串,如果查找不到切割点了,就会返回NULL。

  而我们命令行都是以 空格作为分隔符 的,所以,空格字符就是该接口的第二个参数了,而这个接口会被频繁调用,所以,我们直接使用宏定义空格:

#define MAX_ARGC 64//argv数组的大小
#define SEP " "//表示空格 

   argv是一个指针数组,所以每一个元素都可以指向一段字符串,同时,我们希望argv数组下标能一一对应,所以需要一个键值作为索引:

int i = 0;
argv[i++] =  strtok(commandline, SEP);

  但是,我们输入的命令很可能不止一个空格,所以,我们需要使用循环控制子串的切割,让argv数组的每一个元素都能对应到切割的字符串:

while(argv[i++] = strtok(NULL, SEP));//注意这里用的是=并非==

  并且,这样一个好处就是 在argv数组最后是以 NULL结尾的。同样,为了代码的可读性,我们可以将切割子串的功能封装为一个接口,并且 argv数组放在全局位置,因为根据以往的经验,父子进程可能都会需要argv数组:

void Split(char in[], int size)
{int i = 0;argv[i++] = strtok(in, SEP);//进行子串切割while(argv[i++] = strtok(NULL, SEP));
}int main()
{char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);for(int j = 0; argv[j]; ++j)//测试命令行参数是否切割成功{printf("argv[%d]:%s\n", j, argv[j]);}return 0;
}

在这里插入图片描述


✈️ 子进程进行进程替换

  前面我们学习过,程序替换成功时,后续程序就不会往下走,又因为进程之间具有独立性,所以需要创建一个子进程来完成进程替换这件事情。

pid_t id = fork();
if(id == 0)
{//子进程,执行程序替换exec*();//进程替换函数,待定exit(0);
}
pid_t rid = waitpid(id, NULL, 0);//阻塞等待子进程
printf("run done, rid: %d\n", rid);

  如果要执行命令,那就需要进行程序替换,但是程序替换我们介绍了七个接口,使用哪一个接口会比较好呢?根据前面所写的代码,我们已经有了 argv 这张命令行参数表,所以使用接口一定是要带 ‘v’ 的。

  带 ‘v’ 的接口也有三个,execvp 接口是最好的选择,为什么大家可以自己思考一下,很简单:

	execvp(argv[0], argv);//根据命令在环境变量里查找,在根据选项做出对应的动作

  同样为了代码可读性,我们将其封装为一个接口:

void Execute()
{pid_t id = fork();if(id == 0){//child process execvp(argv[0], argv);exit(1);}pid_t rid = waitpid(id, NULL, 0);printf("run done, rid: %d\n", rid);
}

在这里插入图片描述

  但是这里我们自定义shell只能运行一次,为了让命令一直能运行下去,我们就得循环执行:

int main()
{while(1){char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);//执行命令Execute();}return 0;
}

在这里插入图片描述
  这样,我们的shell就初具雏形了!


✈️内建命令处理
🚩 cd命令处理

  我们来看这样一个现象:

在这里插入图片描述
  命名我已经切换目录很多次了,但是为什么目录没有改变呢?其实这是因为我们一直是在使用子进程执行命令的,所以仅仅是子进程一直在切换目录,父进程的目录却一直不变。

  所以向cd 这种命令,我们就不能交给子进程操作,而这样的命令我们称为 内建命令

  为了解决内建命令,我们可以 把cd 命令来单独处理,用一个接口封装。在执行命令之前,检测输入的命令是否是内建命令,如果是,则处理内建命令,如果不是则直接跳过,执行其他命令。

  我们根据封装接口的返回值判断是否为cd 命令,在选择跳过还是处理命令,那么在接口内部的实现。

int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令{ret = 1;//处理cd 命令}return ret;
}

  处理cd 命令之前我们得先了解cd 命令有哪些特殊表示,cd 命令无外乎:cd -,cd ~,cd /工作目录或文件/,cd。其中只有cd 是不带空格的,其行为是:

在这里插入图片描述
  如果cd 不带任何选项,那么其行为就是 切换到家目录。知道了这种特殊情况之后就好办了,除了这个不带选型的命令以外,其他的命令全部要根据选项处理,那么就要根据选项切换目录了,我们可以使用 chdir 接口切换目录:

在这里插入图片描述

const char* Home()
{return getenv("HOME");//从HOME环境变量获取当前系统的家目录
}int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令{ret = 1;//处理cd 命令char* target = argv[1];if(!target) target = Home();chdir(target);}return ret;
}int main()
{while(1){char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);//处理内建命令int n = BuildinCmd();if(n) continue;//执行命令Execute();}return 0;
}

在这里插入图片描述
  这样cd 命令自由的使用了,切换目录也丝毫不费力了。


🚩 路径显示问题

  这里还有一个很明显的错误行为,我的命令行解释器的路径从开始就没有变过,其实是因为我们没有更新PWD环境变量,我们可以手动给当前进程更新环境变量,使用一个数组存储当前目录,再使用 putenv 将环境变量导出:

在这里插入图片描述

char pwd[CMD_SIZE];//定义全局数组int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令{ret = 1;//处理cd 命令char* target = argv[1];if(!target) target = Home();chdir(target);snprintf(pwd, CMD_SIZE, "PWD=%s", target);//将改变后的路径以 PWD=...的形式写入进pwd数组putenv(pwd);//此时数组内容为PWD=...此时putenv就可以更改环境变量了}return ret;
}

在这里插入图片描述
  刚才的问题解决了…吗??并没有,我们使用cd …或者cd -这种命令的时候路径就显示不出来了,虽然说我们这么写的代码不对,但是我们思路是对的,更新PWD环境变量,那么我们只好使用 Linux 提供的 getcwd 接口了:

在这里插入图片描述

  这个接口可以 获取当前工作目录的绝对路径。

int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){ret = 1;char* target = argv[1];if(!target) target = Home();chdir(target);char tmp[1024];getcwd(tmp, 1024);//获取当前工作目录snprintf(pwd, CMD_SIZE, "PWD=%s", tmp);putenv(pwd);}return ret;
}

在这里插入图片描述

  这样就可以正常切换目录了。


🚩 export命令处理

  当我们在 自定义 Shell 中导入一个新的环境变量时,也是由子进程进行程序替换完成这件事的,所以,当我们使用hell进行env时,是看不到导入的环境变量的:

在这里插入图片描述

  所以,export也是一个内建命令,那么我们就需要在对应的接口里处理export命令:

char env[CMD_SIZE];//全局数组,接收环境变量int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){//...}else if(strcmp("export", argv[0]) == 0)//处理export内建命令{ret = 1;if(argv[1]){strcpy(env, argv[1]);//将需要导入的环境变量放到数组当中putenv(env);//使用接口导入环境变量}}return ret;
}

  此处理方法与cd命令类似,仔细看注释也是很好理解的:

在这里插入图片描述


🚩 echo 命令处理

  我们曾经在shell中演示过 echo的各种用法,其中有 echo $? 表示上一个进程的退出码,除此之外,还有:echoecho $env_nameecho ...,这些特殊情况我们依旧需要处理。

  首先,比较特殊的就是 echo $?这个命令,这个命令需要显示上一个进程的退出码,而获取进程的退出码,这个时候我们就需要先在全局范围内设置退出码变量:

int lastcode = 0;//退出码

  退出码是在执行完进程之后返回的结果,所以必定要在 execute 接口内接收执行命令的退出码(进程退出码相关知识):

int lastcode = 0;void Execute()
{pid_t id = fork();if(id == 0){//child process execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);//使用阻塞等待if(rid == id) lastcode = WEXITSTATUS(status);//保存退出码
}

  而echo 命令也是一个内建命令。它是在 shell 程序中提供的命令,用于在终端输出文本或环境变量的值。所以我们也需要在内建命令中处理echo命令:

int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){ret = 1;//...}else if(strcmp("export", argv[0]) == 0){ret = 1;//...}else if(strcmp("echo", argv[0]) == 0)//处理echo命令{ret = 1;if(argv[1] == NULL)//单单echo命令{printf("\n");//仅仅换行}else{if(argv[1][0] == '$')//argv是一个指针数组,相当于 *(argv[1]),这样获取对你理解有帮助{if(argv[1][1] == '?')//同理当$后是?的情况{printf("%d\n", lastcode);//输出退出码lastcode = 0;//退出码重置}else //为获取环境变量的字符串{char* e = getenv(argv[1] + 1);if(e) printf("%s\n", e);}}else //单纯对终端进行输出{printf("%s\n", argv[1]);}}   }return ret;
}

在这里插入图片描述


🚀自定义Shell源码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>#define CMD_SIZE 1024
#define MAX_ARGC 64
#define SEP " "int lastcode = 0;char* argv[MAX_ARGC];
char pwd[CMD_SIZE];
char env[CMD_SIZE];const char* HostName()
{char* hostname = getenv("HOSTNAME");//获取主机名环境变量if(hostname) return hostname;else return "None";
}const char* UserName()
{char* hostname = getenv("USER");//从用户名环境变量的获取用户名if(hostname) return hostname;else return "None";
}const char* CurrentWorkDir()
{char* hostname = getenv("PWD");//获取当前路径if(hostname) return hostname;else return "None";
}char* Home()
{return getenv("HOME");
}void Interactive(char out[], int size)
{printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());fgets(out, CMD_SIZE, stdin);out[strlen(out) - 1] = '\0';
}void Split(char in[], int size)
{int i = 0;argv[i++] = strtok(in, SEP);//进行子串切割while(argv[i++] = strtok(NULL, SEP));if(strcmp(argv[0], "ls") == 0){argv[i - 1] = "--color";argv[i] = NULL;}
}void Execute()
{pid_t id = fork();if(id == 0){//child process execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status);//保存退出码
}int BuildinCmd()
{int ret = 0;if(strcmp("cd", argv[0]) == 0){ret = 1;char* target = argv[1];if(!target) target = Home();chdir(target);char tmp[CMD_SIZE];getcwd(tmp, CMD_SIZE);snprintf(pwd, CMD_SIZE, "PWD=%s", tmp);putenv(pwd);}else if(strcmp("export", argv[0]) == 0){ret = 1;if(argv[1]){strcpy(env, argv[1]);putenv(env);}}else if(strcmp("echo", argv[0]) == 0){ret = 1;if(argv[1] == NULL)//单单echo命令{printf("\n");//仅仅换行}else{if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n", lastcode);lastcode = 0;}else {char* e = getenv(argv[1] + 1);if(e) printf("%s\n", e);}}else {printf("%s\n", argv[1]);}}   }return ret;
}int main()
{while(1){char commandline[CMD_SIZE];Interactive(commandline, CMD_SIZE); //对命令行字符串切割Split(commandline, CMD_SIZE);//处理内建命令int n = BuildinCmd();if(n) continue;//执行命令Execute();}return 0;
}

  自定义Shell目前就到此为止,当然你可以根据你的喜好去在此基础上拓展更多内容,更加完善这个Shell。


在这里插入图片描述
  如果这篇文章对您有用的话,还望三连支持博主~~

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

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

相关文章

28.Gateway-网关过滤器

GatewayFilter是网关中提供的一种过滤器&#xff0c;可以多进入网关的请求和微服务返回的响应做处理。 GatewayFilter(当前路由过滤器&#xff0c;DefaultFilter) spring中提供了31种不同的路由过滤器工厂。 filters针对部分路由的过滤器。 default-filters针对所有路由的默认…

opencv基础篇 ——(九)图像几何变换

图像几何变换是通过对图像的几何结构进行变换来改变图像的形状、大小、方向或者透视关系。常见的图像几何变换包括缩放、旋转、平移、仿射变换和透视变换等。下面对这些几何变换进行简要介绍&#xff1a; 矩阵的转置&#xff08;transpose &#xff09;&#xff1a; 对于图像来…

微服务之SpringCloud AlibabaNacos服务注册和配置中心

一、概述 1.1注册中心原理 在微服务远程调用的过程中&#xff0c;包括两个角色&#xff1a; 服务提供者&#xff1a;提供接口供其它微服务访问&#xff0c;比如item-service 服务消费者&#xff1a;调用其它微服务提供的接口&#xff0c;比如cart-service 在大型微服务项目…

符合医药行业规范的液氮罐运输和存储温度监测解决方案

API原料药、冻干物质和人体样本必须在玻璃相中以尽可能低的温度运输和存储。专门的低温容器——干式液氮罐——可通过液氮&#xff08;LN2&#xff09;将温度保持在-196 C。由于温度极低&#xff0c;低温容器的温度数据监测不仅具有挑战性&#xff0c;而且还需要更复杂的过程&a…

Linux下的常用基本指令

基本指令 前言ls 指令语法功能常用选项举例注意要点关于拼接关于 -a关于文件ls与/的联用ls与根目录ls与任意文件夹ls与常用选项与路径 ls -d与ls -ldls与ll pwd命令语法功能常用选项注意要点window与Linux文件路径的区别家目录 cd 指令语法功能举例注意要点cd路径.. .相对路径与…

Cesium116版本安装跑错,注意Node版本

SyntaxError: Unexpected token ?? at Loader.moduleStrategy (internal/modules/esm/translators.js:149:18) 无法解析ES node.js本本过低 nvm use无效NVM踩坑不完全指南&#xff0c;nvm use没有*_nvm use 无效-CSDN博客

决策树模型示例

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个决策树模型pytorch程序,最后打印5个条件分别的影响力。 一 决策树模型是一种非参数监督学习方法&#xff0c;主要…

centos7 openresty lua 自适应webp和缩放图片

目录 背景效果图准备安装cwebp等命令&#xff0c;转换文件格式安装ImageMagick&#xff0c;压缩文件下载Lua API 操控ImageMagick的依赖包 代码参考 背景 缩小图片体积&#xff0c;提升加载速度&#xff0c;节省流量。 效果图 参数格式 &#xff1a; ?image_processformat,…

Llama-7b-Chinese本地推理

Llama-7b-Chinese 本地推理 基础环境信息&#xff08;wsl2安装Ubuntu22.04 miniconda&#xff09; 使用miniconda搭建环境 (base) :~$ conda create --name Llama-7b-Chinese python3.10 Channels:- defaults Platform: linux-64 Collecting package metadata (repodata.js…

Linux下软硬链接和动静态库制作详解

目录 前言 软硬链接 概念 软链接的创建 硬链接的创建 软硬链接的本质区别 理解软链接 理解硬链接 小结 动静态库 概念 动静态库的制作 静态库的制作 动态库的制作 前言 本文涉及到inode和地址空间等相关概念&#xff0c;不知道的小伙伴可以先阅读以下两篇文章…

智慧校园建设指导

智慧校园是一个庞大的业务系统&#xff0c;他涉及到校园事务的各个方面&#xff0c;包括教务&#xff0c;考务&#xff0c;教工&#xff0c;学工&#xff0c;办公&#xff0c;科研等。因此&#xff0c;建设符合学校业务需求的智慧校园平台&#xff0c;不仅需要做到认真负责外&a…

C语言位运算详解(移位操作符、位操作符)

目录 一、整数在内存中的存储方式 二、移位操作符 1、左移操作符 2、右移操作符 a.逻辑右移 b.算数右移 ps、移位操作符使用警告 三、位操作符 用例代码&#xff1a; a.按位与&#xff08;&&#xff09; b.按位或&#xff08;|&#xff09; c.按位异或&#xf…

【笔试强训】Day4 --- Fibonacci数列 + 单词搜索 + 杨辉三角

文章目录 1. Fibonacci数列2. 单词搜索3. 杨辉三角 1. Fibonacci数列 【链接】&#xff1a;Fibonacci数列 解题思路&#xff1a;简单模拟题&#xff0c;要最少的步数就是找离N最近的Fibonacci数&#xff0c;即可能情况只有比他小的最大的那个Fibonacci数以及比他大的最小的那…

《软件设计师教程:计算机网络浅了解计算机之间相互运运作的模式》

​ 个人主页&#xff1a;李仙桎 &#x1f525; 个人专栏: 《软件设计师》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ​ ⛺️前言&#xff1a;各位铁汁们好啊&#xff01;&#xff01;&#xff01;&#xff0c;今天开始继续学习中级软件设计师考试相关的内容&#xff0…

Java高阶私房菜:JVM垃圾回收机制及算法原理探究

目录 垃圾回收机制 什么是垃圾回收机制 JVM的自动垃圾回收机制 垃圾回收机制的关键知识点 初步了解判断方法-引用计数法 GCRoot和可达性分析算法 什么是可达性分析算法 什么是GC Root 对象回收的关键知识点 标记对象可回收就一定会被回收吗&#xff1f; 可达性分析算…

【免费源码下载】完美运营版商城 虚拟商品全功能商城 全能商城小程序 智慧商城系统 全品类百货商城php+uniapp

简介 完美运营版商城/拼团/团购/秒杀/积分/砍价/实物商品/虚拟商品等全功能商城 干干净净 没有一丝多余收据 还没过手其他站 还没乱七八走的广告和后门 后台可以自由拖曳修改前端UI页面 还支持虚拟商品自动发货等功能 挺不错的一套源码 前端UNIAPP 后端PHP 一键部署版本&am…

52832 不使用micro_lib ,同时使用AC6编译器且使用printf问题

1. 因为我的工程用AC6是因为要跑自己的C 和 TensorFlow lite micro. 所以是C&#xff0c;C混合的工程&#xff0c;但是一直没法打印&#xff0c;所以写一个总结。 基本说明&#xff1a; micro_lib这种情况不要选&#xff0c;因为存在C文件 第一个坑&#xff1a; 第二个坑&…

windows 避免电脑强制息屏

许多打工人的电脑被公司设置了隔一段时间没有操作&#xff0c;就会自动息屏&#xff0c;如何避免这种事发生呢 方案一 自动操作鼠标的软件 如果能自由安装软件&#xff0c;可以下载自动移动鼠标的软件&#xff0c;设置鼠标每隔多长时间做一次什么操作&#xff0c;防止锁屏 方…

LIUNX:系统编程动态库加载(1)

目录 操作系统角度理解 如何加载 怎么管理库 编址 操作系统角度理解 如何加载 首先main想要运行&#xff0c;首先要为main创建task_struct和mm_struct&#xff0c;然后将main的代码和数据加载到内存&#xff0c;将main的代码通过页表映射到mm_struct的正文代码段&#xff0…

leetcode-比较版本号-88

题目要求 思路 1.因为字符串比较大小不方便&#xff0c;并且因为需要去掉前导的0&#xff0c;这个0我们并不知道有几个&#xff0c;将字符串转换为数字刚好能避免。 2.当判断到符号位的时候加加&#xff0c;跳过符号位。 3.判断数字大小&#xff0c;来决定版本号大小 4.核心代…