当前位置: 首页 > news >正文

【Linux——实现一个简易shell】

黑暗中的我们都没有说话,你只想回家,不想你回家...............................................................

文章目录

前言

一、【shell工作过程】

二、【命令行参数】

2.1、【获取命令行参数】

1、【输出命令行提示符】

2、【输入命令行参数】

2.2、【解析命令行参数】

1、【分割命令行参数】

2、【判断指令类型】

3、【外部命令的执行】

4、【内建命令的执行】

cd命令:

export指令:

echo指令:

5、【实现重定向】

三、【总结说明以及完整代码】

1、【使shell循环工作】

2、【代码中的全局变量说明】

3、【完整代码及效果演示】

总结


前言

shell也就是命令行解释器,其运行原理就是:当有命令需要执行时,shell创建子进程,让子进程执行命令,而shell只需等待子进程退出即可,学习了进程的概念及其控制以后,我们也可以上手做一个简易的shell。


一、【shell工作过程】

我们一般见到的shell一般是下面这个样子:

然后我们可以在光标之后输入一些命令来完成相应的操作:

那么我们来想一下,shell是如何进行工作的呢?

首先我们看到的命令行提示符,光标,这些我们只需要做一些打印工作即可。

但是那些命令是如何执行的呢?

我们知道那些命令实际上也是一个个存放在磁盘中的可执行程序,我们通过在shell界面上输入那些命令名,实际上就是将其从磁盘加载到内存进而进行调用,也就是说,我们要在shell进程里创建子进程,然后通过子进程去调用那些命令的可执行程序,才能完成使用命令的操作。

因此shell需要执行的逻辑其实非常简单,其只需循环执行以下步骤:

  1. 获取命令行。
  2. 解析命令行。
  3. 创建子进程。
  4. 替换子进程。
  5. 等待子进程退出。

其中,创建子进程使用fork函数,替换子进程使用exec系列函数,等待子进程使用wait或者waitpid函数。

下面我们具体来看一看。

二、【命令行参数】

2.1、【获取命令行参数】

1、【输出命令行提示符】

我们首先将shell界面中的命令行提示符打印出来,首先我们先来看看这些命令行提示符都是什么:

不难发现这些都是我们shell环境变量中的值,所以我们可以通过函数getenv(),来获取他们具体如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#define NUM 1024   //用来存放输入的数组大小 //获取用户名
const char* getUsername()
{const char* name=getenv("USER");if(name){return name;}else {return "none";}
}
//获取主机名
const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname){return hostname;}else {return "none";}
}
//获取工作目录
const char* getCwd()
{const char* cwd=getenv("PWD");if(cwd){return cwd;}else {return "none";}
}
int main()
{char usercommand[NUM];printf("[%s@%s %s]$ ",getUsername(),getHostname(),getCwd());scanf("%s",usercommand);return 0;
}

打印效果:

2、【输入命令行参数】

但是这样编写的代码会存在一些问题:

如果我们用scanf读取输入的命令行参数,只会读到一个不含空白符的字符串,因为scanf会以空白符为结尾。我们可以看看:

所以我们不能用scanf读取命令行参数,可以用fgets:【fgets - C++ Reference】

注意:

puts遇到空字符停止输出,在输出字符串时会自动在字符串末尾(\0)加一个换行符。
gets()丢弃输入中的换行符,puts()在输出中添加换行符。

fgets()保留输入中的换行符,fputs()不在输出中添加换行符。

所以更改如下:

最后我们对其进行封装:

2.2、【解析命令行参数】

1、【分割命令行参数】

用户输入参数(指令)后,shell会对该行参数进行解析,一般会将字符串按空格进行分割,然后分割好的命令行参数就可以作为程序替换函数exec*的参数,从而实现程序替换,并调用对应的命令程序,而我们这里用strtok函数【strtok - C++ Reference】分割字符串。

这里我们直接进行封装:

最终达到下面的效果即可:

命令行提示符、用户与命令行交互、解析参数的功能实现了,现在我们需要根据用户输入的参数(指令),执行程序。但在执行程序之前,我们必须对指令进行判断。

2、【判断指令类型】

我们之前学习过指令,Linux系统的指令一般可以分为两类 :

  • 一类是内建指令(builtin shell command),内部指令是指内建在shell中的指令,但我们执行该类指令时,不需要额外创建进程,所以内部指令执行的效率高,内建命令就是bash自己执行的,类似于自己内部的一个函数【SHELL编程之内建命令 | Zorro’s Linux Book】
  • 另一类是外部指令(external shell command)。外部指令是指非内建于shell的指令,我们执行该类指令时,会额外创建一个进程。

