【Linux】第二十站:模拟实现shell

文章目录

  • 一、shell的实现细节
    • 1.shell的一些细节
    • 2.用户名、主机名、工作目录
    • 2.输入命令
    • 3.改为循环
    • 4.切割字符串
    • 5.普通命令的执行
    • 6.内建命令的处理
    • 7.子进程的退出码
    • 8.总结
  • 二、模式实现shell完整代码

一、shell的实现细节

1.shell的一些细节

shell操作系统的一个外壳程序。

shell/bash也是一个进程,执行指令的时候,本质就是自己创建子进程执行的!

2.用户名、主机名、工作目录

当我们进入shell的时候,我们知道会出现一个这样的东西

image-20231123160801267

我们先来实现一下它

其实关于这些信息,在我们的环境变量里面刚好就有

image-20231123160923739

所以这里,我们就不用系统调用了,而是通过环境变量来完成

#include<stdio.h>
#include<stdlib.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
const char* getpwd()
{return getenv("PWD");
}
int main()
{printf(LEFT"%s@%s %s"RIGHT LABLE"\n",getusername(),gethostname(),getpwd());return 0;
}

如上代码所示,我们现在就可以简单的实现打开shell时候要输入命令行的界面

运行结果为

image-20231123163010271

不过由于我们后序要输入一个命令,由于输入结束后本身带有换行,所以我们这里可以去掉这个换行

2.输入命令

我们可以先直接加上一个scanf,这样的话它就会去接收命令了

image-20231123163448241

image-20231123163433680

但是这样对吗,我们可以加上一个打印来看看结果

image-20231123163733195

运行结果如下,我们可以看到其实打印结果并非我们的预期

image-20231123163812684

这其实是因为scanf它本身读取到空格就结束了,所以我们需要一次读取一行

在c语言中有一个函数叫做fgets

image-20231123164043048

char *fgets(char *s, int size, FILE *stream);

这个函数的意思是,从stream这个流中,读取size个字符,放入s中。(s是放在哪里,size是可以访问多少)

如下所示,在这里我们需要注意的是,这里对于27行,我们可以不用对这个大小减一。都是可以的

image-20231123165553240

运行结果为

image-20231123165745667

那么在这里我们有一个问题,这里的s有可能为空吗,即有可能输入失败吗?

在不考虑设备出现问题的情况下,其实是不可能的,即便我们什么都不输入,它也会由于我们按了\n而将这个字符给输入进去。

