Linux学习之路 -- 进程篇 -- 自定义shell的编写

前面介绍了进程程序替换的相关知识,接下来,我将介绍如何基于前面的知识,编写一个简单的shell,另外本文的所展示的shell可能仅供参考。

目录

<1>获取用户的输入和打印命令行提示符

<2>切割字符串

<3>执行这个命令

<4>判断内建命令

<1>cd命令

<2>export

<3>echo命令

<5>全部代码


<1>获取用户的输入和打印命令行提示符

首先我们打开shell时,一般都会看到一个命令行提示符

目前光标卡在当前位置不动,就是在等待用户输入一段命令,这一段命令会被当成字符串。所以我们首先要做的工作就是获取命令行提示符和用户的输入。

在获取命令行提示符前,我们需要回顾一下命令行提示符的组成

虽然用户名、主机名和路径都能通过系统接口进行获取,但是我们也可以通过环境变量来获取这些数据。而我们可以通过getenv接口,就能获得特定环境变量的内容。

下面演示一下输出命令行提示符

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>


const char* HostName()
{
    char* hostname = getenv("HOSTNAME");
    if(hostname)
    {
        return hostname;
    }
    else
    {
        return "NONE";
    }
}
const char* UserName()
{
    char* username = getenv("USER");
    if(username)
    {
        return username;
    }
    else
    {
        return "NONE";
    }
}
const char* Currentdir()
{
    char* dirname = getenv("PWD");
    if(dirname)
    {
        return dirname;
    }
    else
    {
        return "NONE";
    }
}
int main(int argc, char* argv[],char* env[])
{
    printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());
    return 0;
}

 这里需要注意的是,HOSTNAME在一些操作系统中可能没有,这可能是很多原因导致的。所以我们可以在获取HOSTNAME这个环境变量之前手动添加。

运行效果

接下来再解决一下用户输入问题

我们可以用一个字符数组先储存命令行参数。至于输入,我们可以使用fgetc,不能用scanf函数,scanf在读到空串时,会自动停止读取。所以我们使用fgetc从缓冲区里面读取(当然也可以使用其他的接口,这里我以fgets为例)。下面介绍一下fgetc接口

第一个参数表示,存放缓冲区数据的数组,第二个表示数组大小,第三个表示输入流指针(这个暂不做介绍,涉及文件系统内容,这里直接写stdin即可)。

下面演示一下代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#define SIZE 1024

const char* HostName()
{
    char* hostname = getenv("HOSTNAME");
    if(hostname)
    {
        return hostname;
    }
    else
    {
        return "NONE";
    }
}
const char* UserName()
{
    char* username = getenv("USER");
    if(username)
    {
        return username;
    }
    else
    {
        return "NONE";
    }
}
const char* Currentdir()
{
    char* dirname = getenv("PWD");
    if(dirname)
    {
        return dirname;
    }
    else
    {
        return "NONE";
    }
}
int main(int argc, char* argv[],char* env[])
{
    char command[SIZE];
    printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());
    fgets(command,SIZE,stdin);
    printf("command line: %s\n",command);
    return 0;
}

运行结果

这里我们会发现,中间printf打印完一条语句后,多了一行空白,这行空白是其实是因为我们在输入时也会敲回车键,所以实际在执行printf语句时,会有两个\n,这就会造成中间多了一行空白。这里我们只需要将command数组里面存储的最后一个字符由‘\n’变成‘\0’或0即可。所以只需在fgets语句后添加下面一条语句即可。

    command[strlen(command) - 1] = 0;

无需担心command长度为零的情况,因为无论如何你都要输入一个‘\n’。所以command的长度至少也是1。但是我们需要对空串进行一下判断,如果是空串,后面的代码就不需要执行了(这条下面封装时会用到)。

调整后的结果

这里我们可以对命令行提示符的显示和用户输入功能封装一下。

<2>切割字符串

如果我们要执行一个命令,就必需调用程序替换的接口,而在之前的介绍里面,程序替换接口的参数都是没有空格的字符串,而且都是一个一个分开的。所以我们必需要将字符串切割成一个一个子串,然后传递给这些接口。所以第一步就是先分割子串,那我们该如何切割呢?下面介绍一个函数strtok

