【Linux】1w详解如何实现一个简单的shell
目录
实现思路
1. 交互 获取命令行
2. 子串分割 解析命令行
3. 指令的判断 内建命令
4. 普通命令的执行
补充:vim 文本替换
整体代码
重点思考
1.getenv和putenv是什么意思
2.代码extern char **environ;
3.内建命令是什么
4.lastcode = WEXITSTATUS(status);
5.execvp(_argv[0], _argv);的调用
6._argc&_argv
实现思路
1. 交互 获取命令行
Shell本质是一个死循环,不断地显示提示符和获取用户输入。
memset
函数
memset
函数用于将一段内存区域设置为指定的值。它的原型是:
void *memset(void *s, int c, size_t n);
参数说明:
-
s
:指向要填充的内存区域的指针。 -
c
:要设置的值(以无符号字符形式传递,但实际存储在内存中的每个字节的值是该无符号字符的值)。 -
n
:要设置的字节数。
示例用法:
char command_line[NUM];
memset(command_line, '\0', sizeof(command_line) * sizeof(char));
这里的代码表示将 command_line
数组的每个字节都设置为 \0
(空字符),确保初始化整个数组。
fgets
函数用于从指定的输入流读取字符串。它的原型是:
char *fgets(char *s, int n, FILE *stream);
参数说明:
-
s
:指向存储读取数据的字符数组的指针。 -
n
:要读取的最大字符数(包括终止字符\0
)。 -
stream
:输入流,通常是stdin
用于标准输入。
示例用法:
fgets(command_line, NUM, stdin);
这行代码表示从标准输入读取最多 NUM-1
个字符(预留一个字符用于终止字符 \0
)到 command_line
数组中。
结合起来,代码片段如下所示:
char command_line[NUM];
memset(command_line, '\0', sizeof(command_line) * sizeof(char));
fgets(command_line, NUM, stdin);
这段代码的作用是:
使用
memset
函数将command_line
数组的所有字节都设置为\0
,即初始化数组。使用
fgets
函数从标准输入读取最多NUM-1
个字符并存储在command_line
数组中。
这样处理后,command_line
数组会包含从输入读取的字符串,并且如果字符串的长度小于 NUM
,数组中剩余的字节会保持为 \0
。
以下是实现这两个步骤的代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024char command_line[NUM]; // 用来接收命令行内容int main(void) {while (1) {/* Step1:显示提示符 */printf("[用户@主机 当前目录] # ");fflush(stdout);/* Step2:获取用户输入 */memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin); // 从键盘获取输入command_line[strlen(command_line) - 1] = '\0'; // 消除 '\n'printf("%s\n", command_line);}
}
通过上述代码,我们可以实现提示用户输入,并获取用户输入。
注意点:
执行发现有空行怎么办
我们利用 fgets 函数从键盘上获取,标准输入 stdin,获取到 C 风格的字符串,
注意默认会添加 \0 ,我们先把获取到的结果 command_line 打印出来看看:
因为 command_line 里有一个 \n,我们把它替换成 \0 即可:
command_line[strlen(command_line) - 1] = '\0'; // 消除 '\0'
2. 子串分割 解析命令行
获取用户输入后,我们需要将接收到的字符串拆分为命令及其参数。
通过 strtok
函数,我们可以将一个字符串按照特定的分隔符打散,依次返回子串:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void) {while (1) {/* 显示提示符和获取用户输入 */printf("[用户@主机 当前目录] # ");fflush(stdout);memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1] = '\0';/* 将接收到的字符串拆开 */command_args[0] = strtok(command_line, SEP);int idx = 1;while ((command_args[idx++] = strtok(NULL, SEP)));/* 打印拆分结果 */for (int i = 0; i < idx - 1; i++) {printf("%d : %s\n", i, command_args[i]);}}
}
通过这段代码,我们可以将输入的命令行字符串拆分成多个子字符串,并打印出来。
strtok
函数的原型为:
char *strtok(char *str, const char *delim);
参数说明:
-
str
:要进行分割的字符串,第一次调用时传入要分割的字符串,后续调用时传入 NULL 即可。 -
delim
:分隔符,用于指定分割字符串的字符。
在代码中,使用了 strtok
函数将 command_line
字符串按照 SEP
分隔符进行切割,并将每个子字符串存储在 command_args
数组中。
command_args[0] = strtok(command_line, SEP);
int idx = 1;
这里的代码首先将 command_line
字符串按照 SEP
分隔符切割成子字符串,并将第一个子字符串的指针存储在 command_args[0]
中。然后,
利用循环逐个获取剩余的子字符串,并将它们存储在 command_args
数组中(使用 idx
来索引)。
3. 指令的判断 内建命令
为了实现一些特定功能,如路径切换,我们需要在Shell中实现内建命令。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];/* Shell 内置函数: 路径跳转 */
int ChangeDir(const char* new_path) {return chdir(new_path);
}int main(void) {while (1) {/* 显示提示符和获取用户输入 */printf("[用户@主机 当前目录] # ");fflush(stdout);memset(command_line, '\0', sizeof(command_line));fgets(command_line, NUM, stdin);command_line[strlen(command_line) - 1] = '\0';/* 将接收到的字符串拆开 */command_args[0] = strtok(command_line, SEP);int idx = 1;while ((command_args[idx++] = strtok(NULL, SEP)));/* 判断并执行内建命令 */if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL) {ChangeDir(command_args[1]);continue;}/* 执行普通命令 */}
}
这段代码通过判断输入的命令是否为 cd
来执行路径切换,而无需创建子进程。
getcwd用于获取当前工作目录(当前目录)的路径。该函数的声明如下:
char *getcwd(char *buf, size_t size);
函数参数说明:
- buf:指向存储当前工作目录路径的缓冲区
- size:缓冲区的大小
函数返回值: 如果函数调用成功,则返回指向存储当前工作目录路径的缓冲区的指针;如果函数调用失败,则返回NULL。
通过调用getcwd函数,可以获取当前程序所在的工作目录路径。
chdir用于改变当前工作目录(当前目录)的路径。该函数的声明如下:
int chdir(const char *path);
函数参数说明:
- path:要设置为当前工作目录的路径
函数返回值: 如果函数调用成功,则返回0;如果函数调用失败,则返回-1,并设置errno来指示错误的类型。
4. 普通命令的执行
最后,我们实现普通命令的执行,包括创建子进程并执行用户输入的命令。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>#define NUM 1024
#define SEP " "
#define SIZE 128char command_line[NUM];
char* command_args[SIZE];int main(void)
{while (1) {/* Step1:显示提示符 */printf("[lvy@我的主机名 当前目录] # ");fflush(stdout);/* Step2:获取用户输入 */memset (command_line, '\0', sizeof(command_line) * sizeof(char));fgets(command_line, NUM, stdin); /* 从键盘获取,标准输入,stdin 获取到 C 风格的字符串,默认添加 '\0' */command_line[strlen(command_line) - 1] = '\0'; // 消除 '\0'/* Step3: 将接收到的字符串拆开 - 字符串切分 */command_args[0] = strtok(command_line, SEP);int idx = 1;/* 这里的 = 是故意这么写的,因为 strtok 截取成功返回字符串起始地址截取失败,返回 NULL */while (command_args[idx++] = strtok(NULL, SEP));//我们来测试一下看看 // for (int i = 0; i < idx; i++) {// printf("%d : %s\n", i, command_args[i]);// }// printf("%s\n", command_line);/* Step4. TODO *//* Step5. 创建进程,执行 */pid_t id = fork();if (id == 0) {/* child *//* Step6: 程序替换 */execvp (command_args[0], // 保存的是我们要执行的程序名字command_args);exit(1); // 只要执行到这里,子进程一定是替换失败了,直接退出。}/* Father */int status = 0;pid_t ret = waitpid(id, &status, 0);if (ret > 0) { // 等待成功printf("等待成功!sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);}} // end while}
通过上述代码,我们可以创建一个进程来执行用户输入的命令,并等待子进程结束。
为了增强Shell的用户体验,可以给一些常用命令添加颜色,例如 ls
命令:
/* 将接收到的字符串拆开 */
command_args[0] = strtok(command_line, SEP);
int idx = 1;
while ((command_args[idx++] = strtok(NULL, SEP)));/* 给 ls 命令添加颜色 */
if (strcmp(command_args[0], "ls") == 0) {command_args[idx++] = (char*)"--color=auto";
}
以上实现了一个简单的Shell,具备了基本的提示符显示、用户输入获取、命令解析、内建命令和普通命令的执行功能。
内建命令 环境变量
/* Shell 内置函数: 路径跳转 */
int ChangeDir(const char* new_path) {chdir(new_path);return 0; // 调用成功
}int main(void)
{.../* Step4. TODO 编写后面的逻辑,内建命令 */if (strcmp(command_args[0], "cd") == 0 && command_args[1] != NULL) {ChangeDir(command_args[1]); // 让调用方进行路径切换continue;}...
}
保存环境变量的字符串,不能是易变的,所以 strcpy mycommand,实现与argv的分离
补充:vim 文本替换
如何快速将mycmd换为myshell呢
通过如下操作
: %s/mycmd/myshell/g
就可以啦
细节设置的思考,在最后一部分,让我们先来看一下整体
整体代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>//创建子进程
#include <stdlib.h>//这些文件都是什么意思
#include <sys/types.h>
#include <sys/wait.h>#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];//存储切割之后的命令行
char pwd[LINE_SIZE];// 自定义环境变量表
char myenv[LINE_SIZE];
// 自定义本地变量表const char *getusername()
{return getenv("USER");
}const char *gethostname()
{return getenv("HOSTNAME");
}void getpwd()
{getcwd(pwd, sizeof(pwd));//获取当前工作目录
}void interact(char *cline, int size)//交互
{getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);char *s = fgets(cline, size, stdin);输入流进行输入assert(s);//断言不为空(void)s;//调用s避免报错// "abcd\n\0"cline[strlen(cline)-1] = '\0';//取消自动换行
}// ls -a -l | wc -l | head
int splitstring(char cline[], char *_argv[])
{int i = 0;argv[i++] = strtok(cline, DELIM);//区分全局变量和形参_while(_argv[i++] = strtok(NULL, DELIM)); // 故意写的=//NULL的设置才能实现往后移的切割return i - 1;//去除NULL
}void NormalExcute(char *_argv[])
{pid_t id = fork();if(id < 0){perror("fork");return;}else if(id == 0){//让子进程执行命令//execvpe(_argv[0], _argv, environ);execvp(_argv[0], _argv);//系统调用exit(EXIT_CODE);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) {//返回正确执行lastcode = WEXITSTATUS(status);}}
}
//切换路径,内建命令
//shell执行的内建命令,一个一个判断
int buildCommand(char *_argv[], int _argc)
{if(_argc == 2 && strcmp(_argv[0], "cd") == 0){chdir(argv[1]);//切换路径的函数getpwd();//获取当前路径sprintf(getenv("PWD"), "%s", pwd);return 1;}//导入环境变量exportelse if(_argc == 2 && strcmp(_argv[0], "export") == 0){strcpy(myenv, _argv[1]);//为什么要进行一个拷贝 是什么意思呢!!!!putenv(myenv);//argv 是我们定义的,每次都是变化的//所以不要写我们的地址,要提字符串的地址return 1;}else if(_argc == 2 && strcmp(_argv[0], "echo") == 0){if(strcmp(_argv[1], "$?") == 0){printf("%d\n", lastcode);//查看退出码lastcode=0;}else if(*_argv[1] == '$'){//打印环境变量char *val = getenv(_argv[1]+1);//获取if(val) printf("%s\n", val);}else{printf("%s\n", _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], "ls") == 0){_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0; //带有颜色之后返回,再执行普通命令
}int main()
{while(!quit){//1.许多软件启动起来就是死循环// 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txtinteract(commandline, sizeof(commandline));//对函数做了一下封装// commandline -> "ls -a -l -n\0" -> "ls" "-a" "-l" "-n"// 3. 子串分割的问题,解析命令行int argc = splitstring(commandline, argv);//如何将字串打散呢//strtok需要循环调用//while(argv[i++]=strtok(commandline,DELIM);//故意写的等号if(argc == 0) continue;// 4. 指令的判断 // debug//for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);//内键命令,本质就是一个shell内部的一个函数int n = buildCommand(argv, argc);// ls -a -l | wc -l// 4.0 分析输入的命令行字符串,获取有多少个|, 命令打散多个子命令字符串// 4.1 malloc申请空间,pipe先申请多个管道// 4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始. 输出重定向, 1->指定的一个管道的写端 // 中间:输入输出重定向, 0标准输入重定向到上一个管道的读端 1标准输出重定向到下一个管道的写端// 最后一个:输入重定向,将标准输入重定向到最后一个管道的读端// 4.3 分别让不同的子进程执行不同的命令--- exec* --- exec*不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向// 5. 普通命令的执行if(!n) NormalExcute(argv);//让命令0的时候执行}return 0;
}
重点思考
1.getenv和putenv是什么意思
getenv
函数用于获取指定环境变量的值。它的函数定义如下:
char *getenv(const char *name);
-
参数:
-
-
name
:要获取的环境变量的名称。
-
-
返回值:
-
-
如果指定的环境变量存在,那么返回一个指向该环境变量值的指针。
-
如果指定的环境变量不存在,则返回
NULL
。
-
以下是一个使用getenv
函数的示例:
#include <stdio.h>
#include <stdlib.h>int main() {char *path = getenv("PATH");if (path != NULL) {printf("PATH environment variable: %s\n", path);} else {printf("PATH environment variable not found.\n");}return 0;
}
成功实现对环境变量的调用啦
putenv函数
putenv
函数用于设置环境变量。它的函数定义如下:
int putenv(char *string);
-
参数:
-
-
string
:形式为"name=value"
的字符串,用于设置具体的环境变量及其值。
-
-
返回值:
-
-
成功时返回0。
-
失败时返回非零值。
-
以下是一个使用putenv
函数的示例:
#include <stdio.h>
#include <stdlib.h>int main() {char env_str[] = "MY_ENV=hello_world";if (putenv(env_str) == 0) {printf("Environment variable set successfully.\n");} else {perror("putenv");return 1;}char *my_env = getenv("MY_ENV");if (my_env != NULL) {printf("MY_ENV: %s\n", my_env);} else {printf("MY_ENV environment variable not found.\n");}return 0;
}
注意事项
-
内存管理:
-
-
getenv
返回的指针指向的是环境变量的值,不能直接修改此值,否则可能导致未定义行为。 -
putenv
函数参数所指向的字符串在函数调用后仍需存在,因为putenv
不会复制这个字符串。因此传递给putenv
的字符串应始终位于可修改的全局或堆内存中,而不是局部变量中。
-
-
线程安全性:
-
-
getenv
和putenv
函数在某些实现中不是线程安全的,特别是当修改同一个环境变量时。建议在多线程环境中使用setenv
和unsetenv
函数,它们是现代C库中提供的线程安全的替代函数。
-
总结
getenv
和putenv
是C语言中用于获取和设置环境变量的基本函数。通过了解并正确使用它们,可以更好地管理进程环境。
2.代码extern char **environ;
extern char **environ;
是C语言中的全局变量声明,用于访问当前进程的环境变量。为了理解这一行代码,我们需要理清以下几个关键概念:
环境变量的存储
在Unix和类Unix操作系统(如Linux)中,环境变量是一组键值对(例如PATH=/usr/bin
),用于向进程传递配置信息。每个环境变量项以字符串的形式存储在一个全局变量数组中。这个数组在进程启动时由操作系统初始化,并且每个程序都可以访问和修改它。
环境变量在内存中的表示
在内存中,环境变量通常表示为一个字符串数组,每个字符串保存一个环境变量。例如:
PATH=/usr/bin
HOME=/home/user
USER=user
...
这些字符串指针存储在一个全局变量数组中,即char **environ
。
extern关键字
extern
关键字用于声明一个全局变量,但不定义它。它告诉编译器这个变量是在别处(比如另一个源文件或由操作系统提供)定义的。因此,extern char **environ;
仅仅是一个声明,用来告知编译器这个变量在别处已经定义过,可以在当前文件中使用它。
为什么这样写?
在标准C库中,environ
变量实际上在系统库中已经定义,我们只需要在我们的程序中声明一下即可使用。这种方式使我们能够访问和操作环境变量。
这里是extern char **environ;
的具体含义:
-
声明:它声明了一个外部变量
environ
,是一个指向字符指针的指针。 -
外部定义:实际的环境变量数组由操作系统初始化,并定义在某个系统库中。
-
全局访问:通过这个声明,我们可以在任何源文件中访问和操作环境变量。
示例
下面是一个具体的例子,展示了如何使用environ
来访问并打印所有环境变量:
#include <stdio.h>// 声明外部环境变量数组
extern char **environ;int main(void) {// 指向环境变量数组的指针char **env = environ;// 遍历并打印所有环境变量while (*env) {printf("%s\n", *env);env++;}return 0;
}
就可以成功调用所有环境变量啦
总结
extern char **environ;
这一行代码的作用是声明一个指针数组,用于访问当前进程的环境变量。通过这种方式,我们可以在C程序中方便地读取和操作环境变量。
3.内建命令是什么
内建命令是指直接内置在操作系统内核中的一些命令,与普通的外部命令(外部程序文件)不同。这些内建命令是直接由shell解释器(如Bash、Zsh等)所处理,而不需要通过外部文件的方式来执行。这些内建命令通常在操作系统的shell环境中被频繁使用,并且执行速度更快,因为它们不需要创建新的进程来执行。
在Unix和类Unix操作系统中,通常会有一些内建命令,比如cd
、echo
、exit
等。这些命令不需要单独的可执行文件,而是直接由shell内核提供支持。当用户在shell中输入这些命令时,shell会直接处理它们,而不需要通过搜索系统路径来找到可执行文件。
值得一提的是,某些shell也允许用户通过自定义的方式添加新的内建命令,这样用户可以根据自己的需求来扩展shell的内建功能。
4.lastcode = WEXITSTATUS(status);
在C语言中,WEXITSTATUS(status)
是一个宏,用于从wait
或waitpid
返回的状态信息中提取子进程的退出状态。这个宏主要用于处理子进程的退出状态信息。
具体来说,WEXITSTATUS(status)
用于提取子进程在终止时传递给exit
或_exit
函数的退出状态。这个宏将状态信息进行适当的位操作,以获取子进程的退出状态值。
一般情况下,status
是由wait
或waitpid
函数返回的子进程状态,其中包含了有关子进程终止的信息,包括退出状态。通过使用WEXITSTATUS(status)
,可以将状态转换为子进程的退出状态,以便于后续处理和判断子进程的终止情况。
具体的用法示例如下:
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {pid_t pid;int status;int lastcode;pid = fork();if (pid < 0) {perror("fork failed");exit(1);} else if (pid == 0) {// This is the child processchar *args[] = {"ls", "-l", NULL};execvp(args[0], args);} else {// This is the parent processwaitpid(pid, &status, 0);//获得了子进程的退出码lastcode = WEXITSTATUS(status);printf("子进程的退出状态是:%d\n", lastcode);}return 0;
}
在这个例子中,WEXITSTATUS(status)
会从 status
中提取子进程的退出状态,并将其赋值给 lastcode
。然后这个退出状态可以被用来进行一些处理,比如根据不同的退出状态进行不同的操作。
需要注意的是,使用 WEXITSTATUS(status)
的前提是要确保传入的 status
参数是一个子进程终止的状态,因为该宏只能提取终止进程的退出状态信息。
5.execvp(_argv[0], _argv);的调用
在代码中,execvp(_argv[0], _argv)
是一个执行函数 execvp
的调用,用于执行磁盘文件上的程序。这个函数会用指定的程序文件(由 _argv[0]
指定)来覆盖当前进程的镜像,并且用 _argv
数组中的参数替换掉原来的程序参数。
相对路径执行指令
- 路径搜索:根据
PATH
环境变量,execvp
会在指定路径中查找可执行文件。 - 内存映射:找到可执行文件后,将其映射到当前进程地址空间。
- 替换镜像:用新程序的数据、堆栈、代码段替换当前进程的相应部分。
- 执行:新程序从其入口点开始执行,覆盖原进程的代码。
下面是对 execvp
函数调用的解释:
_argv[0]
表示要执行的程序文件的路径或名称。如果是一个程序的名称而没有路径,execvp
会在$PATH
环境变量指定的路径中搜索这个程序。_argv
是一个以空指针结尾的字符串数组,用于传递给新程序的命令行参数。数组的第一个元素(_argv[0]
)通常是被执行的程序的名称,随后的元素是程序的参数。- 当调用
execvp
时,操作系统会加载并执行指定的程序文件,并用_argv
数组中的参数来替换当前进程的参数。(因为默认会在PATH中查询,就和系统连接上了) - 如果
execvp
调用成功,则当前进程的镜像将被新程序替换,并且新程序开始执行。原来的程序代码和数据都会被新程序的代码和数据取代。 - 如果
execvp
调用失败,它会返回-1,并且当前进程的状态不会改变。
在简单的C代码中,execvp
函数通常与 fork
函数一起使用,例如:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {char *_argv[] = {"ls", "-l", "-a", NULL}; // 要执行的命令及参数组成的数组execvp(_argv[0], _argv); // 在新的程序中执行 ls 命令// 如果执行成功,下面的代码不会被执行perror("execvp"); // 如果 execvp 失败,打印出错误信息return 1;
}
需要注意的是,execvp
在执行成功后,原进程的代码和数据将会被新进程替换。这就意味着,如果 execvp
后面还有代码,那么这些代码将不会被执行,因为当前的程序已经不再存在。
实现shell, 一行一行的运行,先判断是否为内建命令
6._argc&_argv
_argv
:是一个字符指针数组,用于存储命令和参数。_argc
:是整型变量,用于存储命令和参数的数量。splitstring
函数将命令行字符串分割成多个子字符串,存储在_argv
中,并返回子字符串的数量_argc
。NormalExcute
函数使用_argv
数组创建子进程并执行命令。buildCommand
函数使用_argv
和_argc
处理内建命令。
相关文章:
![](https://i-blog.csdnimg.cn/direct/87fc8f4bd0fd48a690a2822e27273d6e.png)
【Linux】1w详解如何实现一个简单的shell
目录 实现思路 1. 交互 获取命令行 2. 子串分割 解析命令行 3. 指令的判断 内建命令 4. 普通命令的执行 补充:vim 文本替换 整体代码 重点思考 1.getenv和putenv是什么意思 2.代码extern char **environ; 3.内建命令是什么 4.lastcode WEXITSTATUS(sta…...
![](https://i-blog.csdnimg.cn/direct/cfa47e27a32f4108992a7852d4121233.png)
单目测距 单目相机测距 图片像素坐标转实际坐标的一种转换方案
需要相机位置固定 原图 红色的点是我们标注的像素点,这些红色的点我们知道它的像素坐标,以及以右下角相机位置为原点的x y 实际坐标数值 通过转换,可以得到整个图片内部其余像素点的实际坐标, 这些红色的点是通过转换关系生成的&…...
![](https://i-blog.csdnimg.cn/direct/193d18ceabf84872be907b4fb3dbd26a.png)
ensp防火墙综合实验作业+实验报告
实验目的要求及拓扑图: 我的拓扑: 更改防火墙和交换机: [USG6000V1-GigabitEthernet0/0/0]ip address 192.168.110.5 24 [USG6000V1-GigabitEthernet0/0/0]service-manage all permit [Huawei]vlan batch 10 20 [Huawei]int g0/0/2 [Huawei-…...
![](https://i-blog.csdnimg.cn/direct/273e9ffe60f74512a01c101ef96f4977.png)
【大模型LLM面试合集】大语言模型基础_Word2Vec
Word2Vec 文章来源:Word2Vec详解 - 知乎 (zhihu.com) 1.Word2Vec概述 Word2Vec是google在2013年推出的一个NLP工具,它的特点是能够将单词转化为向量来表示,这样词与词之间就可以定量的去度量他们之间的关系,挖掘词之间的联系。 …...
![](https://www.ngui.cc/images/no-images.jpg)
图论基础概念(详细讲解)
今天,我们讲解一下图论的概念,首先我们知道图是一个什么东西。 图你可以理解成一个网络系统,两个节点之间可能会有边,边链接两个节点,可能是有向(就比如说a只能往b,或者b只能往c),可能是无向&a…...
![](https://www.ngui.cc/images/no-images.jpg)
未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序报错的解决办法
今天在免费云服务器,三丰云上运行c#或python程序,都提示:未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序,PHP程序就直接乱码,odbc_connect(): SQL error: [Microsoft][ODBC ���&…...
![](https://www.ngui.cc/images/no-images.jpg)
《从零开始学习Linux》——开篇
前言 近日笔者新开专栏,《从零开始学习Linux》,Linux水深而且大,学了一圈之后,有懂得有不懂的,一直没有机会整体的全部重新捋一遍,本专栏的目的是,带着大家包括我自己重新学习Linux一遍这些知识…...
![](https://i-blog.csdnimg.cn/direct/8b1c3154360f4a28a50fd74d571a1de4.jpeg)
3D工艺大师快速生成装配动画,驱动汽车工业装配流程革新
在现代制造业的一般生产流程中,车间装配环节是产品由蓝图迈向市场前至关重要的一道工序。随着产品结构的日益复杂化和个性化需求的不断增长,车间装配工作面临着前所未有的挑战。高精密度的装配要求、错综复杂的组件关系以及频繁变更的生产计划࿰…...
![](https://i-blog.csdnimg.cn/direct/ab5716b1bc1a4438892835dbb0179ef8.png)
gateway
gateway核心概念 1. 路由(route) 路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL 和 配置的路由匹配。 2. 断言(predicates) 断言函数允许开发者去定义匹配Htt…...
![](https://i-blog.csdnimg.cn/direct/a4519ccceca348be8b79cf2720397e3d.jpeg)
第一个ffmpeg程序
在进行使用ffmpeg进行编写程序时,首先要记得进行注册设备(avdevice_register_all ),程序运行时,只需要注册一次就可以 avdevice_register_all 是 FFmpeg 多媒体处理库中的一个函数,其作用是注册所有可用的音…...
![](https://i-blog.csdnimg.cn/direct/9f1ff63356294417979b24a1a9ac95f0.png)
论文翻译:Large Language Models for Education: A Survey and Outlook
https://arxiv.org/abs/2403.18105 目录 教育领域的大型语言模型:一项调查和展望摘要1. 引言2. 教育应用中的LLM2.1 概述2.2 学习辅助2.2.1 问题解决(QS) 2.2.2 错误纠正(EC)2.2.3 困惑助手(CH)…...
![](https://www.ngui.cc/images/no-images.jpg)
python为什么慢?(自用)
《Cython系列》1. Cython 是什么?为什么要有 Cython?为什么我们要用 Cython? - 古明地盆 - 博客园 (cnblogs.com) 古明地盆的主页 - 博客园 (cnblogs.com) 我原本认为,python慢的原因是“逐行解释程序并执行”,那么我…...
![](https://i-blog.csdnimg.cn/direct/7fe897bf5b264b4a8fbd2ae2ae0ff40c.png)
压缩感知3——重构算法正交匹配追踪算法
算法流程 问题的实质是:AX Y 求解(A是M维,Y是N维且N>>M并且稀疏度K<M)明显X有无穷多解,重构过程是M次采样得到的采样值升维的过程。OMP算法的具体步骤:(1)用X表示信号,初始化残差e0 …...
![](https://i-blog.csdnimg.cn/direct/3c91e2674cf84380a554887c92d0712f.png)
“好物”推荐+Xshell连接实例+使用Conda创建独立的Python环境
目录 主题:好易智算平台推荐RTX 4090DGPU实例租用演示安装配置torch1.9.1cuda11.1.1环境引言:算力的新时代平台介绍:技术与信任的结晶使用案例:实际使用展示创建实例开始使用连接实例(下文演示使用Xshell连接ÿ…...
![](https://i-blog.csdnimg.cn/direct/06080e5a3ff146478810a2c93751064a.png)
浪潮天启防火墙TQ2000远程配置方法SSL-V偏、L2xx 配置方法
前言 本次设置只针对配置V偏,其他防火墙配置不涉及。建议把防火墙内外网都调通后再进行V偏配置。 其他配置可参考:浪潮天启防火墙配置手册 配置SSLVxx 在外网端口开启SSLVxx信息 开启SSLVxx功能 1、勾选 “启用SSL-Vxx” 2、设置登录端口号࿰…...
![](https://www.ngui.cc/images/no-images.jpg)
java八股文面试题
Java八股文面试题通常涵盖了Java语言的基础知识、高级特性、框架应用、数据库操作等多个方面。以下是一些常见的Java面试题及其详细回答,按照不同的主题进行分类: 一、Java基础 面向对象的特征有哪些? 抽象:忽略与当前目标无关的…...
![](https://img-blog.csdnimg.cn/ae666600e3f8492191b4e355bc04b05e.png)
【服务器】在Linux查看运行的Python程序,并找到特定的Python程序
在Linux查看运行的Python程序并找到特定的Python程序 写在最前面1. 使用ps命令查看所有Python进程查看详细信息 2. 使用pgrep命令查找Python进程ID 3. 使用top或htop命令使用top命令使用htop命令 4. 使用lsof命令查找Python进程打开的文件 5. 使用nvidia-smi命令查看GPU使用情况…...
![](https://img-blog.csdnimg.cn/img_convert/d75558bf641b5edd0357a2d0470659a6.png)
安全防御---防火墙实验1
安全防御—防火墙实验1 一、实验拓扑与要求 要求: 1、DMZ区内的服务器,办公区仅能在办公时间内(9:00-18:00)可以访问,生产区的设备全天可以访问 2、生产区不允许访问互联网,办公区和游客区允许访问互联网 …...
![](https://www.ngui.cc/images/no-images.jpg)
SpringBoot配置Swagger开启页面访问限制
在Spring Boot项目中配置Swagger时,开启页面访问限制通常意味着你希望控制哪些用户或角色可以访问Swagger UI文档页面。由于Swagger UI是一个静态资源,它本身并不直接支持基于角色的访问控制(RBAC)。但是,你可以通过Sp…...
![](https://www.ngui.cc/images/no-images.jpg)
前端代码基本逻辑-vue3
前端vue建立过程 安装nodejs 官网下载安装,并且记住安装路径,记得配置系统变量Path 安装VUE/CLI npm install -g vue/cli --全局安装vue 使用VUE/CLI生成代码框架 vue create your-project-name --我的your-project-name为web 运行项目 cd your-…...
![](https://csdnimg.cn/release/blog_editor_html/release2.3.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=N7T8)
怎么用PPT录制微课?详细步骤解析!
随着信息技术的不断发展,微课作为一种新型的教学形式,因其短小精悍、针对性强等特点,在教育领域得到了广泛的应用。而PPT作为一款常用的演示工具,不仅可以用来制作课件,还可以利用其内置的录屏功能或结合专业的录屏软件…...
![](https://www.ngui.cc/images/no-images.jpg)
Git使用方法
Git是一种分布式版本控制系统,它可以记录和管理软件开发过程中的变更。 Git的基本概念包括以下几个部分: 仓库(Repository):Git用仓库来存储项目的代码和历史记录。一个仓库可以包含多个分支。 分支(Bran…...
![](https://www.ngui.cc/images/no-images.jpg)
HTTP的请求报文和响应报文是怎样的,有哪些常见字段?
http报文分为请求报文和响应报文 请求报文包含:请求行、请求头、空行、、请求体 请求行包含: 方法:即要执行的操作,如get,post、put、delet 资源路径:请求的资源的URL HTTP版本:使用的http协议…...
![](https://i-blog.csdnimg.cn/direct/0c396ca73fbd4fa7ae709ebd263843c5.png)
自注意力简介
在注意力机制中,每个查询都会关注所有的键值对并生成一个注意力输出。如果查询q,键k和值v都来自于同一组输入,那么这个注意力就被称为是自注意力(self-attention)。自注意力这部分理论,我觉得台大李宏毅老师…...
![](https://www.ngui.cc/images/no-images.jpg)
【GameFramework框架】7-2、GameFramework框架是否“过度设计”?
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群:398291828大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录: https://blog.csdn.net/q764424567/article/details/1…...
![](https://img-blog.csdnimg.cn/direct/e349662f1b4446e0b3d4c9c92f198217.png)
RISC-V异常处理流程概述(2):异常处理机制
RISC-V异常处理流程概述(2):异常处理机制 一、异常处理流程和异常委托1.1 异常处理流程1.2 异常委托二、RISC-V异常处理中软件相关内容2.1 异常处理准备工作2.2 异常处理函数2.3 Opensbi系统调用的注册一、异常处理流程和异常委托 1.1 异常处理流程 发生异常时,首先需要执…...
![](https://www.ngui.cc/images/no-images.jpg)
Unity3D中如何降低游戏的Drawcall详解
在Unity3D游戏开发中,Drawcall是一个至关重要的性能指标,它指的是CPU通知GPU绘制一个物体的命令次数。过多的Drawcall会导致游戏性能下降,因此优化Drawcall的数量是提高游戏性能的关键。本文将详细介绍Unity3D中降低Drawcall的几种主要方法&a…...
![](https://i-blog.csdnimg.cn/direct/01081024fcba4a0a81ed8135b33f2abf.png)
小程序-设置环境变量
在实际开发中,不同的开发环境,调用的接口地址是不一样的 例如:开发环境需要调用开发版的接口地址,生产环境需要正式版的接口地址 这时候,我们就可以使用小程序提供了 wx.getAccountInfoSync() 接口,用来获取…...
![](https://i-blog.csdnimg.cn/direct/11158fac96564b9ab318056374b3ff09.png#pic_center)
【RabbitMQ】一文详解消息可靠性
目录: 1.前言 2.生产者 3.数据持久化 4.消费者 5.死信队列 1.前言 RabbitMQ 是一款高性能、高可靠性的消息中间件,广泛应用于分布式系统中。它允许系统中的各个模块进行异步通信,提供了高度的灵活性和可伸缩性。然而,这种通…...
![](https://www.ngui.cc/images/no-images.jpg)
RuntimeError: Unexpected error from cudaGetDeviceCount
RuntimeError: Unexpected error from cudaGetDeviceCount 0. 引言1. 临时解决方法 0. 引言 使用 vllm-0.4.2 部署时,多卡正常运行。升级到 vllm-0.5.1 时,报错如下: (VllmWorkerProcess pid30692) WARNING 07-12 08:16:22 utils.py:562] U…...
![](/images/no-images.jpg)
delphi WordPress/深圳高端seo公司助力企业
启动页的作用 在我遇到这个实际问题之前,我一直认为启动页的作用是美化产品,提升软件逼格。但实际上,它更重要的是起到了一个拦截器的作用。比如,当App首页需要动态得从网上加载数据时,就必须请求网络权限,…...
![](/images/no-images.jpg)
温州外贸公司网站建设公司排名/世界十大网站排名
java基础之抽象类: 一、父类不确定性:(所谓抽象方法就是没有实现的方法,也就是没有方法体的方法) 1.当父类的某些方法,需要声明,但是又不确定如何实现是,可以将其声明为抽象方法&am…...
![](/images/no-images.jpg)
网站抄袭我网站/商品热搜词排行榜
题目:将一个正整数分解质因数。例如:输入90,打印出902*3*3*5。 程序分析:对n进行分解质因数,应先找到一个最小的质数k,然后按下述步骤完成: (1)如果这个质数恰等于(小于的时候,继续…...
![](/images/no-images.jpg)
网站建设背景图/湖南企业seo优化
1.listbox控件如何多选取值 将listbox控件中的selectionmode属性值设置为multiple,listbox控件可以支持多选 取值问题 假设控件为listbox1 Dim i As Integer Dim str As String For i 0 To ListBox1.Items.Count - 1 If ListBox1.Items(i).Selected …...
![](http://upload-images.jianshu.io/upload_images/3397380-6ef519bbc705ce28.png?imageMogr2/auto-orient/strip|imageView2/2/w/654/format/webp)
老网站做成适合手机端的网站怎么做/网络营销模式下品牌推广途径
Spring中有很多继承于aware中的接口,这些接口到底是做什么用到的。 awre.pngaware,翻译过来是知道的,已感知的,意识到的,所以这些接口从字面意思应该是能感知到所有Aware前面的含义。 先举个BeanNameAware的例子,实现B…...
![](/images/no-images.jpg)
如何做英文网站的外链/seo描述快速排名
常用的shell命令包括:ls(列出文件)、cd(切换目录)、mkdir(创建目录)、mv(移动或重命名文件)、rm(删除文件)、cat(显示文件内容)、echo(显示文本)、man(查看命令手册)等。...