而且我们发现我们在打印的时候,多了一个空行,这个空行其实就是因为我们输入了这个\n字符所导致的,所以我们可以进行一次调整

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"#define LINE_SIZE 1024
char commandline[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
const char* getpwd()
{return getenv("PWD");
}
int main()
{printf(LEFT"%s@%s %s"RIGHT LABLE" ",getusername(),gethostname(),getpwd());char* s = fgets(commandline,sizeof(commandline),stdin);assert(s);commandline[strlen(commandline) - 1] = '\0';if(s)printf("echo : %s",commandline);return 0;
}

运行结果为

image-20231123182157106

为了方便,我们可以将对应的代码给写为一个函数

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"#define LINE_SIZE 1024
char commandline[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
const char* getpwd()
{return getenv("PWD");
}void Interact(char* cline,int size)
{printf(LEFT"%s@%s %s"RIGHT LABLE" ",getusername(),gethostname(),getpwd());char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int main()
{Interact(commandline,sizeof(commandline));printf("echo : %s",commandline);return 0;
}

然后运行结果为

image-20231123183907749

3.改为循环

我们的前面的代码还有的缺陷就是只能用一次,所以我们不妨直接将其改为一个循环结构

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"#define LINE_SIZE 1024
char commandline[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
const char* getpwd()
{return getenv("PWD");
}void Interact(char* cline,int size)
{printf(LEFT"%s@%s %s"RIGHT LABLE" ",getusername(),gethostname(),getpwd());char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int main()
{int quit = 0;while(!quit){Interact(commandline,sizeof(commandline));printf("echo : %s\n",commandline);}return 0;
}

这样的话,看起来就已经像回事了

image-20231123184505350

4.切割字符串

为了分析这个指令,我们首先要做的就是先将字符串给切割出来。放到一个数组里面去

于是我们可以利用这个函数

 char *strtok(char *str, const char *delim);

第一个参数是待分割的字符串,第二个参数是分割的字符

这个函数每调用一次,便会截取一次字符串。具体操作方法,可见下文

字符串函数详解

最终代码如下所示

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32char commandline[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
const char* getpwd()
{return getenv("PWD");
}void Interact(char* cline,int size)
{printf(LEFT"%s@%s %s"RIGHT LABLE" ",getusername(),gethostname(),getpwd());char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char* argv[])
{int i = 0;argv[i++] = strtok(commandline,DELIM);while(argv[i++] = strtok(NULL,DELIM));return i - 1;
}
int main()
{char* argv[ARGC_SIZE] = {NULL};int quit = 0;while(!quit){Interact(commandline,sizeof(commandline));int argc = splitstring(argv);if(argc == 0) continue;//测试分割for(int i = 0; argv[i]; i++){printf("[%d] : %s\n",i,argv[i]);}}return 0;
}

运行结果为

image-20231123193949608

5.普通命令的执行

由于我们的命令分为内建命令和普通命令。这里我们先考虑普通命令

对于普通命令,我们的基本思路是创建一个子进程,然后通过程序替换来实现。

这里的程序替换根据我们已有的条件最好选择时候execvp/execvpe。环境变量可带可不带,但是v和p一定要带上,因为我们需要从PATH环境变量中去找指令且我们已经有了一个数组了,所以使用v形式更加方便

如下所示,我们就可以实现一个简单的普通命令的执行了

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44char commandline[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
const char* getpwd()
{return getenv("PWD");
}void Interact(char* cline,int size)
{printf(LEFT"%s@%s %s"RIGHT LABLE" ",getusername(),gethostname(),getpwd());char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char* argv[])
{int i = 0;argv[i++] = strtok(commandline,DELIM);while(argv[i++] = strtok(NULL,DELIM));return i - 1;
}
int main()
{extern char** environ;char* argv[ARGC_SIZE] = {NULL};int quit = 0;while(!quit){//2.交互问题,解决命令行Interact(commandline,sizeof(commandline));//3.子串分割问题,解析命令行int argc = splitstring(argv);if(argc == 0) continue;//  for(int i = 0; argv[i]; i++)//  {//      printf("[%d] : %s\n",i,argv[i]);//  }//4.指令的判断(内建命令和普通命令)//5.普通命令的执行pid_t id = fork();if(id < 0){perror("fork");}else if (id == 0){//子进程执行命令execvpe(argv[0],argv,environ);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id){}}}return 0;
}

我们可以简单的使用一下

image-20231123202524230

我们发现这些命令都可以正常的使用。

不过相比原本的shell还是有一些区别的,比如说,我们的shell中像目录文件,可执行程序没有颜色。ll指令还没有办法解析(因为没有支持改名)等问题。

而且还有当我们想要使用内建命令的时候,是没有任何效果的

image-20231123202902572

因为这是子进程在跑这个命令,最后变化的都是子进程的目录等,最后子进程退出了。所以最终就没有任何变化。

我们现在可以将普通指令的执行给封装为一个函数,如下所示

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
const char* getpwd()
{return getenv("PWD");
}void Interact(char* cline,int size)
{printf(LEFT"%s@%s %s"RIGHT LABLE" ",getusername(),gethostname(),getpwd());char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char cline[],char* _argv[])
{int i = 0;_argv[i++] = strtok(cline,DELIM);while(_argv[i++] = strtok(NULL,DELIM));return i - 1;
}void NormalExcute(char* _argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if (id == 0){//子进程执行命令execvpe(_argv[0],_argv,environ);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id){last_code = WEXITSTATUS(status);}}
}
int main()
{while(!quit){//2.交互问题,解决命令行Interact(commandline,sizeof(commandline));//3.子串分割问题,解析命令行int argc = splitstring(commandline,argv);if(argc == 0) continue;//4.指令的判断(内建命令和普通命令)//5.普通命令的执行NormalExcute(argv);}return 0;
}

6.内建命令的处理

对于内建命令,我们则是直接判断即可

比如下面

image-20231124142115463

运行结果最终为,我们可以发现确实更改当前目录了

image-20231124142232901

不过我们会发现,这个前面字符串的显示还是存在一些问题的

这是因为我们当前所使用的是环境变量的方式,我们可以去sprintf函数去修改环境变量。不过这样有很多比较麻烦的事情

最简单的方式是,我们不用环境变量来获取当前工作目录了,我们可以直接用getcwd这个函数来使用,它可以获得当前的工作目录。放入一个数组中。

image-20231124143809245

如下代码所示,完成了cd的内建命令,并且我们实现了ls在显示时候的颜色输出。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
void getpwd()
{getcwd(pwd,sizeof(pwd));
}void Interact(char* cline,int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char cline[],char* _argv[])
{if(strcmp(cline,"") == 0) return 0;int i = 0;_argv[i++] = strtok(cline,DELIM);while(_argv[i++] = strtok(NULL,DELIM));return i - 1;
}void NormalExcute(char* _argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if (id == 0){//子进程执行命令execvpe(_argv[0],_argv,environ);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id){last_code = WEXITSTATUS(status);}}
}
int BuildCommand(char* _argv[],int _argc)
{if(_argc == 2 && strcmp(_argv[0],"cd") == 0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}if(_argc > 0 && strcmp(_argv[0],"ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}
int main()
{while(!quit){//2.交互问题,解决命令行Interact(commandline,sizeof(commandline));//3.子串分割问题,解析命令行int argc = splitstring(commandline,argv);if(argc == 0) continue;        //4.指令的判断(内建命令和普通命令)int n = BuildCommand(argv,argc);//5.普通命令的执行if(!n) NormalExcute(argv);}return 0;
}

我们再来实现一下其他的内建命令,比如echo命令,如果我们直接使用的话,相当于是调用了子进程中的echo命令,它会出现这样的问题,无论我们输入什么,最终也依然输出什么

image-20231124164608751

而且如果我们导入环境变量的时候,直接使用export也是不可以的

image-20231124170820416

因为这是创建了子进程才导入的,我们不希望创建子进程,所以就需要内建命令

直接在这里putenv即可

image-20231124171038176

这样的话,就有了这个环境变量了

image-20231124171258629

不过上面的其实还存在很多问题

因为如果我们再次使用一下export的话,我们会发现原来加入的环境变量消失了

image-20231124173731157

这其实是因为我们前面的代码是将_argv中的内容直接导入到了环境变量中。这里的导入不是说拷贝一份。而是将这个地址写入到了环境变量所对应的空间中。而我们后面再次导入的时候已经将这块的内容修改了。所以说原来的就不见了。总之环境变量表保存的不是字符串,而是地址。

所以我们需要专门开辟一块空间用来存储环境变量

image-20231124180738933

然后我们最后将echo给实现一下即可。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
void getpwd()
{getcwd(pwd,sizeof(pwd));
}void Interact(char* cline,int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char cline[],char* _argv[])
{if(strcmp(cline,"") == 0) return 0;int i = 0;_argv[i++] = strtok(cline,DELIM);while(_argv[i++] = strtok(NULL,DELIM));return i - 1;
}void NormalExcute(char* _argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if (id == 0){//子进程执行命令execvpe(_argv[0],_argv,environ);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id){last_code = WEXITSTATUS(status);}}
}int BuildCommand(char* _argv[],int _argc)
{if(_argc == 2 && strcmp(_argv[0],"cd") == 0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0],"export") == 0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0],"echo") == 0){if(*_argv[1] == '$'){char* val = getenv(_argv[1] + 1);if(val) printf("%s\n",val);}else {printf("%s\n",_argv[1]);}return 1;}if(_argc > 0 && strcmp(_argv[0],"ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}
int main()
{while(!quit){//2.交互问题,解决命令行Interact(commandline,sizeof(commandline));//3.子串分割问题,解析命令行int argc = splitstring(commandline,argv);if(argc == 0) continue;//4.指令的判断(内建命令和普通命令)int n = BuildCommand(argv,argc);//5.普通命令的执行if(!n) NormalExcute(argv);}return 0;
}

7.子进程的退出码

当子进程退出的时候,它会有一个退出码。

所以我们可以对于echo的内建命令在加上一个打印退出码的操作

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
void getpwd()
{getcwd(pwd,sizeof(pwd));
}void Interact(char* cline,int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char cline[],char* _argv[])
{if(strcmp(cline,"") == 0) return 0;int i = 0;_argv[i++] = strtok(cline,DELIM);while(_argv[i++] = strtok(NULL,DELIM));return i - 1;
}void NormalExcute(char* _argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if (id == 0){//子进程执行命令execvpe(_argv[0],_argv,environ);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id){last_code = WEXITSTATUS(status);}}
}int BuildCommand(char* _argv[],int _argc)
{if(_argc == 2 && strcmp(_argv[0],"cd") == 0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0],"export") == 0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0],"echo") == 0){if(strcmp(_argv[1],"$?") == 0){printf("%d\n",last_code);last_code = 0;}else if(*_argv[1] == '$'){char* val = getenv(_argv[1] + 1);if(val) printf("%s\n",val);}else {printf("%s\n",_argv[1]);}return 1;}if(_argc > 0 && strcmp(_argv[0],"ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}
int main()
{while(!quit){//2.交互问题,解决命令行Interact(commandline,sizeof(commandline));//3.子串分割问题,解析命令行int argc = splitstring(commandline,argv);if(argc == 0) continue;//4.指令的判断(内建命令和普通命令)int n = BuildCommand(argv,argc);//5.普通命令的执行if(!n) NormalExcute(argv);}return 0;
}

运行效果如下

image-20231124182832088

8.总结

所以,当我们进行登录的时候,系统就要启动一个shell进程,那么我们shell本身的环境变量是从哪里来的???

其实在我们用户的目录下,就一个bash_profile文件

image-20231124183111243

它里面的内容是这样的,这里面就会帮助我们导入环境变量

image-20231124183138019

类似的,还有.bashrc文件等

所以说

当用户登录的时候,shell会读取用户目录下的.bashprofile文件,里面保存了导入环境变量的方式!

二、模式实现shell完整代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];const char* getusername()
{return getenv("USER");
}
const char* gethostname()
{return getenv("HOSTNAME");
}
void getpwd()
{getcwd(pwd,sizeof(pwd));
}void Interact(char* cline,int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);char* s = fgets(cline,size,stdin);(void)s;assert(s);commandline[strlen(cline) - 1] = '\0';
}
int splitstring(char cline[],char* _argv[])
{if(strcmp(cline,"") == 0) return 0;int i = 0;_argv[i++] = strtok(cline,DELIM);while(_argv[i++] = strtok(NULL,DELIM));return i - 1;
}void NormalExcute(char* _argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if (id == 0){//子进程执行命令execvpe(_argv[0],_argv,environ);exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid == id){last_code = WEXITSTATUS(status);}}
}int BuildCommand(char* _argv[],int _argc)
{if(_argc == 2 && strcmp(_argv[0],"cd") == 0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0],"export") == 0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0],"echo") == 0){if(strcmp(_argv[1],"$?") == 0){printf("%d\n",last_code);last_code = 0;}else if(*_argv[1] == '$'){char* val = getenv(_argv[1] + 1);if(val) printf("%s\n",val);}else {printf("%s\n",_argv[1]);}return 1;}if(_argc > 0 && strcmp(_argv[0],"ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}
int main()
{while(!quit){//2.交互问题,解决命令行Interact(commandline,sizeof(commandline));//3.子串分割问题,解析命令行int argc = splitstring(commandline,argv);if(argc == 0) continue;//4.指令的判断(内建命令和普通命令)int n = BuildCommand(argv,argc);//5.普通命令的执行if(!n) NormalExcute(argv);}return 0;
}

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

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

相关文章

笔记:pycharm当有多个plt.show()时候,只显示第一个plt.show()

import matplotlib.pyplot as plt import numpy as np# 创建数据 x np.linspace(0, 10, 100) y1 np.sin(x) y2 np.cos(x) y3 np.tan(x) y4 np.exp(x)# 创建一个2x2的子图网格 # fig plt.figure() fig,((ax1, ax2), (ax3, ax4)) plt.subplots(nrows2, ncols2, figsize(8,…

【matlab程序】matlab画台风符号和实例应用

【matlab程序】matlab画台风符号和实例应用 没有看文献&#xff0c;不知道文献中的符号什么样子&#xff0c;据我理解为这样子的&#xff1a; 因此&#xff0c;按照自己的理解做了这期。 结果浏览&#xff1a; 台风符号一切可改&#xff0c;可细细改。可是我不发论文&#xf…

Oracle 中的操作符

1.union:对两个结果集进行并集操作&#xff0c;不包括重复行&#xff0c;同时进行默认规则的排序&#xff1b; SELECT * FROM emp WHERE sal < 1500 UNION SELECT * FROM emp WHERE sal BETWEEN 1000 AND 2000 order by 1 2.union All&#xff1a;对两个结果集进行并集操…

[C/C++]数据结构 堆排序(详细图解)

一:前言 在[C/C]数据结构 堆的详解中,介绍了什么是堆,并且完成了堆的实现和一系列接口,包括向上调整法和向下调整法等,接下来小编介绍一个有点量级的排序方法------堆排序,时间复杂度为O(n*lgn) 二:堆排序详解 2.1 方法介绍 1.首先将待排序数组建为大堆,此时堆顶元素就为数组…

给国外客户价格报低了怎么办

前一段时间有一个单子的货发出去了&#xff0c;被朋友提醒才发现自己报错了价格&#xff0c;造成了亏损&#xff0c;而报错价格的原因并不是自己看错了或者是抄错了价格&#xff0c;而是自己的脑子里记错了产品的价格列表。 如果不是朋友善意的提醒&#xff0c;大概我会一直错…

【心得】XXE漏洞利用个人笔记

XML中关于DTD类型(内部(SYSTEM)的和外部(PUBLIC)的区别) xxe的利用 XML Entity 实体注入 当程序处理xml文件时&#xff0c;没有禁止对外部实体的处理&#xff0c;容易造成xxe漏洞 危害 主流是任意文件读取 XML 文件 一般表示带有结构的数据 祖父 3个叔父 8个堂弟堂妹 …

python-opencv 人脸68点特征点检测

python-opencv 人脸68点特征点检测 不是很难&#xff0c;主要还是掉包&#xff0c;来看一下代码啊&#xff1a; # coding: utf-8 # 导包 import numpy as np import dlib import cv2class face_emotion(object):def __init__(self):# 人脸检测器对象&#xff0c;通过它拿到人…

Rust UI开发(三):iced如何打开图片(对话框)并在窗口显示图片?

注&#xff1a;此文适合于对rust有一些了解的朋友 iced是一个跨平台的GUI库&#xff0c;用于为rust语言程序构建UI界面。 这是一个系列博文&#xff0c;本文是第三篇&#xff0c;前两篇的链接&#xff1a; 1、Rust UI开发&#xff08;一&#xff09;&#xff1a;使用iced构建…

【LeetCode:1457. 二叉树中的伪回文路径 | 二叉树 + DFS +回文数】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

使用 OpenCV 发现圆角矩形的轮廓

OpenCV - 如何找到圆角矩形的矩形轮廓? 问题: 在图像中,我试图找到矩形对象的圆角轮廓。然而,我对两者的尝试 HoughLinesP 并 findContours 没有产生预期的结果。 我的目标是找到一个类似于以下形状的矩形: 。 代码: import cv2 import matplotlib.pyplot as plt…

能让PDF看起来像是扫描件的Look Scanned

什么是 Look Scanned ? Look Scanned 是一个能够让 PDF 看起来就像是扫描件一样的纯前端网站。你再也不需要麻烦地打印之后扫描了&#xff0c;你所需要的就是鼠标点几下。 这是个挺有意思的软件&#xff0c;但是老苏不确定什么场景下会用到这个软件&#xff0c;如果不想自己搭…

OBS Studio 30.0 正式发布:支持 WebRTC

导读OBS Studio 30.0 已正式发布。此版本移除了对 Ubuntu 20.04、Qt 5 和 FFmpeg 4.4 之前版本的支持。 OBS Studio 30.0 已正式发布。此版本移除了对 Ubuntu 20.04、Qt 5 和 FFmpeg 4.4 之前版本的支持。 主要变化包括&#xff1a; 支持 WebRTC&#xff08;详情查看 OBS Stu…

【YOLOv5入门】目标检测

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍YOLOv5入门-目标检测的任务、性能指标、yolo算法基本思想、yolov5网络架构图。 后续会继续分享其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 上一篇…

Android 单元测试初体验

Android 单元测试初体验 前言一、单元测试是什么&#xff1f;二、简单使用1.依赖2.单元测试代码简单模版及解释 总结 前言 当初在学校学安卓的时候&#xff0c;老师敢教学进度&#xff0c;翻到单元测试这一章节的时候提了两句&#xff0c;没有把单元测试当重点讲&#xff0c;只…

详解Python对Excel处理

Excel是一种常见的电子表格文件格式&#xff0c;广泛用于数据记录和处理。Python提供了多个第三方库&#xff0c;可以方便地对Excel文件进行读写、数据操作和处理。本文将介绍如何使用Python对Excel文件进行处理&#xff0c;并提供相应的代码示例和详细说明。 一、安装第三方库…

java设计模式学习之【抽象工厂模式】

文章目录 引言抽象工厂模式简介定义与用途实现方式&#xff1a; 使用场景优势与劣势工厂模式在spring中的应用银行和贷款服务示例代码地址 引言 在我们之前的讨论中&#xff0c;我们探索了工厂方法模式——一种简化单一产品创建的设计模式。现在&#xff0c;我们将视角转向抽象…

2023年亚太杯APMCM数学建模大赛A题水果采摘机器人的图像识别

2023年亚太杯APMCM数学建模大赛 A题 水果采摘机器人的图像识别 原题再现 中国是世界上最大的苹果生产国&#xff0c;年产量约3500万吨。同时&#xff0c;中国也是世界上最大的苹果出口国&#xff0c;世界上每两个苹果中就有一个是中国出口的&#xff0c;世界上超过六分之一的…

Star History 十月开源精选 |AI for Postgres

在 2023 年 Stack Overflow 开发者调查中&#xff0c;Postgres 顶替了 MySQL 被评为最受欢迎的数据库。一个重要因素应该是 Postgres 支持扩展&#xff1a;可扩展的架构 Postgres 仍然由社区拥有&#xff0c;Postgres 生态近年来蓬勃发展。 扩展可以看作是内置功能&#xff0c…

跟着chatgpt学习|1.spark入门

首先先让chatgpt帮我规划学习路径&#xff0c;使用Markdown格式返回&#xff0c;并转成思维导图的形式 目录 目录 1. 了解spark 1.1 Spark的概念 1.2 Spark的架构 1.3 Spark的基本功能 2.spark中的数据抽象和操作方式 2.1.RDD&#xff08;弹性分布式数据集&#xff09; 2…

Callable、Future和FutrueTask详解

一、Callable介绍 1.1 Runnable介绍 Runnable是一个接口&#xff0c;里面声明了run方法。但是由于run方法返回值类型为void&#xff0c;所以在执行完成任务后&#xff0c;无法返回任何结果。 FunctionalInterface public interface Runnable {public abstract void run(); }…