str参数表示要切割的串,delim表示以什么为分割符。需要注意的是,第一次调用该函数时,str参数传需要切割的字符串指针,第二次传NULL,delim不变,就是” “(空格)。

下面演示一下代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>

#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];



const char* HostName()
{
    char* hostname = getenv("HOSTNAME");
    if(hostname)
    {
        return hostname;
    }
    else
    {
        return "NONE";
    }
}
const char* UserName()
{
    char* username = getenv("USER");
    if(username)
    {
        return username;
    }
    else
    {
        return "NONE";
    }
}
const char* Currentdir()
{
    char* dirname = getenv("PWD");
    if(dirname)
    {
        return dirname;
    }
    else
    {
        return "NONE";
    }
}
int interactive(char* command)
{
    printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());
    fgets(command,SIZE,stdin);
    command[strlen(command) - 1] = 0;
    return strlen(command);
}
void Split(char* command)
{
    int i = 0;
    argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量
    while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
}
int main()
{
    char command[SIZE];
    //1.获取用户指令并打出命令行提示符
    int ret = interactive(command);//如果是空串,则下面的代码就不必执行了
    if(!ret)
    {
    }
    else
    {
        //2.切割命令行
        Split(command);
        for(int i = 0; argv[i];i++)
        {
            printf("argv[%d]:%s\n",i,argv[i]);
        }
    }
    return 0;
}

Split函数中的while循环条件变成argv[i++] = strtok(NULL, SYM),可以直接把strtok切割的子串放进argv里面,而且在切完后,strtok会返回NULL,我们之前了解过,命令行参数列表的结尾就是NULL。同时我们argv[ i ]设为空后,条件判断也就不成立了,此时也就跳出循环并 i++。

运行结果

从结果上来看,上述的代码逻辑并没有什么问题。

<3>执行这个命令

在切割完命令行后,我们就需要依照argv来执行命令。在这里我们依照程序替换的方式来进行执行命令。不过在这之前,我们需要创建一个子进程。这是因为我们的shell需要关注用户和机器之间的交互,如果我们直接让shell执行命令,那交互性能就会变差。万一程序崩溃了,shell也会无法运行。所以这里我们就需要让子进程替我们执行命令。

在创建完子进程后,我们就可以让子进程执行对应的任务。执行任务的过程其实并不难,就是选择一个合适的程序替换接口即可。这里我们选择execvp这个接口是最好的,因为该接口的参数是最少的,并且中间不用做处理。在执行完后,我们需要等待回收子进程,这里我使用的是阻塞等待。

下面是演示的代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];



const char* HostName()
{
    char* hostname = getenv("HOSTNAME");
    if(hostname)
    {
        return hostname;
    }
    else
    {
        return "NONE";
    }
}
const char* UserName()
{
    char* username = getenv("USER");
    if(username)
    {
        return username;
    }
    else
    {
        return "NONE";
    }
}
const char* Currentdir()
{
    char* dirname = getenv("PWD");
    if(dirname)
    {
        return dirname;
    }
    else
    {
        return "NONE";
    }
}
int interactive(char* command)
{
    printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());
    fgets(command,SIZE,stdin);
    command[strlen(command) - 1] = 0;
    return strlen(command);
}
void Split(char* command)
{
    int i = 0;
    argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量
    while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        execvp(argv[0],argv);
    }
    pid_t rid = waitpid(id,NULL,0);
    // if(rid > 0)
    // {
    //     printf("wait success, pid: %d\n",rid);
    // }
}
int main()
{
    while(1)//让shell持续运行
    {
        char command[SIZE];
        //1.获取用户指令并打出命令行提示符
        int ret = interactive(command);
        if(!ret)
        {
            continue;
        }
        //2.切割命令行
        Split(command);
        //3.执行命令
        Execute();
    }
    // for(int i = 0; argv[i];i++)
    // {
    //     printf("argv[%d]:%s\n",i,argv[i]);
    // }
    return 0;
}

运行结果:

