模拟实现Bash

news2024/11/27 22:27:03

模拟实现Bash

  • 1.Bash基本认识
  • 2.Bash实现
  • 3.添加细节
  • 4.内置命令
  • 5.完整代码

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【Linux的学习】
📝📝本篇内容:Bash的基本认识;bash的实现;添加细节;内置命令;完整代码
⬆⬆⬆⬆上一篇:Linux多线程(下)
💖💖作者简介:轩情吖,请多多指教(>> •̀֊•́ ) ̖́-

1.Bash基本认识

什么是bash呢?其实bash本质上就是我们的命令行解释器,它其实就相等于WindowsGUI就是通过图形化界面的点击来进行访问,我们可以通过bash来和操作系统进行交互,bash把我们的命令解析给Linux内核,Linux内核又把反馈结果给bash,bash又解析给我们显示。这样做有什么好处呢?首先如果直接进行和内核进行交互的话,可能比较难,成本比较高,因此有了bash,通过bash来进行交互会更好。同时我们的bash会内核起到保护作用,作为用户和操作系统交互的中间软件层,防止对内核造成破坏,即使出问题,也只是我们的bash出问题,不会伤及核心。
我们平时所使用的命令以及跑的代码其实都是bash的子进程,这是为了保护我们的bash,不至于代码跑崩溃了,而导致bash也崩了

2.Bash实现

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

char* commend_list[10]={NULL};
int main()
{
    char commend[1024];//将命令存入这个数组
    while(1)
    {
        cout<<"[lnb@VM-16-17centos 当前目录]";
        fgets(commend,1024,stdin);
        //去掉\n
        commend[strlen(commend)-1]='\0';
        //拆分命令
        commend_list[0]=strtok(commend," ");
        int i=1;
        while(commend_list[i++]=strtok(NULL," "))
        {}

        // i=0;
        // while(commend_list[i])        
        // {
        //     cout<<commend_list[i++]<<endl;
        // }
        
        //创建子进程
        pid_t ret=fork();
        if(ret>0)
        {
            //父进程,进行等待
            int status=0;
            waitpid(ret,&status,0);
            cout<<"退出信号:"<<(status&0x7f)<<",退出码:"<<((status>>8)&0xff)<<endl;
            cout<<"退出信号:"<<WTERMSIG(status)<<",退出码:"<<WEXITSTATUS(status)<<endl;
        }
        else
        {
            //子进程,进行程序替换
            execvp(commend_list[0],commend_list);
            exit(-1);
        }
    }
    return 0;
}

首先我们来剖析一下上面的代码,因为比较简单,就直接给出代码了
第一步我们先模拟打印出bash命令行解释器的提示,然后通过fgets来从标准输入里面读取命令
第二步给把我们存储命令的数组中的\n去除,然后把命令进行拆分放入一个指针数组中,在这一步中用到了一个函数strtok
在这里插入图片描述
第三步就是通过fork来进行父子进程分别工作,这就是前面提到的bash创建子进程来保证自身的安全
第四步就是详细编写父子进程的代码,对于父进程而言就是等待子进程结束,对于子进程而言就是使用到了进程替换,来执行对于的命令行,这样即使子进程崩溃了,也不会影响到父进程bash

3.添加细节

这个时候可以使用大部分的命令了,但是我们的ls和系统自带的ls不太一样
在这里插入图片描述可以发现我们的ls下不会展示高亮,这是为什么呢? 在这里插入图片描述
通过查看可以发现他其实真正使用时会自带命令选项
此时我们再看另一个现象,我们使用ll
在这里插入图片描述
可以发现无法使用,其实这个和前一个问题是一样的
在这里插入图片描述
这个时候我们可以对我们的命令进行一个补充

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