我们可以通过指令type判断一个指令是否是内建指令,type命令来自英文单词“类型”,其功能是用于查看命令类型,如需区分某个命令是Shell内部指令还是外部命令,则可以使用type命令进行查看。

【参考资料】:type命令 – 查看命令类型 – Linux命令大全(手册)

显示出文件路径的一般是外部命令,显示“is a shell builtin”是内建命令,内建命令不是存放在磁盘中的可执行程序,我们不能简单的创建子进程并进行程序替换,来实现内建命令的调用,对于内建命令我们需要作特殊处理。

3、【外部命令的执行】

对于外部命令的执行,我们不需要考虑太多,只需要在我们的shell进程中创建子进程,使用分割好的命令行参数,进行程序替换,从而完成对应命令的调用。

具体如下:

4、【内建命令的执行】

由于内建命令不同于外部命令只进行简单的程序替换即可完成,内建命令需要特殊的处理,所以对于不同的内建命令我们有不同的处理措施,下面我们看几个例子:

cd命令:

我们知道cd + 目标路径,就可以将我们当前的工作目录改为cd命令后面的目标路径,所以我们可以通过chdir系统调用,将当前的工作路径换为“目标路径”

将工作目录改变以后我们可以通过getcwd函数来进行查看:

需要注意的是,这里我们仍然是创建子进程来实现cd命令所以我们会在子进程中使用chdir函数,而chdir只会进程的当前工作目录,只会影响当前进程及其子进程的工作目录,不会影响环境变量中的PWD,PWD 是由 shell 自动维护,而不是由内核直接管理。

对于典型的 shell,PWD 在你使用命令(如 cd)改变目录时会被更新,但是,直接用系统调用 chdir 不会自动更新 PWD。所以我们仅仅使用chdir函数就会出现下面的场景:

所以当我们调用chdir更改当前工作目录后,还要对环境变量PWD进行修改。先通过调用getcwd()获取当前工作目录的绝对路径,并将其存放到临时空间tmp中,之后通过函数sprintf将tmp的值格式化给全局变量cwd,最后再使用函数putenv将全局变量cwd覆盖环境变量PWD,最后就可以完成修改如下:

export指令:

像cd指令一样,一旦我们识别到对应指令为export,我们就要对其进行特殊的处理,我们知道export命令可用于显示或设置环境变量。所以我们可以使用putenv函数来实现:

echo指令:

当我们使用echo指令打印字符串,都没什么问题,可当我们像下面这样使用时:

按理说这里应该会为我们打印PATH环境变量,但是echo好像将其当成了字符串进行输出,所以我们要来解决一下这个问题,与前面一样我们对其进行针对性特殊处理即可:

5、【实现重定向】

在myshell当中添加重定向功能的步骤大致如下:

  1. 对于获取到的命令进行判断,若命令当中包含重定向符号>>>或是<,则该命令需要进行处理。
  2. 设置type变量,type为0表示命令当中包含输出重定向,type为1表示命令当中包含追加重定向,type为2表示命令当中包含输入重定向。
  3. 重定向符号后面的字段标识为目标文件名,若type值为0,则以写的方式打开目标文件;若type值为1,则以追加的方式打开目标文件;若type值为2,则以读的方式打开目标文件。
  4. 若type值为0或者1,则使用dup2接口实现目标文件与标准输出流的重定向;若type值为2,则使用dup2接口实现目标文件与标准输入流的重定向。

请看下面的实现过程:

最后注意每次进行新指令的输入时把我们定义的redir和filename两个全局变量进行清理:

三、【总结说明以及完整代码】

1、【使shell循环工作】

到这里就算把大部分常见的功能实现完全了,但是细心的你一定会发现,我们的shell还存一个致命的缺陷,就是我们好像只能执行一次命令,所以我们要对其进行完善,是我们的shell能够像正常shell一样,能够连续工作:

2、【代码中的全局变量说明】

我们可以看到我们的代码中用了很多全局变量,那么它们有什么作用呢?

我们先来看lastcode,该全局变量表示程序的退出码,这样我们在父进程等待子进程,或者程序退出的时候获得退出信息:

下面再让我们看看enval和cwd:

我们会发现,这两者都是在调用函数putenv时使用的,也就是说当我们需要添加换将变量时需要定义它们两个定义为全局变量,那么是为什么呢?

首先我们假设没有这两个全局变量,而是将enval和cwd定义在了函数中:

我们要知道当进程启动时,会专门开辟一块空间用来存储命令行参数和环境变量,同时用一个字符串指针数组管理这些环境变量,这个管理环境变量的字符串指针数组就叫做环境变量表(char* envrion[]),当我们用putenv新增一个环境变量时,这时环境变量表会分配一个元素,也就是字符串指针指向这个新增的环境变量,这个新增的环境变量并没有添加到专门存储环境变量的内存空间中(也就是environ指向的空间),而是在栈区,这是因为我们使用argv[1],作为路径path,去当作新传入的环境变量(这里在cd指令中path是目标路径,我们要通过改变环境变量来实现命令行提示符中路径部分的持续变化,而在export中path是我们要导入的环境变量),大致如下:

所以我们通过putenv函数将path添加到环境变量表中,实际上是在environ中创建了一个字符串指针指向了path,而path本身存放在栈区中:

而当我们输入重新在命令行输入指令时,就会刷新命令行参数表argv,如果argv可以分割成两个字符串(也就是argv[0],argv[1]均存在),第二个字符串就会覆盖原来的argv[1],“这样环境变量表environ就找不到原来新增的环境变量了因为被覆盖了。

所以,我们要自己维护一个存放环境变量的空间myenv或cwd,将新增的环境变量存放在myenv和cwd中:

这样就不会覆盖了。 (但再添加一个新的环境变量会覆盖旧的环境变量,大家也可以把自己维护的环境变量设置成二维数组的形式,在堆上申请空间)。

3、【完整代码及效果演示】

为了将系统的shell和我们自己写的shell进行区分,我们可以通过之前写进度条的颜色方案为我们的命令行提示符部分进行上色:

同时也应使用Makefile进行管理:

完整代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
#define NUM 1024   //用来存放输入的数组大小 
#define SEP " "#define SIZE 64
char cwd[1024];
char enval[1024]; // for test 
int lastcode=0;
//重定向类别
#define NoneRedir   0// 不是重定向
#define OutputRedir 1//输出重定向
#define AppendRedir 2 //追加重定向
#define InputRedir  3//输入重定向
int redir=NoneRedir;//全局变量,redir,用来标识
char* filename=NULL;// 文件名
//获取用户名
const char* getUsername()
{const char* name=getenv("USER");if(name){return name;}else {return "none";}
}
//获取主机名
const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname){return hostname;}else {return "none";}
}
//获取工作目录
const char* getCwd()
{const char* cwd=getenv("PWD");if(cwd){return cwd;}else {return "none";}
}
//获取命令行参数
int getUserCommand(char *command, int num)
{printf("\033[35;1m[%s@\033[32;1m%s \033[34;1m%s]\033[31;1m %c\033[0m ", getUsername(), getHostname(), getCwd(),'$');char *r = fgets(command, num, stdin); if(r == NULL)return -1;command[strlen(command) - 1] = '\0';return strlen(command);
}
//分割命令行参数
void commandSplit(char *in, char *out[])
{int argc = 0;out[argc++] = strtok(in, SEP);while( out[argc++] = strtok(NULL, SEP));
}
//执行外部命令
int execute(char *argv[])
{//pid_t id = fork();//创建子进程if(id < 0) return -1;//创建失败else if(id == 0) //child{// 程序替换会不会影响曾经的重定向呢??不会!! 为什么?如何理解??int fd = 0;if(redir == InputRedir){fd = open(filename, O_RDONLY); // 差错处理我们不做了dup2(fd, 0);}else if(redir == OutputRedir){fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);dup2(fd, 1);}else if(redir == AppendRedir){fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);dup2(fd, 1);}else{//do nothing}//程序替换execvp(argv[0], argv); // argv[0]-->ls,argv[1]-->-a,argv[2]-->-l,argv[3]-->NULL exit(1);//子进程结束后使用exit函数退出}else // father{//父进程等待子进程int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}}return 0;
}void cd(const char *path)
{char tmp[1024];//定义临时空间存放当前工作目录getcwd(tmp, sizeof(tmp));//获取当前工作目录printf("当前工作目录:%s\n",tmp);printf("环境变量:%s\n",getenv("PWD"));chdir(path);//改变工作目录getcwd(tmp, sizeof(tmp));//更新工作目录,并将其存放到tmpsprintf(cwd, "PWD=%s", tmp); //使用tmp覆盖环境变量,cwd的值被格式化为"PWD=tmp"putenv(cwd);//覆盖PWD环境变量printf("当前工作目录:%s\n",tmp);printf("环境变量:%s\n",getenv("PWD"));
}char *homepath()
{char *home = getenv("HOME");if(home) return home;else return (char*)".";
}extern  char    **environ;  //使用全局变量environ打印所有环境变量
//内建命令
int doBuildin(char *argv[])
{if(strcmp(argv[0], "cd") == 0)//判断是否是cd命令{char *path = NULL;if(argv[1] == NULL)path=homepath();//cd后为空就设置为家目录elsepath = argv[1];//不为空就设置为目标路径cd(path);//执行cd逻辑return 1;}else if(strcmp(argv[0], "export") == 0){if(argv[1] == NULL) return 1;//若未传入环境变量则返回strcpy(enval, argv[1]);//否则将传入的环境变量拷贝到全局变量enval中putenv(enval); // 使用putenv函数将enval添加到环境变量中return 1;}else if(strcmp(argv[0], "echo") == 0){if(argv[1] == NULL){printf("\n");return 1;}if(*(argv[1]) == '$' && strlen(argv[1]) > 1)//判断是否是类似与$PATH,$?这种参数{ char *val = argv[1]+1; // 对于不同的参数进行不同的处理,$?就要打印退出码if(strcmp(val, "?") == 0){printf("%d\n", lastcode);lastcode = 0;}else//其他的使用getenv函数进行获取并打印即可{const char *enval = getenv(val);if(enval) printf("%s\n", enval);else printf("\n");}return 1;}else//不是$*这种参数就当作字符串即可 {printf("%s\n", argv[1]);return 1;}}else if(0){//有其他内建指令,再进行特殊处理}return 0;
}
#define SkipSpace(pos) do{ while(isspace(*pos)) pos++; }while(0)//这是一个宏函数,作用是跳过空格,里面使用了函数isspace,该函数作用是判断pos位置是否为空格
void checkRedir(char usercommand[], int len)
{// ls -a -l > log.txt// ls -a -l >> log.txtchar *end = usercommand + len - 1;//从后遍历char *start = usercommand;while(end>start){if(*end == '>')//可能是输入重定向,也可能是追加重定向{if(*(end-1) == '>')//输入重定向{*(end-1) = '\0';filename = end+1;SkipSpace(filename);redir = AppendRedir;break;}else//追加重定向{*end = '\0';filename = end+1;SkipSpace(filename);redir = OutputRedir;break;}}else if(*end == '<')//输出重定向{*end = '\0';filename = end+1;SkipSpace(filename); // 如果有空格,就跳过redir = InputRedir;break;}else//说明未找到“<,<<,>”三个中的一个,继续向前遍历{end--;}}
}
int main()
{while(1){redir=NoneRedir;filename=NULL;char usercommand[NUM];char *argv[SIZE];//获取int n = getUserCommand(usercommand, sizeof(usercommand));if(n<=0)//查看getUserCommand函数返回值,n<=0说明其输入的是空串{continue;//空串直接跳过循环,重新输入}//获得字符串以后,首先检查是否进行了重定向//ls -a -l > log.txt ?> "ls -a -l" [redir_type]   "log.txt"checkRedir(usercommand,sizeof(usercommand));//分割字符串commandSplit(usercommand, argv);n = doBuildin(argv);if(n){continue;//n不为0说明为内建命令,就不向下继续执行了。}n= execute(argv);}return 0;
}

演示效果:

总结


到这里我们就完了一个简易shell的实现,其中我们我们使用了,进程替换,字符分割,重定向,以及文件的打开及关闭,甚至包括dup2函数,等等知识,本篇博客到这里也就结束了,希望对你有所帮助!

......................................................................是否沉默就是你的回答,我们都别挣扎,去爱他

                                                                                                          ————《爱我还是他》

相关文章:

【Linux——实现一个简易shell】

黑暗中的我们都没有说话&#xff0c;你只想回家&#xff0c;不想你回家............................................................... 文章目录 前言 一、【shell工作过程】 二、【命令行参数】 2.1、【获取命令行参数】 1、【输出命令行提示符】 2、【输入命令行参数】 2…...

python使用python-docx处理word

文章目录 一、python-docx简介二、基本使用1、新建与保存word2、写入Word&#xff08;1&#xff09;打开文档&#xff08;2&#xff09;添加标题&#xff08;3&#xff09;添加段落&#xff08;4&#xff09;添加文字块&#xff08;5&#xff09;添加图片&#xff08;6&#xf…...

Typora设置自动上传图片到图床

Typora设置自动上传图片到图床 方法一&#xff1a;使用php 打开设置界面&#xff1a; 自定义命令&#xff1a; php F:/WWW/php-library/TyporaUploadImage.php ${filename}php代码&#xff1a; # TyporaUploadImage.php <?php // Set the API endpoint URL // $apiUrl…...

如何进行Appium实现移动端UI自动化测试呢?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Appium是一个开源跨平台移动应用自动化测试框架。 既然只是想学习下Appium如何入门&#xff0c;那么我们就直奔主题。文章结构如下&#xff1a; 为什么要使用…...

PHP语法学习(第三天)

老规矩&#xff0c;先回顾一下昨天学习的内容 PHP语法学习(第二天) 主要学习了PHP变量、变量的作用域、以及参数作用域。 今天由Tom来打开新的篇章 文章目录 echo 和 print 区别PHP echo 语句实例 PHP print 语句实例 PHP 数组创建数组利用array() 函数 数组的类型索引数组关联…...

mac访达打开终端

选择文件夹打开 选中文件夹&#xff0c;然后右键即可&#xff1a; 在当前文件夹打开 在访达的当前文件夹长按option键 左下角出现当前文件夹路径 右键即可打开终端...

游戏引擎学习第30天

仓库: https://gitee.com/mrxiao_com/2d_game 回顾 在这段讨论中&#xff0c;重点是对开发过程中出现的游戏代码进行梳理和进一步优化的过程。 工作回顾&#xff1a;在第30天&#xff0c;回顾了前一天的工作&#xff0c;并提到今天的任务是继续从第29天的代码开始&#xff0c…...

git将远端库地址加入到本地库中

git将远端库地址加入到本地库中 ​ git remote add test https://test.git其中test表示远端库的名称&#xff0c;url表示远端库的地址&#xff0c;这样添加后在.git/config配置文件中就能够看到新的remote已经被添加&#xff0c;并且通过git remote -v能够看到新添加的远端库...

学习HTML第三十五天

学习文章目录 一.全局属性二..meta 元信息 一.全局属性 常用的全局属性 id 给标签指定唯一标识&#xff0c;注意&#xff1a; id 是不能重复的。 作用&#xff1a;可以让 label 标签与表单控件相关联&#xff1b;也可以与 CSS 、 JavaScript 配合使 用class 给标签指定类名&a…...

MySQL 事务和索引

关于 MySQL 事务特性、 索引特性。 请你简单解释一下 MySQL 事务是什么&#xff1f; 事务是一组数据库操作&#xff0c;这些操作要么全部成功执行&#xff0c;要么全部不执行。它是一个不可分割的工作单元&#xff0c;用于保证数据的一致性和完整性。 请详细阐述一下事务的 AC…...

Matlab学习笔记

Magic Traits 文件读取 fid fopen(fn,rt);out fscanf(fid,spec,inf);fclose(fid);2. 读取数据 fid fopen(fn,rt); out textscan(fid,spec);运算篇 fprintf(" xxx %d",a)&#xff0c;当a为数组时&#xff0c;会输出数组数目行&#xff0c;每行是一个元素相关文…...

在1~n中、找出能同时满足用3除余2,用5除余3,用7除余2的所有整数。:JAVA

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 在1~n中、找出能同时满足用3除余2&#xff0c;用5除余3&#xff0c;用7除余2的所有整数。 输入描述: 输入一行&#xff0c;包含一个正整数n &#xff0c;n在100000以内 输出描述:…...

《极品飞车》游戏运行是弹窗“msvcp140.dll文件丢失”是如何造成的?“找不到msvcp140.dll文件”怎么解决?教你几招轻松解决

《极品飞车》游戏运行时弹窗“msvcp140.dll文件丢失”问题解析及解决方案 在畅游《极品飞车》这类精彩刺激的电脑游戏时&#xff0c;突然遇到弹窗提示“msvcp140.dll文件丢失”&#xff0c;无疑会让玩家感到头疼。那么&#xff0c;这个问题究竟是如何造成的&#xff1f;又该如…...

IDE如何安装插件实现Go to Definition

项目背景 框架&#xff1a;Cucumber Cypress 语言&#xff1a;Javascript IDE&#xff1a;vscode 需求 项目根目录cypress-automation的cypress/integration是测试用例的存放路径&#xff0c;按照不同模块不同功能创建了很多子目录&#xff0c;cucumber测试用例.feature文…...

【Vulkan入门】01-列举物理设备

目录 先叨叨git信息主要逻辑VulkanEnvEnumeratePhysicalDevices()PrintPhysicalDevices() 编译并运行程序 先叨叨 上一篇已经创建了VkInstance&#xff0c;本篇我们问问VkInstance&#xff0c;在当前平台上有多少个支持Vulkan的物理设备。 git信息 repository: https://gite…...

pytest(二)excel数据驱动

一、excel数据驱动 excel文件内容 excel数据驱动使用方法 import openpyxl import pytestdef get_excel():excel_obj openpyxl.load_workbook("../pytest结合数据驱动-excel/data.xlsx")sheet_obj excel_obj["Sheet1"]values sheet_obj.valuescase_li…...

主动安全和驾驶辅助模块(ASDM):未来驾驶的核心科技 随着汽车技术的不断进步,驾驶体验和安全性正经历着前所未有的变革。

未来驾驶的核心科技 随着汽车技术的不断进步&#xff0c;驾驶体验和安全性正经历着前所未有的变革。在这场变革中&#xff0c;主动安全和驾驶辅助模块&#xff08;ASDM&#xff09;扮演着至关重要的角色。本文将深入探讨ASDM模块的定义、功能、工作原理以及它如何改变我们的驾驶…...

8 Bellman Ford算法SPFA

图论 —— 最短路 —— Bellman-Ford 算法与 SPFA_通信网理论基础 分别使用bellman-ford算法和dijkstra算法的应用-CSDN博客 图解Bellman-Ford计算过程以及正确性证明 - 知乎 (zhihu.com) 语雀版本 1 概念 **适用场景&#xff1a;**单源点&#xff0c;可以有负边&#xff0…...

Oracle篇—11gRAC安装在linux7之后集群init.ohasd进程启动不了报错CRS-0715问题

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…...

[golang][MAC]Go环境搭建+VsCode配置

一、go环境搭建 1.1 安装SDK 1、下载go官方SDK 官方&#xff1a;go 官方地址 中文&#xff1a;go 中文社区 根据你的设备下载对应的安装包&#xff1a; 2、打开压缩包&#xff0c;根据引导一路下一步安装。 3、检测安装是否完成打开终端&#xff0c;输入&#xff1a; go ve…...

【乐企文件生成工程】搭建docker环境,使用docker部署工程

1、自行下载docker 2、自行下载docker-compose 3、编写Dockerfile文件 # 使用官方的 OpenJDK 8 镜像 FROM openjdk:8-jdk-alpine# 设置工作目录 WORKDIR ./app# 复制 JAR 文件到容器 COPY ../lq-invoice/target/lq-invoice.jar app.jar # 暴露应用程序监听的端口 EXPOSE 1001…...

关于数据库数据国际化方案

方案一&#xff1a;每个表设计一个翻译表 数据库国际化的应用场景用到的比较少&#xff0c;主要用于对数据库的具体数据进行翻译&#xff0c;在需要有大量数据翻译的场景下使用&#xff0c;举个例子来说&#xff0c;力扣题目的中英文切换。参考方案可见&#xff1a; https://b…...

【目标跟踪】Anti-UAV数据集详细介绍

Anti-UAV数据集是在2021年公开的专用于无人机跟踪的数据集&#xff0c;该数据集采用RGB-T图像对的形式来克服单个类型视频的缺点&#xff0c;包含了318个视频对&#xff0c;并提出了相应的评估标准&#xff08;the state accurancy, SA)。 文章链接&#xff1a;https://arxiv.…...

第10章 大模型的有害性(下)

在本章中&#xff0c;我们继续探讨大型语言模型&#xff08;LLM&#xff09;可能带来的有害影响&#xff0c;重点讨论有毒性&#xff08;toxicity&#xff09;和虚假信息&#xff08;disinformation&#xff09;。这些影响不仅影响用户的体验&#xff0c;也可能对社会产生深远的…...

DevOps工程技术价值流:GitLab源码管理与提交流水线实践

在当今快速迭代的软件开发环境中&#xff0c;DevOps&#xff08;开发运维一体化&#xff09;已经成为提升软件交付效率和质量的关键。而GitLab&#xff0c;作为一个全面的开源DevOps平台&#xff0c;不仅提供了强大的版本控制功能&#xff0c;还集成了持续集成/持续交付(CI/CD)…...

Qt 面试题学习11_2024-11-29

Qt 面试题 1、什么是Qt事件循环 &#xff1f;2、纯虚函数和普通的虚函数有什么区别3、Qt 的样式表是什么&#xff1f; 1、什么是Qt事件循环 &#xff1f; Qt事件循环是一种程序架构&#xff0c;它用于处理窗口系统和其他用户界面事件&#xff0c;以及与用户界面无关的事件例如…...

云原生和数据库哪个好一些?

云原生和数据库哪个好一些&#xff1f;云原生和数据库各有其独特的优势&#xff0c;适用于不同的场景。云原生强调高效资源利用、快速开发部署和高可伸缩性&#xff0c;适合需要高度灵活性和快速迭代的应用。而数据库则注重数据一致性、共享和独立性&#xff0c;确保数据的稳定…...

baomidou Mabatis plus引入异常

1 主要异常信息 Error creating bean with name dataSource 但是有个重要提示 dynamic-datasource Please check the setting of primary 解决方法&#xff1a;增加 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-sp…...

Oracle篇—通过官网下载最新的数据库软件或者历史数据库软件

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…...

初学git报错处理 | 从IDEA远程拉取、创建分支中“clone failed”“couldn‘t checkout”

1.远程拉取“clone failed” 我新建了一个文件夹&#xff0c;结果clone failed。后来发现&#xff0c;原来是在这个文件夹里没有建立本地仓库。 打开文件夹&#xff0c;右键git bush&#xff0c;然后键入git init,就可以成果clone啦&#xff01; 2.新建分支“couldnt checkou…...

【趣味】斗破苍穹修炼文字游戏HTML,CSS,JS

目录 图片展示 游戏功能 扩展功能 完整代码 实现一个简单的斗破苍穹修炼文字游戏&#xff0c;你可以使用HTML、CSS和JavaScript结合来构建游戏的界面和逻辑。以下是一个简化版的游戏框架示例&#xff0c;其中包含玩家修炼的过程、增加修炼进度和显示经验值的基本功能。 图片…...

Luban数据插件的用法

配置后数据后&#xff0c;点击图1中的gen.bat文件启动生成配置数据和解析配置数据代码的程序&#xff0c;自动生成配置数据和解析用到的代码&#xff1b;因为我已经 指定了生成内容的输出路径为Unity项目的路径下面&#xff0c;所以&#xff0c;不用再搬运生成的内容到项目目录…...

「Mac畅玩鸿蒙与硬件35」UI互动应用篇12 - 简易日历

本篇将带你实现一个简易日历应用&#xff0c;显示当前月份的日期&#xff0c;并支持选择特定日期的功能。用户可以通过点击日期高亮选中&#xff0c;还可以切换上下月份&#xff0c;体验动态界面的交互效果。 关键词 UI互动应用简易日历动态界面状态管理用户交互 一、功能说明…...

BiGRU:双向门控循环单元在序列处理中的深度探索

一、引言 在当今的人工智能领域&#xff0c;序列数据的处理是一个极为重要的任务&#xff0c;涵盖了自然语言处理、语音识别、时间序列分析等多个关键领域。循环神经网络&#xff08;RNN&#xff09;及其衍生结构在处理序列数据方面发挥了重要作用。然而&#xff0c;传统的 RN…...

sscanf与sprintf函数

本期介绍&#x1f356; 主要介绍&#xff1a;sscanf()、sprintf()这对输入/输出函数&#xff0c;并详细讲解了这两个函数的应用场景。 概述&#x1f356; 在C语言的输出和输入库中&#xff0c;有三对及其相似的库函数&#xff1a;printf()、scanf()、fprintf()、fscanf()、spri…...

工业智能网关在该企业中的应用实践

随着工业4.0时代的到来&#xff0c;智能制造已成为企业转型升级的重要方向。工业智能网关作为工业互联网架构中的关键组件&#xff0c;正逐步在各大企业中发挥重要作用。本文将以某制造企业为例&#xff0c;详细探讨天拓四方工业智能网关在该企业中的应用实践&#xff0c;展现其…...

python毕业设计常见的一些开源库!

作为一个Python开发者&#xff0c;在开发过程中经常会使用到各种工具库来简化工作、提高效率。以下是一些常见的Python开发工具库及其介绍和官方链接。 序号库名称功能介绍官方链接或参考网址1numpy提供高效的多维数组操作和数学函数&#xff0c;是许多数据科学和科学计算任务的…...

编程语言中什么是框架?什么是Cocoa?Foundation.framework的底层实现?Swift如何引入ObjC框架?

编程语言中什么是框架&#xff1f; 在编程语言中&#xff0c;框架&#xff08;Framework&#xff09;是一种特定的软件库&#xff0c;它提供了一套预先定义的代码和组件&#xff0c;用于加速和简化特定类型的应用程序的开发。框架通常提供了一套标准化的开发工具集和约定&#…...

C++ 游戏开发入门

一、为什么选择 C 进行游戏开发 C 在游戏开发领域具有独特的地位。它兼具高效性与对底层硬件的良好控制能力&#xff0c;这使得它非常适合开发对性能要求极高的游戏核心引擎部分。许多知名的大型游戏&#xff0c;如《使命召唤》系列、《虚幻竞技场》等&#xff0c;其底层架构都…...

【娱乐项目】基于cnchar库与JavaScript的汉字查询工具

Demo介绍 利用了 cnchar 库来进行汉字相关的信息查询&#xff0c;并展示了汉字的拼音、笔画数、笔画顺序、笔画动画等信息用户输入一个汉字后&#xff0c;点击查询按钮&#xff0c;页面会展示该汉字的拼音、笔画数、笔画顺序&#xff0c;并绘制相应的笔画动画和测试图案 cnchar…...

20241129解决在Ubuntu20.04下编译中科创达的CM6125的Android10出现找不到库文件libncurses.so.5的问题

20241129解决在Ubuntu20.04下编译中科创达的CM6125的Android10出现找不到库文件libncurses.so.5的问题 2024/11/29 21:11 缘起&#xff1a;中科创达的高通CM6125开发板的Android10的编译环境需要。 vendor/qcom/proprietary/commonsys/securemsm/seccamera/service/jni/jni_if.…...

自然语言处理:基于BERT预训练模型的中文命名实体识别(使用PyTorch)

命名实体识别&#xff08;NER&#xff09; 命名实体识别&#xff08;Named Entity Recognition, NER&#xff09;是自然语言处理&#xff08;NLP&#xff09;中的一个关键任务&#xff0c;其目标是从文本中识别出具有特定意义的实体&#xff0c;并将其分类到预定义的类别中。这…...

记录一次 用php 调用ai用stream返回

直接写代码了 config 里面是配置文件就不写了&#xff0c;这样要去不同的平台申请去 写一个 service,解释一下代码 写了两个ai&#xff0c;一个是星火&#xff0c;一个是质谱&#xff0c;他们都是调用curl 方法&#xff0c;并返回数据&#xff0c; s t r e a m 为假就是等等返…...

vue引入并调用electron插件在网页报错Dynamic require of “electron“ is not supported

报错信息 Error: Dynamic require of "electron" is not supported 这个错误信息表明你正在尝试在一个普通的网页环境中动态地引入(electron)&#xff0c;但是这是不被允许的。Electron是一个用于构建桌面应用程序的框架&#xff0c;它结合了Node.js和Chromium&#…...

【C++】数组

1.概述 所谓数组&#xff0c;就是一个集合&#xff0c;该集合里面存放了相同类型的数据元素。 数组特点&#xff1a; &#xff08;1&#xff09;数组中的每个数据元素都是相同的数据类型。 &#xff08;2&#xff09;数组是有连续的内存空间组成的。 2、一维数组 2.1维数组定…...

Python 中的 try-except 语句介绍

Python 中的 try-except 语句介绍 在编程过程中&#xff0c;异常处理是非常重要的一部分。Python 提供了 try-except 语句来捕获和处理程序运行时可能出现的异常。本文将详细介绍 try-except 语句的基本概念、常见错误类型以及一些实用的代码示例。 1. try-except 语句的基本…...

网络原理-初识

1.网络的发展历程 独立模式 独立模式&#xff1a;计算机之间相互独立。 每个终端A、B、C各自持有客户端数据 网络互连 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享软件和数据&#xff0c;即可以多个计算机协调工作来完成业务&#xff0c;就有…...

uniapp动态表单

使用了uniapp自带扩展组件和uv-ui组件库自行安装下载 <template><view class"assetEdit_container"><view class"type-box"><uv-formlabelPosition"left"labelWidth"140rpx":model"formData"ref"…...

基于智能语音交互的智能呼叫中心工作机制

在智能化和信息化不断进步的现代&#xff0c;智能呼叫中心为客户提供高质量、高效率的服务体验&#xff0c;提升众多品牌用户的满意度和忠诚度。作为实现智能呼叫中心的关键技术之一的智能语音交互技术&#xff0c;它通过集成自然语言处理&#xff08;NLP&#xff09;、语音识别…...

flask的第一个应用

本文编写一个简单的实例来记录下flask的使用 文章目录 简单实例flask中的路由无参形式有参形式 参数类型不同的http方法本文小结 简单实例 flask的依赖包都安装好之后&#xff0c;我们就可以写一个最简单的web应用程序了&#xff0c;我们把这个应用程序命名为first.py: from fl…...