此时,我们运行普通的命令已经没有什么问题了,但是一旦我们运行类似于cd .. 命令时,就会出现无法执行的情况。其实这是因为决定当前路径的父进程,这里我们使用子进程执行cd 命令,但是父进程的路径并没有改变,所以当我们执行cd命令后,再执行pwd命令,会发现路径并没有改变。像这样的情况还有很多,这些命令本就不应该交由子进程执行,而是让父进程直接执行。而这些命令叫做内建命令。我们在执行命令前要加一个步骤,那就是判断内建命令,并让父进程去执行这个命令。

<4>判断内建命令

<1>cd命令

由于一些命令是需要父进程自己执行的,所以我们就需要修改执行顺序。首先我们先以cd 命令为例,修改一下原来的shell。在此之前我们需要先了解一下chdir接口,这个接口是修改当前工作路径的。

path就是修改后的路径。使用这个命令的原因是因为cd 命令后可能是没有东西的,这会直接进入该用户的家目录。所以我们必需要通过这个接口来实现工作路径的切换。

在编写判断内建命令的函数时,一共分为两步,一是判断是否为内建命令,二是执行内建命令。如果是内建命令,我们需要执行并且在退出后跳过子进程执行的步骤。如果不是内建命令,那就直接退出函数,继续让子进程执行该命令。

下面演示一下代码(这里只截取部分代码,其实较上面的代码,就只是增加了Built-in-com()函数)

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{
    int ret = 0;
    if(strcmp(argv[0],"cd") == 0)
    {
       ret = 1;
       char* home = argv[1];
       if(!home) home = Home(); 
       chdir(home);
    }
    return ret;
}
int main()
{
    while(1)//让shell持续运行
    {
        char command[SIZE];
        //1.获取用户指令并打出命令行提示符
        int ret = interactive(command);
        if(!ret)
        {
            continue;
        }
        //2.切割命令行
        Split(command);
        //3.处理内建命令
        ret = Built_in_com();
        if(ret)
        {
            continue;
        }
        //4.执行命令
        Execute();
    }
    // for(int i = 0; argv[i];i++)
    // {
    //     printf("argv[%d]:%s\n",i,argv[i]);
    // }
    return 0;
}

执行结果

我们可以发现,虽然命令行提示符的路径没有改变,但是pwd命令和cd命令确实是成功执行了。这里的命令行提示符是从环境变量中获取当前的工作路径的,命令行提示符路径没有改变,说明环境变量没有跟着chdir的改变而改变,所以我们在内建命令改变路径时,我们要手动对环境变量进行更新,以确保环境变量是正确的。

要修改环境变量,我们就不得不提到putenv这个接口了,这个接口常用于添加和修改环境变量。

这里的string参数就是要修改的环境变量参数,具体的参数形式:“USER=root”(例)。不过要获得修改后的环境变量参数,又要使用别的字符串函数。

要获得修改后的环境变量参数,就得先获取环境变量形式的字符串,而要获得这个字符串,我们可以通过很多种方式,可以使用strcat,strcpy等等,这里我使用snprintf。

printf就是把特定格式的内容写到显示器上,而sprintf就是把特定格式的内容写到一个str字符串里面,而snprintf就是把指定长度的内容写到一个str字符串里面。

我们可以定义一个全局变量数组pwd,里面存放修改后的环境变量字符串。通过snprintf我们就可以把对应的字符串写入pwd中,再putenv(pwd)即可。

下面演示一下代码(这里只对Built_in_com这个函数进行修改,其他的一律不做修改)

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{
    int ret = 0;
    if(strcmp(argv[0],"cd") == 0)
    {
       ret = 1;
       char* home = argv[1];
       if(!home) home = Home(); 
       chdir(home);
       snprintf(pwd,SIZE,"PWD=%s",home);
       putenv(pwd);
    }
    return ret;
}

运行结果

除了cd .. 外,其他的命令都还正常,说明之前的代码总体逻辑是没有啥问题的,只不过cd .. 这个需要处理一下。这里出现 .. 路径是因为没有获得 “ .. ”代表的绝对路径。所以我们就需要通过特定的接口获得..路径的绝对路径 。我们可以通过getcwd函数获取“..”的绝对路径。