char* commend_list[10]={NULL};
int main()
{
    char commend[1024];//将命令存入这个数组
    while(1)
    {
        cout<<"[lnb@VM-16-17centos 当前目录]";
        fgets(commend,1024,stdin);
        //去掉\n
        commend[strlen(commend)-1]='\0';
        //拆分命令
        commend_list[0]=strtok(commend," ");
        int i=1;
        while(commend_list[i++]=strtok(NULL," "))
        {}

        // i=0;
        // while(commend_list[i])        
        // {
        //     cout<<commend_list[i++]<<endl;
        // }
        
     

        if(strcmp(commend_list[0],"ls")==0)
        {
            commend_list[i++]="--color=auto";
        }
        if(strcmp(commend_list[0],"ll")==0)
        {
            commend_list[0]="ls";
            commend_list[1]="-l";
            commend_list[2]="--color=auto";
            commend_list[3]=NULL;
        }


        //创建子进程
        pid_t ret=fork();
        if(ret>0)
        {
            //父进程,进行等待
            int status=0;
            waitpid(ret,&status,0);
            cout<<"退出信号:"<<(status&0x7f)<<",退出码:"<<((status>>8)&0xff)<<endl;
            cout<<"退出信号:"<<WTERMSIG(status)<<",退出码:"<<WEXITSTATUS(status)<<endl;
        }
        else
        {
            //子进程,进行程序替换
            execvp(commend_list[0],commend_list);
            exit(-1);
        }
    }
    return 0;
}

4.内置命令

接下来我们要讲一下内置命令,这是实现bash的重点,也是我要详细讲的。首先我们先在我们自己的bash下使用cd和export命令看一下现象
在这里插入图片描述
可以发现我们的cd完全没有效果,其实这就是内建命令造成的,在真正的bash实现中,这是bash自己来执行的,而我们在自己实现的过程中,是由我们的子进程来执行的,作为两个不同的进程,有独立性,并不是我们的父进程bash来执行的,因此导致了没有效果,此时我们也要进行特殊处理,使用chdir函数

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

char* commend_list[10]={NULL};
int main()
{
    char commend[1024];//将命令存入这个数组
    while(1)
    {
        cout<<"[lnb@VM-16-17centos 当前目录]";
        fgets(commend,1024,stdin);
        //去掉\n
        commend[strlen(commend)-1]='\0';
        //拆分命令
        commend_list[0]=strtok(commend," ");
        int i=1;
        while(commend_list[i++]=strtok(NULL," "))
        {}

        // i=0;
        // while(commend_list[i])        
        // {
        //     cout<<commend_list[i++]<<endl;
        // }
        
     

        if(strcmp(commend_list[0],"ls")==0)
        {
            commend_list[i++]="--color=auto";
        }
        if(strcmp(commend_list[0],"ll")==0)
        {
            commend_list[0]="ls";
            commend_list[1]="-l";
            commend_list[2]="--color=auto";
            commend_list[3]=NULL;
        }
        if(strcmp(commend_list[0],"cd")==0)
        {
            if(commend_list[1]!=NULL)
            chdir(commend_list[1]);
            continue;
        }


        //创建子进程
        pid_t ret=fork();
        if(ret>0)
        {
            //父进程,进行等待
            int status=0;
            waitpid(ret,&status,0);
            cout<<"退出信号:"<<(status&0x7f)<<",退出码:"<<((status>>8)&0xff)<<endl;
            cout<<"退出信号:"<<WTERMSIG(status)<<",退出码:"<<WEXITSTATUS(status)<<endl;
        }
        else
        {
            //子进程,进行程序替换
            execvp(commend_list[0],commend_list);
            exit(-1);
        }
    }
    return 0;
}

接下来要处理的就是export,对于它,在编写这个代码的时候我也发现了很多以前没注意的问题,我们现在来讲一讲
首先是putenv这个函数,它其实是一个添加环境变量的函数,但是它仅仅是对使用它的程序有效,在程序里做的环境变量更改不会反映到外部环境,这是因为变量的值不会从子进程(你的程序)传播到父进程(shell)。通过putenv添加的环境变量通常是临时的,它们仅在当前进程及其子进程中可见。当进程结束时,这些环境变量将消失。
我这里有一段代码可以验证一下,就简单的使用一个putenv,看看外界会不会有什么变化

#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
    putenv("val=100");
    while(1);//使用循环来保证环境变量不会随着程序结束而结束

    return 0;
}

在这里插入图片描述
可以发现并没有影响,但是会对进程的子进程产生影响,我这边还有一个例子,可以看一下

//test.cc
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
extern char** environ;
int main(int argc,char* argv[],char *envp[])
{
  //std::cout<<(void*)envp<<std::endl;
  //std::cout<<(void*)environ<<std::endl;
  putenv("val=111111111111111111111111111111111111111111111111111");
  for(int i=0;environ[i];i++)
  {
    std:: cout<<environ[i]<<std::endl;
  }

  std::cout<<std::endl<<std::endl<<std::endl<<std::endl;
  
int ret=fork();
if(ret==0)
{
  execl("/home/lnb/linux-l/24_review/24_11_13/replace","./replace");//程序替换成我们自己写的程序来查看环境变量
}
waitpid(ret,NULL,0);//父进程负责等待子进程

while(1)
{

}


  return 0;
}

