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

news/2024/5/17 7:52:54

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

相关文章

分析和比较深度学习框架 PyTorch 和 Tensorflow

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 深度学习作为人工智能的一个重要分支&#xff0c;在过去十年中取得了显著的进展。PyTorch 和 TensorFlow 是目前最受欢迎、最强大的两个深度学习框架&#xff0c;它们各自拥有独特的特点和优势。 1. Py…

Vue+Echarts 实现中国地图和飞线效果

目录 实现效果准备 实现效果 在线预览&#xff1a;https://mouday.github.io/vue-demo/packages/china-map/dist/index.html 准备 高版本的echarts&#xff0c;不包含地图数据&#xff0c;需要自己下载到项目中 1、地图数据下载 https://datav.aliyun.com/portal/school/at…

U9网页提示Base-64字符数组或字符串的长度无效

GoogleChrome打开U9网页突然出现错误 Base-64 字符数组或字符串的长度无效。 说明:Base-64 字符数组或字符串的长度无效。Page URL: /U9/mvc/login/index?ReturnUrl=%2fU9%2fmvc%2fmain%2findex 堆栈信息在 System.Convert.FromBase64_Decode(Char* startInputPtr, Int32 inp…

Kafka源码分析(四) - Server端-请求处理框架

系列文章目录 Kafka源码分析-目录 一. 总体结构 先给一张概览图&#xff1a; 服务端请求处理过程涉及到两个模块&#xff1a;kafka.network和kafka.server。 1.1 kafka.network 该包是kafka底层模块&#xff0c;提供了服务端NIO通信能力基础。 有4个核心类&#xff1a;…

FIR滤波器——DSP学习笔记三(包含一个滤波器设计的简明案例)

​​​​​​ 背景知识 FIR滤波器的特性与优点 可精确地实现线性相位响应&#xff08;Linear phase response&#xff09;&#xff0c;无相位失真&#xff1b; 总是稳定的&#xff0c;所有极点都位于原点 线性相位FIR滤波器的性质、类型及零点位置 冲击响应满足&#xff1a;奇…

在React函数组件中使用错误边界和errorElement进行错误处理

在React 18中,函数组件可以使用两种方式来处理错误: 使用 ErrorBoundary ErrorBoundary 是一种基于类的组件,可以捕获其子组件树中的任何 JavaScript 错误,并记录这些错误、渲染备用 UI 而不是冻结的组件树。 在函数组件中使用 ErrorBoundary,需要先创建一个基于类的 ErrorB…

【GitHub】主页简历优化

【github主页】优化简历 写在最前面一、新建秘密仓库二、插件卡片配置1、仓库状态统计2、Most used languages&#xff08;GitHub 常用语言统计&#xff09;使用细则 3、Visitor Badge&#xff08;GitHub 访客徽章&#xff09;4、社交统计5、打字特效6、省略展示小猫 &#x1f…

智慧浪潮下的产业园区:洞察智慧化转型如何打造高效、绿色、安全的新园区

目录 一、引言 二、智慧化转型的内涵与价值 三、打造高效园区的智慧化策略 1、建设智能化基础设施 2、推广智能化应用 3、构建智慧化服务平台 四、实现绿色园区的智慧化途径 1、推动绿色能源应用 2、实施绿色建筑设计 3、加强环境监测与治理 五、保障园区安全的智慧…

文化旅游3D数字孪生可视化管理平台推动文旅产业迈向更加美好的未来

随着数字化、智能化管理成为文旅产业发展的必然趋势&#xff0c;数字孪生公司深圳华锐视点创新性地推出了景区三维可视化数字孪生平台&#xff0c;将线下的实体景区与线上的虚拟世界完美融合&#xff0c;引领智慧文旅新潮流。 我们运用先进的数字孪生、web3D开发和三维可视化等…

【无监督+自然语言】 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…

时间序列生成数据,TransformerGAN

简介&#xff1a;这个代码可以用于时间序列修复和生成。使用transformer提取单变量或者多变时间窗口的趋势分布情况。然后使用GAN生成分布类似的时间序列。 此外&#xff0c;还实现了基于prompt的数据生成&#xff0c;比如指定生成某个月份的数据、某半个月的数据、某一个星期的…

【树莓派Linux内核开发】入门实操篇(虚拟机Ubuntu环境搭建+内核源码获取与配置+内核交叉编译+内核镜像挂载)

【树莓派Linux内核开发】入门实操篇&#xff08;虚拟机Ubuntu环境搭建内核源码获取与配置内核交叉编译内核镜像挂载&#xff09; 文章目录 【树莓派Linux内核开发】入门实操篇&#xff08;虚拟机Ubuntu环境搭建内核源码获取与配置内核交叉编译内核镜像挂载&#xff09;一、搭建…

WEB攻防-ASP安全-MDB下载

MDB下载漏洞主要涉及到早期ASPAccess构架的数据库文件。当Web站点提供文件下载功能时&#xff0c;如果没有对下载请求进行充分的验证和过滤&#xff0c;或者服务器配置不当&#xff0c;就可能产生文件下载漏洞。攻击者可以利用这个漏洞&#xff0c;通过修改请求参数或尝试猜测或…

「React Native」为什么要选择 React Native 作为的跨端方案

文章目录 前言一、常见因素二、举个栗子2.1 项目背景2.2 为什么选择 React Native2.3 项目实施2.4 成果总结 前言 没有完美的跨端技术&#xff0c;只有适合的场景。脱离适用场景去谈跨端技术没有什么意义。 一、常见因素 共享代码库&#xff1a; React Native 允许开发者编写…

[C++基础学习]----02-C++运算符详解

前言 C中的运算符用于执行各种数学或逻辑运算。下面是一些常见的C运算符及其详细说明&#xff1a;下面详细解释一些常见的C运算符类型&#xff0c;包括其原理和使用方法。 正文 01-运算符简介 算术运算符&#xff1a; a、加法运算符&#xff08;&#xff09;&#xff1a;对两个…

【QT】ROS2 Humble联合使用QT教程

【QT】ROS2 Humble联合使用QT教程 文章目录 【QT】ROS2 Humble联合使用QT教程1. 安装ROSProjectManager插件2. 创建ROS项目3.一个快速体验的demoReference 环境的具体信息如下&#xff1a; ubunt 22.04ros2 humbleQt Creator 13.0.0ROS ProjectManager 13.0.0 本文建立在已经…

重生之我是Nginx服务专家

nginx服务访问页面白色 问题描述 访问一个域名服务返回页面空白&#xff0c;非响应404。报错如下图。 排查问题 域名解析正常&#xff0c;网络通讯正常&#xff0c;绕过解析地址访问源站IP地址端口访问正常&#xff0c;nginx无异常报错。 在打开文件时&#xff0c;发现无法…

在IDEA中使用.env文件导入系统配置的图文教程

JetBrains的IDEA是一款功能强大的集成开发环境&#xff0c;为开发人员提供了丰富的功能和工具。使用.env文件来管理配置信息在IDEA中非常简单。 旧版本默认支持&#xff0c;新版本idea需要安装插件才可以。 这里我们可以安装EnvFile插件&#xff0c;步骤如下&#xff1a; 在弹…

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

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

项目部署总结

1、安装jdk 第一步&#xff1a;上传jdk压缩安装包到服务器 第二步&#xff1a;将压缩安装包解压 tar -xvf jdk-8uXXX-linux-x64.tar.gz 第三步&#xff1a;配置环境变量 编辑/etc/profile文件&#xff0c;在文件末尾添加以下内容&#xff1a; export JAVA_HOME/path/to/j…