getcwd函数就是获取当前的工作路径,具体的参数含义如下:

  1. buf:这是一个指向字符数组的指针,用于存储获取到的当前工作目录的路径。getcwd函数会将路径字符串写入到这个数组中。

  2. size:这个参数指定了buf数组的大小,也就是它能够存储的字符数量。这个大小应该至少能够容纳当前工作目录的路径加上一个终止的空字符('\0')。                                           

既然可以通过此时,我们就可以直接把buf里面内容写到pwd中,再由pwd写入到环境变量表中。

下面演示一下代码

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{
    int ret = 0;
    if(strcmp(argv[0],"cd") == 0)
    {
       ret = 1;
       char* home = argv[1];
       if(!home) home = Home(); 
       chdir(home);
       char word[512];
       getcwd(word,512);
       snprintf(pwd,SIZE,"PWD=%s",word);
       putenv(pwd);
    }
    return ret;
}

 运行结果

其他的内建命令

内建命令当然不止只有cd,还有很多,下面列举一些供大家参考

  • cd - 改变当前工作目录
  • echo - 显示消息或变量的值
  • exit - 退出当前shell
  • export - 设置环境变量
  • history - 显示或操作命令历史
  • kill - 发送信号到特定进程
  • pwd - 显示当前工作目录的路径
  • set - 设置或显示shell特性或位置参数
  • source - 在当前shell执行脚本
  • unset - 删除变量或函数
  • wait - 等待后台进程结束

下面在原来shell基础上,再添加几个内建命令

<2>export

export命令导入环境变量也是内建命令,所以我们需要添加进Built_in_com 这个函数中。这个命令会相对简单一点,我们可以直接写代码

下面演示一下代码

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{
    int ret = 0;
    if(strcmp(argv[0],"cd") == 0)
    {
       ret = 1;
       char* home = argv[1];
       if(!home) home = Home(); 
       chdir(home);
       char word[512];
       getcwd(word,512);
       snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界
       putenv(pwd);
    }
    else if(strcmp(argv[0],"export") == 0) // 
    {
        ret = 1;
        if(argv[1]) putenv(argv[1]);
        
    }
    return ret;
}

运行结果

这个结果看似是非常正确的,但其实我们只要运行几次其他命令,再次查看环境变量。我们就会发现,我们新增的环境变量消失了。所以上述的代码其实是不完善的。这里的argv[ 1 ]是一个指针,指向command里面的一段内容,而我们每次输入新的命令,command就会被覆盖,而argv[ 1 ]指向的内容也会随之改变。 所以我们需要通过一个数组来存储特定环境变量,令其固定不变。这里为了方便演示,这里只用一个一维字符数组来存储一个新增的环境变量。这里严格意义上来说,是要通过一个环境变量表来存储环境变量的。

下面演示一下代码

    else if(strcmp(argv[0],"export") == 0)
    {
        ret = 1;
        if(argv[1]) 
        {
            strcpy(env,argv[1]);
            putenv(env);
        }
    }

由于运行结果过长,这里不变展示,读者可以自行测试。

<3>echo命令

echo命令也是一个内建命令,通常用于打印一些变量值,常见的就是“echo XXX”,向显示器打印XXX;”echo $环境变量名“,向显示器打印环境变量;“echo $?” 打印退出码。除此之外,echo还可以结合重定向进行操作,不过这里不做演示,这部分内容涉及文件系统的内容。这里主要演示上面所述的三个与echo有关的命令。

1.直接echo

echo后面不接内容,就是直接换行。

2.echo $? 

该命令会显示上一个进程的退出码,这个退出码最好就用全局变量来保存。

3.echo $环境变量

该命令会显示对应环境变量的内容,不过需要判断这个环境变量是否存在。

4.echo XXX

该命令会直接打印XXX到显示器上