//relpace.cc
#include <iostream>
using namespace std;
int main(int argc,char* argv[],char* envp[])
{
  for(int i=0;envp[i];i++)  
  {
    cout<<envp[i]<<endl;
  }




  return 0;
}

在这里插入图片描述
可以看到我们的自己的程序和子进程都打印出来了新添加的环境变量
不知道你是否有注意到我的test.cc中注释了两行代码,分别是打印对于environ和envp的值,按理来说,他们的使用和结果是相等的,但是实际测试下来并不是,它们的地址是相同的,但是打印出来的结果不同,使用envp时,程序本身不能打印出添加的val,不知道是不是系统bug造成的,因此推荐使用environ,具体见下面结果

//test.cc
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
extern char** environ;
int main(int argc,char* argv[],char *envp[])
{
  std::cout<<(void*)envp<<std::endl;//打印地址
  std::cout<<(void*)environ<<std::endl;//打印地址
  putenv("val=111111111111111111111111111111111111111111111111111");
  for(int i=0;envp[i];i++)
  {
    std:: cout<<envp[i]<<std::endl;
  }

  std::cout<<std::endl<<std::endl<<std::endl<<std::endl;//和子进程打印的结果进行分割
  
int ret=fork();
if(ret==0)
{
  execl("/home/lnb/linux-l/24_review/24_11_13/replace","./replace");//程序替换成我们自己写的程序来查看环境变量
}
waitpid(ret,NULL,0);//父进程负责等待子进程

while(1)
{

}


  return 0;
}

在这里插入图片描述
不知道有没有知道的小伙伴这是为什么?可以一起在评论区一起讨论一下(我查询了AI,不知道是否准确,见下图)
在这里插入图片描述
并且我们平时使用export来添加的环境变量或者是通过export修改的环境变量都是临时的,当关闭这个shell会话时,通过export设置的环境变量就会失效,重新打开后,bash会重新从配置文件中读取,并不会包含之前添加的环境变量,想要使环境变量永久存在,需要将它添加到配置文件中(当前用户的bash配置文件,如~/.bashrc 或 ~/.bash_profile))

接下来我们回归主题,讲一下我们自己实现bash时对于export的处理,先来看一下直接使用export的现象
在这里插入图片描述
因此我们要对他进行特殊处理

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

extern char** environ;

char* commend_list[10]={NULL};
char  environment[20][30]={0};//用来存储用户自己的环境变量
int envir_index=0;
int main()
{
    char commend[1024];//将命令存入这个数组
    while(1)
    {
        cout<<"[lnb@VM-16-17centos 当前目录]";
        fgets(commend,1024,stdin);
        //去掉\n
        commend[strlen(commend)-1]='\0';
        //拆分命令
        commend_list[0]=strtok(commend," ");
        int i=1;
        while(commend_list[i++]=strtok(NULL," "))
        {}

        // i=0;
        // while(commend_list[i])        
        // {
        //     cout<<commend_list[i++]<<endl;
        // }
        
     

        if(strcmp(commend_list[0],"ls")==0)
        {
            commend_list[i++]="--color=auto";
        }
        if(strcmp(commend_list[0],"ll")==0)
        {
            commend_list[0]="ls";
            commend_list[1]="-l";
            commend_list[2]="--color=auto";
            commend_list[3]=NULL;
        }
        if(strcmp(commend_list[0],"cd")==0)
        {
            if(commend_list[1]!=NULL)
            chdir(commend_list[1]);
            continue;
        }

        if(strcmp(commend_list[0],"export")==0)
        {   
            //我们要对这个它添加的环境变量进行一个保存,
            //因为如果不保存会出现环境变量指针消失的问题
            //commend_list[1]中存储了我们的添加的环境变量
            //但是当我们下一回合重新读取命令的时候,他就会
            //被覆盖,因为我们的commend_list数组中的指针都是
            //从commend中出来的,这样的话就会导致结果有问题,
            //无法正常显示
            if(commend_list[1]!=NULL)
            {
                strcpy(environment[envir_index],commend_list[1]);
                putenv(environment[envir_index++]);
                continue;
            }
        }
        
        if(strcmp(commend_list[0],"env")==0)
        {
                //之所以要进行特殊处理env是因为我们要打印显示的是bash父进程本身的环境变量
                //而不是我们的子进程
            for(int i=0;environ[i];i++)
            {
                cout<<environ[i]<<endl;
            }
            continue;
        }
        //创建子进程
        pid_t ret=fork();
        if(ret>0)
        {
            //父进程,进行等待
            int status=0;
            waitpid(ret,&status,0);
            cout<<"退出信号:"<<(status&0x7f)<<",退出码:"<<((status>>8)&0xff)<<endl;
            cout<<"退出信号:"<<WTERMSIG(status)<<",退出码:"<<WEXITSTATUS(status)<<endl;
        }
        else
        {
            //子进程,进行程序替换
            execvp(commend_list[0],commend_list);
            exit(-1);
        }
    }
    return 0;
}

