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

news2024/12/27 13:34:26

前面介绍了进程程序替换的相关知识,接下来,我将介绍如何基于前面的知识,编写一个简单的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.coloradmin.cn/o/1631127.html

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

相关文章

网络安全的防护措施有哪些?

1. 安全策略和合规性 2. 物理和网络安全 3. 数据加密 4. 软件和系统更新 5. 访问控制 6. 威胁监测和响应 7. 员工培训和安全意识 8. 备份和灾难恢复 零基础入门学习路线 视频配套资料&国内外网安书籍、文档 网络安全面试题 网络安全的防护措施多种多样&#xff0c…

openssl3.2 - exp - 使用默认的函数宏,在release版中也会引入__FILE__

文章目录 openssl3.2 - exp - 使用默认的函数宏&#xff0c;在release版中也会引入__FILE__概述笔记验证是否__FILE__在release版下也能用&#xff1f;将openssl编译成release版的&#xff0c;看看CRYPTO_free()是否只需要一个参数就行&#xff1f;将工程中的openssl相关的库换…

Vue---组件

Vue—组件 目录 Vue---组件定义组件全局组件局部组件 组件通讯***重点***父子通信之父传子&#xff08;props&#xff09;父子通信之子传父&#xff08;$emit&#xff09;ref属性&#xff08;$refs&#xff09; 动态组件插槽命名插槽 定义组件 全局组件 vue2中template只能传…

2.Neo4j的搭建启动

Graph Database 图数据库 版本对应关系 官网都是高版本&#xff0c;推荐使用下载地址可以找到社区老版本&#xff1a; https://we-yun.com/doc/neo4j/ neo4j.bat 启动脚本 cypher-shell.bat 执行CQL语句的。 import文件夹可以放入excel,csv等数据文件&#xff0c;导入到…

IMU/捷联惯导常见的术语,以及性能评价标准(附Python解析代码)

0. 简介 现在的机器人领域在普遍使用IMU&#xff08;惯性导航单元&#xff09;。该系统有三个加速度传感器与三个角速度传感器&#xff08;陀螺&#xff09;组成&#xff0c;加速度计用来感受飞机相对于地垂线的加速度分量&#xff0c;陀螺仪用来感知飞机的角速率变化&#xff…

服务器被CC攻击怎么办

遇到CC攻击时&#xff0c;可采取以下措施&#xff1a;限制IP访问频率、启用防DDoS服务、配置Web应用防火墙、增加服务器带宽、使用负载均衡分散请求压力。 处理服务器遭遇CC攻击的方法如下&#xff1a; 1. 确认攻击 你需要确认服务器是否真的遭受了CC攻击&#xff0c;这可以…

基于单片机的多功能电子万年历系统

摘要:该题目要求学生综合运用单片机原理、低频电子线路、数字电路与逻辑设计等相关知识,设计完成多功能电子万年历系统。通过完成设计任务,使学生掌握单片机设计开发的基本流程,增强学生动手实践能力,培养学生分析和解决实际问题的能力,为后续课程的学习和工作打下良好基础。 关…

张大哥笔记:我付钱了,我就是大爷?

很抱歉用这个当做标题&#xff0c;来给大家分享一些电商的故事&#xff01;大家好&#xff0c;我是张大哥&#xff0c;今天聊聊在电商路上遇到过的奇葩买家&#xff1f; 比如最近我在做PDD的时候&#xff0c;就会遇到很多莫名其妙的sha子&#xff0c;咱是知识份子&#xff0c;肯…

Excel vlookup函数的使用教程 和 可能遇到的错误解决方法

使用VLOOKUP示例 被查询的表格 表一 A列B列C列A1aB2bC3c 要匹配的列 表二 F列G列H列ACBDA 要G列匹配字母&#xff0c;H列匹配数字 G 使用公式VLOOKUP(F5,A:D,3,0) 参数说明 F5 是表二 F列第五行的A A:D表是要匹配的数据列表在A到D列&#xff0c;就是表一 &#xff08;注意…

go 安装软件报go.mod file not found

执行 go get -u github.com/go-sql-driver/mysql 下载mysql 报错 解决方法: 控制台&#xff1a;输入go env 返回如下&#xff1a; 红圈值为NUL&#xff0c;需要设置GOMOD的值, 然后再控制台执行 &#xff08;1&#xff09;mkdir mod (2)go mod init mod 然后再执行下载&…