下面演示一下代码(只展示修改被修改部分的代码)

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        execvp(argv[0],argv);
    }
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(WIFEXITED(status))
    {
        lastcode = WEXITSTATUS(status);
    }
    // if(rid > 0)
    // {
    //     printf("wait success, pid: %d\n",rid);
    // }
}
int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{
    int ret = 0;
    if(strcmp(argv[0],"cd") == 0)
    {
       ret = 1;
       char* home = argv[1];
       if(!home) home = Home(); 
       chdir(home);
       char word[512];
       getcwd(word,512);
       snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界
       putenv(pwd);
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        ret = 1;
        if(argv[1]) 
        {
            strcpy(env,argv[1]);
            putenv(env);
        }
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        ret = 1;  
        if(argv[1] == NULL)
        {
            printf("\n");
        }
        else
        {
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')
                {
                    printf("%d\n",lastcode);
                    lastcode = 0;
                }
                else // echo $环境变量名
                {
                    char* n = getenv(argv[1]+1);
                    if(n)
                    {
                        printf("%s\n",n);
                    }
                    else
                    {
                        printf("The environment variable does not exist\n");
                    }
                }
            }
            else//echo XXXX 这里暂不考虑其他情况,例如echo和重定向符号结合
            {
                printf("%s\n",argv[1]);
            }
        }
        }
    return ret;
}

 这里lastcode变量定义为全局变量。echo的其他搭配暂时不考虑。

运行结果

额外的配置:我们可以看见当我们使用ls命令时,打印出来的文件名是没有颜色的。如果我们想让文件名具有颜色,需要再Split函数里面进行修改,我们需要再命令行参数列表中加上一个“--color”字符即可。

下面演示一下代码

void Split(char* command)
{
    int i = 0;
    argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量
    while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
    if(strcmp(argv[0],"ls") == 0)
    {
        argv[i - 1] = "--color";
        argv[i] = NULL;
    }
}

运行结果

<5>全部代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];
char pwd[SIZE];
char env[SIZE];
int lastcode;

const char* HostName()
{
    putenv("HOSTNAME=iZuf6at4ih6u7gbg2vxumnZ");
    char* hostname = getenv("HOSTNAME");
    if(hostname)
    {
        return hostname;
    }
    else
    {
        return "NONE";
    }
}
const char* UserName()
{
    char* username = getenv("USER");
    if(username)
    {
        return username;
    }
    else
    {
        return "NONE";
    }
}
const char* Currentdir()
{
    char* dirname = getenv("PWD");
    if(dirname)
    {
        return dirname;
    }
    else
    {
        return "NONE";
    }
}
char* Home()
{
    char* home = getenv("HOME");
    if(home)
    {
        return home;
    }
    else
    {
        return NULL;
    }
}
int interactive(char* command)
{
    printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());
    fgets(command,SIZE,stdin);
    command[strlen(command) - 1] = 0;
    return strlen(command);
}
void Split(char* command)
{
    int i = 0;
    argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量
    while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
    if(strcmp(argv[0],"ls") == 0)
    {
        argv[i - 1] = "--color";
        argv[i] = NULL;
    }
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        execvp(argv[0],argv);
    }
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(WIFEXITED(status))
    {
        lastcode = WEXITSTATUS(status);
    }
    // if(rid > 0)
    // {
    //     printf("wait success, pid: %d\n",rid);
    // }
}
int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{
    int ret = 0;
    if(strcmp(argv[0],"cd") == 0)
    {
       ret = 1;
       char* home = argv[1];
       if(!home) home = Home(); 
       chdir(home);
       char word[512];
       getcwd(word,512);
       snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界
       putenv(pwd);
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        ret = 1;
        if(argv[1]) 
        {
            strcpy(env,argv[1]);
            putenv(env);
        }
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        ret = 1;  
        if(argv[1] == NULL)
        {
            printf("\n");
        }
        else
        {
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')
                {
                    printf("%d\n",lastcode);
                    lastcode = 0;
                }
                else // echo $环境变量名
                {
                    char* n = getenv(argv[1]+1);
                    if(n)
                    {
                        printf("%s\n",n);
                    }
                    else
                    {
                        printf("The environment variable does not exist\n");
                    }
                }
            }
            else//echo XXXX 这里暂不考虑其他情况,例如echo和重定向符号结合
            {
                printf("%s\n",argv[1]);
            }
        }
        }
    return ret;
}
int main()
{
    while(1)//让shell持续运行
    {
        char command[SIZE];
        //1.获取用户指令并打出命令行提示符
        int ret = interactive(command);
        if(!ret)
        {
            continue;
        }
        //2.切割命令行
        Split(command);
        //3.处理内建命令
        ret = Built_in_com();
        if(ret)
        {
            continue;
        }
        //4.执行命令
        Execute();
    }
    // for(int i = 0; argv[i];i++)
    // {
    //     printf("argv[%d]:%s\n",i,argv[i]);
    // }
    return 0;
}

 如有需要,可自行拓展,另外上述代码仅供参考,不是唯一的写法。