在这里插入图片描述
在这里面我们要格外的注意对于export的处理以及和env的处理,我们一般用户自定义的环境变量,在bash中要用户自己来维护,在使用putenv时,应注意内存管理问题,避免释放传递给它的字符串的内存空间,也不要进行覆盖,否则会出bug。对于我们的env同样也要做特处理,因为我们需要的是mybash的环境变量
其实我们的大部分环境变量的命令都是内建命令

5.完整代码

在下面的完整代码中,我也加入了echo等其他部分的命令的处理,也进行了注释,相信大家也能看得懂

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

extern char** environ;

static int ret_code=0;//用来记录上一个进程的退出码

char* commend_list[10]={NULL};
char  environment[20][30]={0};//用来存储用户自己的环境变量
int envir_index=0;
int main()
{
    char commend[1024];//将命令存入这个数组
    while(1)
    {
        cout<<"[lnb@VM-16-17centos 当前目录]";
        fgets(commend,1024,stdin);
        //去掉\n
        commend[strlen(commend)-1]='\0';
        //拆分命令
        commend_list[0]=strtok(commend," ");
        int i=1;
        while(commend_list[i++]=strtok(NULL," "))
        {}

        // i=0;
        // while(commend_list[i])        
        // {
        //     cout<<commend_list[i++]<<endl;
        // }
        
     

        if(strcmp(commend_list[0],"ls")==0)
        {
            commend_list[i++]="--color=auto";
        }
        if(strcmp(commend_list[0],"ll")==0)
        {
            commend_list[0]="ls";
            commend_list[1]="-l";
            commend_list[2]="--color=auto";
            commend_list[3]=NULL;
        }
        if(strcmp(commend_list[0],"cd")==0)
        {
            if(commend_list[1]!=NULL)
            chdir(commend_list[1]);
            continue;
        }

        if(strcmp(commend_list[0],"export")==0)
        {   
            //我们要对这个它添加的环境变量进行一个保存,
            //因为如果不保存会出现环境变量指针消失的问题
            //commend_list[1]中存储了我们的添加的环境变量
            //但是当我们下一回合重新读取命令的时候,他就会
            //被覆盖,因为我们的commend_list数组中的指针都是
            //从commend中出来的,这样的话就会导致结果有问题,
            //无法正常显示
            if(commend_list[1]!=NULL)
            {
                strcpy(environment[envir_index],commend_list[1]);
                putenv(environment[envir_index++]);
                continue;
            }
        }
        
        if(strcmp(commend_list[0],"env")==0)
        {
                //之所以要进行特殊处理env是因为我们要打印显示的是bash父进程本身的环境变量
                //而不是我们的子进程
            for(int i=0;environ[i];i++)
            {
                cout<<environ[i]<<endl;
            }
            continue;
        }

        if(strcmp(commend_list[0],"echo")==0&&(*(commend_list[1]))=='$')//对于查看环境变量值的处理
        {
            if(commend_list[1][1]=='?')//第一个命令选项中第二个字符
            {
                cout<<ret_code<<endl;
                continue;
            }
            const char* str=NULL;
            str=getenv(commend_list[1]+1);//此处的commend_list[1]的值为char*,指向第一个命令选项,+1后指向$后面
            printf("%s:%s\n",commend_list[1]+1,str);
            continue;
        }


        //创建子进程
        pid_t ret=fork();
        if(ret>0)
        {
            //父进程,进行等待
            int status=0;
            waitpid(ret,&status,0);
            cout<<"退出信号:"<<(status&0x7f)<<",退出码:"<<((status>>8)&0xff)<<endl;
            cout<<"退出信号:"<<WTERMSIG(status)<<",退出码:"<<WEXITSTATUS(status)<<endl;
            ret_code=((status>>8)&0xff);
        }
        else
        {
            //子进程,进行程序替换
            execvp(commend_list[0],commend_list);
            exit(-1);
        }
    }
    return 0;
}