Linux逻辑方式合并物理磁盘

在日常生活中&#xff0c;我们总是遇到一个文件太大&#xff0c;以至于我们的两个磁盘都装不下&#xff0c;这时我们就需要将两块物理磁盘逻辑化的连接在一起&#xff0c;把物理磁盘使用逻辑化的方法合并在一起&#xff0c;形成卷组&#xff0c;使得磁盘空间可以公用&#xff1…

【idea】idea 中 git 分支多个提交合并一个提交到新的分支

一、方法原理讲解 我们在 dev 分支对不同的代码文件做了多次提交。现在我们想要把这些提交都合并到 test 分支。首先我们要明白四个 git 操作&#xff0c; commit&#xff1a;命令用于将你的代码变更保存到本地代码仓库中&#xff0c;它创建了一个新的提交&#xff08;commit…

Python 操作PDF图片 – 添加、替换、删除PDF中的图片

PDF文件中的图片可以丰富文档内容&#xff0c;提升用户的阅读体验。除了在PDF中添加图片外&#xff0c;有时也需要替换或删除其中的图片&#xff0c;以改进视觉效果或更新信息。文本将提供以下三个示例&#xff0c;介绍如何使用Python 操作PDF文件中的图片&#xff1a; 目录 …

2017年全国职业院校技能大赛高职组“信息安全管理与评估”样题

培训、环境、资料、考证 公众号&#xff1a;Geek极安云科 网络安全群&#xff1a;624032112 网络系统管理群&#xff1a;223627079 网络建设与运维群&#xff1a;870959784 移动应用开发群&#xff1a;548238632 极安云科专注于技能提升&#xff0c;赋能 2024年广东省高校的技…

面试八股——HashMap

实现原理 红黑树是为了解决链表过长之后&#xff0c;查找时间过长的问题&#xff0c;将链表存储变为红黑树存储。 put方法的实现&#xff08;5⭐&#xff09; 相关属性&#xff1a; 1. 容量&#xff1a;初始容量为2^4。 2. 加载因子&#xff1a;初始值为0.75 上面两个属性的…

linux网络编程启动!(开端)

网络设计模式 &#xff1a;就两种模型 b/s 模型 : 浏览器—>服务器 优点是&#xff1a;跨平台。开发成本低 缺点是&#xff1a;网络通信的时候必须要使用http/https协议 http协议 是个应用层协议 不能在磁盘缓存或者从磁盘加载大量数据 http 与https 多了一层加密 c/s模型 …

【Shell】循环结构——for和while循环实例

Shell可以重复地执行特定的指令&#xff0c;直到特定的条件被满足为止。这重复执行的一组指令就叫做循环 特点&#xff1a; 首先&#xff0c;循环条件中使用的变量必须是已初始化的&#xff0c;然后在循环中开始执行每次在循环开始时进行一次测试重复地执行一个代码块 循环实例…

OpenHarmony 实战开发——分布式购物车案例展示~

简介 分布式购物车demo 模拟的是我们购物时参加满减活动&#xff0c;进行拼单的场景&#xff1b;实现两人拼单时&#xff0c;其他一人添加商品到购物车&#xff0c;另外一人购物车列表能同步更新&#xff0c;且在购物车列表页面结算时&#xff0c;某一人结算对方也能实时知道结…

【无监督+自然语言】 GPT,BERT, GPT-2,GPT-3 生成式预训练模型方法概述 (Generative Pre-Traning)

主要参考 【GPT&#xff0c;GPT-2&#xff0c;GPT-3 论文精读【李沐论文精读】-2022.03.04】 https://www.bilibili.com/video/BV1AF411b7xQ/ 大语言模型综述&#xff1a; https://blog.csdn.net/imwaters/article/details/137019747 GPT与chatgpt的关系 图源&#xff1a;L…

JMeter的下载安装与使用(Mac)

1、下载地址​​​​​​https://jmeter.apache.org/download_jmeter.cgi 2、下载Binaries 下的apache-jmeter5.5.tgz 3、解压 4、启动 在bin目录下打开终端&#xff0c;输入sh jmeter 出现jmeter首页界面&#xff0c;即为成功。 5、使用 5.1 语言选择 option选项卡&am…