以上就是全部内容,文中如有不对之处,还望各位大佬指正,谢谢!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

qt-C++笔记之滑动条QSlider和QProgressBar进度条

qt-C笔记之滑动条QSlider和QProgressBar进度条 —— 2024-04-28 杭州 本例来自《Qt6 C开发指南》 文章目录 qt-C笔记之滑动条QSlider和QProgressBar进度条1.运行2.阅读笔记3.文件结构4.samp4_06.pro5.main.cpp6.widget.h7.widget.cpp8.widget.ui 1.运行 2.阅读笔记 3.文件结构…

智慧供热一站式热网平衡多功能集成系统

供热管理地域分散的现实&#xff0c;决定必须采用先进技术手段开发软件系统&#xff0c;使各管理单位互联互通。在多年技术积累的基础上&#xff0c;公司采用目前成熟而且领先的技术架构&#xff0c;研发了适用于多个组织机构集中式管理的供热管理软件。使管理在技术上不再受地…

经典的目标检测算法有哪些?

一、经典的目标检测算法有哪些&#xff1f; 目标检测算法根据其处理流程可以分为两大类&#xff1a;One-Stage&#xff08;单阶段&#xff09;算法和Two-Stage&#xff08;两阶段&#xff09;算法。以下是一些经典的目标检测算法&#xff1a; 单阶段算法: YOLO (You Only Loo…

Java集合框架-Collection-queue

目录 一、Deque二、ArrayDequeArrayDeque层次结构图ArrayDeque概述ArrayDeque底层数据结构ArrayDeque常用方法(简略) 三、PriorityQueuePriorityQueue层次结构图PriorityQueue概述PriorityQueue 底层数据结构PriorityQueue常用方法(详细) Java里有一个叫做Stack的类&#xff0c…

[tkinter实现]汉字笔顺小软件

软件简介 本软件旨在帮助小学生通过互动式学习掌握汉字的基本笔画和笔顺。软件采用Tkinter库构建&#xff0c;提供了一个用户友好的图形界面&#xff0c;适合小学生使用。 主要功能&#xff1a; 汉字展示&#xff1a;软件能够展示单个汉字&#xff0c;并以动画形式演示其标准…

SAP SALV研究

1.SALV SALV用的是类cl_salv_table下的相关方法,一般用于对话框或者功能要求比较简单的数据显示,比如明细数据显示,不能单元格编辑或者某行某列编辑,可以实现全部数据编辑(实用性不强),如要实现编辑可参考这篇博文 https://mp.weixin.qq.com/s?__biz=MzU1MDAzMDY3MQ==…

车载系统的 加减串器应用示意

overview 车载系统上使用加减串器来实现camera&#xff0c; led液晶显示屏等 图像数据的远距离传输&#xff0c;将原先在短距离传输视频信号的mipi csi&#xff0c;dsi 等的TX&#xff0c;RX中间&#xff0c;插入加减串器&#xff0c;实现长距离的可靠传输。 示意图如下 往往…

开发 Chrome 浏览器插件入门

前言 简介 Chrome 插件是扩展 Chrome 浏览器的功能的软件程序。它们可以执行各种任务&#xff0c;例如阻止广告、增强隐私、添加新功能等等。 要开始编写 Chrome 插件&#xff0c;你需要掌握以下&#xff1a; 1.JavaScript语言 2.html 3.css 4.会使用chrome扩展开发手册…