🌸🌸模拟实现bash的知识大概就讲到这里啦,博主后续会继续更新更多Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

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

相关文章

sql注入报错分享(mssql+mysql)

mysql mysql的报错内容比较多 网上也有比较多的 这里重复的就不多介绍了。一笔带过 溢出类 bigint 当超过mysql的整形的时候&#xff0c;就会导致溢出&#xff0c;mysql可能会将错误信息带出。这里user()是字母默认为0 取反以后1可能就会导致异常。 报错特征 BIGINT UNSIG…

Hadoop3.3.6集群安装

Hadoop3.3.6 三节点集群安装 准备工作 准备三台机器&#xff0c;大小为4c8g&#xff0c;主节点为 8c16g。并需要保证网络连通性&#xff0c;每台机器都相互ping一下 1、关闭网络防火墙 # 查看网络防火墙状态 sudo systemctl status firewalld # 立即停止 firewalld sudo sy…

如何制作项目网页

一、背景 许多论文里经常会有这样一句话Supplementary material can be found at https://hri-eu.github.io/Lami/&#xff0c;这个就是将论文中的内容或者补充视频放到一个网页上&#xff0c;以更好的展示他们的工作。因此&#xff0c;这里介绍下如何使用前人提供的模板制作我…

JVM调优篇之JVM基础入门AND字节码文件解读

目录 Java程序编译class文件内容常量池附录-访问标识表附录-常量池类型列表 Java程序编译 Java文件通过编译成class文件后&#xff0c;通过JVM虚拟机解释字节码文件转为操作系统执行的二进制码运行。 规范 Java虚拟机有自己的一套规范&#xff0c;遵循这套规范&#xff0c;任…

已存大量数据的mysql库实现主从各种报错----解决方案(看评论)

背景何谓“先死后生”本文使用技术1、实施流程图2、实施2.1、数据库备份2.2、搭建Mysql的Master-Slave2.2.1、准备工作2.2.2、开始部署2.2.3、账号配置2.2.4、slave 同步配置2.2.5、验证 2.3、Master做数据恢复 结语 背景 计划对已有大量数据的mysql库的主从搭建&#xff0c;使…

数据结构 【双向哨兵位循环链表】

链表的结构分为8中&#xff0c;其实搞懂了单链表和双向哨兵位循环链表&#xff0c;这部分的知识也就掌握的差不多了。双向哨兵位循环链表的结构如下&#xff1a; 下面我从0构建一个双向哨兵位循环链表。 1、准备工作 构建节点结构体&#xff0c;双向循环链表的每一个…

高级AI记录笔记(五)

学习位置 B站位置&#xff1a;红豆丨泥 UE AI 教程原作者Youtube位置&#xff1a;https://youtu.be/-t3PbGRazKg?siRVoaBr4476k88gct素材自备 改良近战AI格挡行为 把近战AI的格挡行为从行为树中单独一个任务分块中给删除掉&#xff0c;因为我们希望敌人在受到伤害后立即进行…

彻底解决 macOS 下Matplotlib 中文显示乱码问题

彻底解决 macOS 下Matplotlib 中文显示乱码问题 在使用 Python 的 Matplotlib 库进行数据可视化时&#xff0c;中文字符的显示常常会出现乱码问题&#xff0c;尤其在 macOS 系统上。在网上找了一大堆方法&#xff0c;花了很久&#xff0c;发现不是要安装各种字体就是要改配置&…

11.25.2024刷华为OD

文章目录 HJ76 尼科彻斯定理&#xff08;观察题&#xff0c;不难&#xff09;HJ77 火车进站&#xff08;DFS&#xff09;HJ91 走格子方法&#xff0c;&#xff08;动态规划&#xff0c;递归&#xff0c;有代表性&#xff09;HJ93 数组分组&#xff08;递归&#xff09;语法知识…

突破性算法:让无人机集群在狭窄空间内穿针引线