C#设计树形程序界面的方法:创建特殊窗体

目录 1.TreeView控件 2.实例 &#xff08;1&#xff09;Resources.Designer.cs &#xff08;2&#xff09;Form1.Designer.cs &#xff08;3&#xff09;Form1.cs &#xff08;4&#xff09;生成效果 以树形来显示程序的菜单&#xff0c;可以更直观、更快捷地对窗体进行…

PostgreSQL大版本如何升级?

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

算法训练营day25

零、回溯算法理论 参考链接13.1 回溯算法 - Hello 算法 (hello-algo.com) 1.尝试与回退 之所以称之为回溯算法&#xff0c;是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时&#xff0c;它会…

vue使用海康控件开发包——浏览器直接查看海康监控画面

1、下载控件开发包 2、安装插件&#xff08;双击/demo/codebase/HCWebSDKPlugin.exe进行安装&#xff09; 3、打开/demo/index.html文件 4、在页面上输入你的海康监控的登录信息进行预览 如果有监控画面则可以进行下面的操作 注意&#xff1a;以下操作都在Vue项目进行 5、复…

【Unity】shader中参数传递

1、前言 unity shader这个对于我来说是真的有点难&#xff0c;今天这篇文章主要还是总结下最近学习到的一些东西&#xff0c;避免过段时间忘记了&#xff0c;可能有不对&#xff0c;欢迎留言纠正。 2、参数传递的两种方式 2.1 语义传递 语义传递这个相对来说是简单的 shad…

ENVI不同版本个人使用对比

ENVI不同版本个人使用对比 文章目录 ENVI不同版本个人使用对比前言对比5.3学习版5.6学习版6.0试用版 总结 前言 目前来看&#xff0c;流传较广的可供大家免费获取的ENVI版本主要是5.3学习版 5.6学习版 6.0学习版这三个版本&#xff0c;不同的版本有不同特色&#xff0c;在此做…

21.7K Star力荐!跨平台的开源免费可视化爬虫,让数据采集不再是难题!

朋友们!你是否曾梦想着轻松地从网上抓取数据,却苦于编程技能的门槛?现在,有了EasySpider,这一切都变得触手可及!这不仅仅是一个工具,它是一个革命性的网络爬虫神器,让你能够像专业人士一样,无需编写一行代码,就能轻松设计和执行爬虫任务。无论是动态内容还是复杂页面…

【介绍下分布式系统】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

Spring Boot框架强大的事件驱动模型(ApplicationEvent)

文章目录 前言应用场景异步处理事务边界外的操作跨微服务通信系统监控与日志聚合UI更新生命周期管理工作流或业务流程缓存同步 小试牛刀定义事件实现事件处理器注册事件处理器发布事件测试事件 写在最后 前言 在Spring Boot应用中&#xff0c;事件处理器是指那些处理特定类型事…

实时采集麦克风并播放(springboot+webscoekt+webrtc)

项目技术 springbootwebscoektwebrtc 项目介绍 项目通过前端webrtc采集麦克风声音&#xff0c;通过websocket发送后台&#xff0c;然后处理成g711-alaw字节数据发生给广播UDP并播放。 后台处理项目使用线程池(5个线程)接受webrtc数据并处理g711-alaw字节数组放到Map容器中&…

将针孔模型相机 应用到3DGS

Motivation 3DGS 的 投影采用的是 CG系的投影矩阵 P P P, 默认相机的 principal point (相机光心) 位于图像的中点处。但是 实际应用的 绝大多数的 相机 并不满足这样一个设定&#xff0c; 因此我们 需要根据 f , c x , c y {f,c_x, c_y} f,cx​,cy​ 这几个参数重新构建3D …

Linux 安装 nvm,并使用 Jenkins 打包前端

文章目录 nvm是什么nvm下载nvm安装设置 nvm 环境变量设置 Jenkins 打包命令 nvm是什么 nvm全英文也叫node.js version management&#xff0c;是一个nodejs的版本管理工具。nvm和n都是node.js版本管理工具&#xff0c;为了解决node.js各种版本存在不兼容现象可以通过它可以安装…
最新文章