导读 在建筑救援、森林搜索等任务中&#xff0c;无人机集群经常会遇到狭窄空间限制和动态障碍物变化等挑战。这些挑战会导致集群内部冲突&#xff0c;或在执行任务时因避让动态障碍物而导致系统混乱。实际应用场景和任务的严格特征往往使得全局搜索难以优化&#xff0c;而局部避…

Python中的简单爬虫

文章目录 一. 基于FastAPI之Web站点开发1. 基于FastAPI搭建Web服务器2. Web服务器和浏览器的通讯流程3. 浏览器访问Web服务器的通讯流程4. 加载图片资源代码 二. 基于Web请求的FastAPI通用配置1. 目前Web服务器存在问题2. 基于Web请求的FastAPI通用配置 三. Python爬虫介绍1. 什…

【Shell】运维快捷键及shell各种不同实际运维场景

一&#xff0c;控制台使用技巧 1&#xff0c;操作快捷键 Ctrlr :可以快速查找历史命令 Ctrll :可以清理控制台屏幕 Ctrla \ Ctrle :移动光标到命令行首\行尾 Ctrlw \ Ctrlk :删除光标之前\之后的内容 2&#xff0c;VIM文件编辑快捷键 快捷键ZZ :文件保存并退出 3&#xff…

SlickGrid复选框

分析 1、先在columns首列添加复选框&#xff1b; 2、在SlickGrid注册刚添加的复选框&#xff1b; 3、添加复选框变化事件&#xff1b; 4、注册按钮点击事件&#xff0c;点击获取已选中的行。 展示 代码 复选框样式&#xff08;CSS&#xff09; .slick-cell-checkboxsel {bac…

基于单片机的智慧小区人脸识别门禁系统

本设计基于单片机的智慧小区人脸识别门禁系统。由STM32F103C8T6单片机核心板、显示模块、摄像头模块、舵机模块、按键模块和电源模块组成。可以通过摄像头模块对进入人员人脸数据进行采集&#xff0c;识别成功后&#xff0c;舵机模块动作&#xff0c;模拟门禁打开&#xff0c;门…

【小白学机器学习33】 大数定律python的 pandas.Dataframe 和 pandas.Series基础内容

目录 0 总结 0.1pd.Dataframe有一个比较麻烦琐碎的地方&#xff0c;就是引号 和括号 0.2 pd.Dataframe关于括号的原则 0.3 分清楚几个数据类型和对应的方法的范围 0.4 几个数据结构的构造关系 list → np.array(list) → pd.Series(np.array)/pd.Dataframe 1 python 里…

Edge浏览器保留数据,无损降级退回老版本+禁止更新教程(适用于Chrome)

3 个月前阿虚就已经写文章告警过大家&#xff0c;Chromium 内核的浏览器将在 127 以上版本开始限制仍在使用 Manifest V2 规范的扩展&#xff1a;https://mp.weixin.qq.com/s/v1gINxg5vMh86kdOOmqc6A 像是 IDM、油猴脚本管理器、uBblock 等扩展都会受到影响&#xff0c;后续将无…

基于Spring Boot的装饰工程管理系统论文

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统装饰工程项目信息管理难度大&#xff0c;容错率低&#x…

路由器的工作原理

网络拓扑结构 主机A&#xff1a; IP地址&#xff1a;10.1.0.1/16 MAC地址&#xff1a;MACA 主机B&#xff1a; IP地址&#xff1a;10.2.0.1/16 MAC地址&#xff1a;MACB 网关G0/0/0&#xff1a; IP地址&#xff1a;10.1.0.2/16 MAC地址&#xff1a;MACC 网关G0/0/1&#…

【深度学习|特征增强模块】FFN(前馈神经网络)和E_FFN(增强型前馈神经网络)是transformer特征增强的重要组成部分!

【深度学习|特征增强模块】FFN&#xff08;前馈神经网络&#xff09;和E_FFN&#xff08;增强型前馈神经网络&#xff09;是transformer特征增强的重要组成部分&#xff01; 【深度学习|特征增强模块】FFN&#xff08;前馈神经网络&#xff09;和E_FFN&#xff08;增强型前馈神…

51单片机从入门到精通:理论与实践指南(一)

单片机在智能控制领域的应用已非常普遍&#xff0c;发展也很迅猛&#xff0c;学习和使用单片机的人员越来越多。虽然新型微控制器在不断推出&#xff0c;但51单片机价格低廉、易学易用、性能成熟&#xff0c;在家电和工业控制中有一定的应用&#xff0c;而且学好了51单片机&…