自主Shell命令行解释器
什么是命令行
我们一直使用的"ls","cd","pwd","mkdir"等命令,都是在命令行上输入的,我们之前对于命令行的理解:
命令行是干啥的?是为我们做命令行解释的。
命令行这个东西实际上是我们登入时,系统默认创建的,我们最终输入的命令都交给他了。
感性理解
可是,在我们看来,这个命令行究竟该如何理解呢?
想象一下,你走进一家餐厅,坐下来后,你会对服务员说:“请给我来一份牛排,五分熟,加一些胡椒。” 这里的“你”就是用户,“服务员”就是命令行界面(Command Line Interface,简称CLI),而“牛排,五分熟,加一些胡椒”就是你通过命令行输入的命令。
命令行界面(CLI):
命令行界面就像餐厅里的服务员,它等待你告诉它要做什么。在计算机中,命令行界面是一个程序,它等待你输入命令来告诉计算机执行什么任务。
输入命令:
就像你对服务员说“来一份牛排”,你在命令行中输入命令,比如
ls
(列出目录内容)、cd
(改变目录)、pwd
(显示当前目录)或mkdir
(创建新目录)。执行命令:
命令行界面接收你的命令后,就像服务员去厨房告诉厨师你的点餐一样,命令行界面会将你的命令发送给操作系统,操作系统会找到相应的程序来执行这个命令。
反馈结果:
执行完毕后,就像服务员把牛排端给你一样,命令行界面会显示命令的执行结果,比如列出文件列表、显示当前路径或确认目录创建成功。
交互性:
你可以不断地对命令行界面说话(输入命令),它也会不断地回应你(显示结果),这种一问一答的方式就是命令行界面的交互性。
灵活性:
就像你可以告诉服务员你的特殊要求一样,命令行界面允许你通过组合不同的命令和选项来完成复杂的任务。
所以,命令行界面就像是你的个人助理,你通过它来告诉计算机你想要做什么,它则负责将你的命令翻译成计算机可以理解和执行的指令。这种方式虽然看起来不如图形界面直观,但它非常强大和灵活,可以让有经验的用户快速、高效地完成各种任务。
这里我们只能感性的理解命令行,下面,我们来深刻理解一下,到底什么是命令行:我们用实现一个自主的Shell来理解!!!
实现原理
考虑下面这个与 shell 典型的互动:
1 [root@localhost epoll]# ls
2 client.cpp readme.md server.cpp utility.h
3 [root@localhost epoll]# ps
4 PID TTY TIME CMD
5 3451 pts/0 00:00:00 bash
6 3514 pts/0 00:00:00 ps
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell 由标识为 sh 的方块代表,它随着时间的流逝从左向右移动。shell 从用户读入字符串 "ls"。shell 建立一个新的进程,然后在那个进程中运行 ls 程序并等待那个进程结束。然后 shell 读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束。
所以要写一个 shell,需要循环以下过程:
-
获取命令行
-
解析命令行
-
建立一个子进程(fork)
-
替换子进程(execvp)
-
父进程等待子进程退出(wait)
根据这些思路,和我们前面的学的技术,就可以自己来实现一个 shell 了。
深刻理解(实现)
命令行其实就是一门语言,里面有许多词法语法分析,但是我们今天并不考虑。我们将最基本的谈谈,就足以理解了。
在我们用户登入的时候,会出现一个类似如下的命令行:
lfz@hcss-ecs-ff0f:~/lesson/lesson17/myshell$
我们发现其组成是:用户名+@+主机名+当前工作目录,这些我们如果想要的话是都可以通过系统调用进行获取的,我们今天暂时不用这些系统调用,我们只要在Shell启动的时候,打印出类似的字符串就OK了:
这时候,我们需要写基本的几个接口:
获取用户名的函数:(使用环境变量获取)
//获取用户名
const char *GetUserName()
{const char *name = getenv("USER");return name == NULL ? "None" : name;
}
获取主机名的函数:
//获取主机名
const char *GetHostName()
{const char* hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}
获取当前路径的函数:
//获取当前路径
const char *GetPwd()
{const char* pwd = getenv("PWD");return pwd == NULL ? "None" : pwd;
}
我们发现打印出来的和真正命令行的有点不一样,因为这是Ubuntu系统,我就临时export了一个HOSTNAME,而且当前路径是完整显示,这个问题我们稍后解决,主要是为了便于和真正的命令行区分。
对于真正的命令行,接着会开在这里,这是在等待用户输入:
我们在命令行输入ls -a -l其实本质是被命令行读取成字符串:"ls -a -l" ,我们可以定义一个字符数组:
#define COMMAND_SIZE 1024char commandline[COMMAND_SIZE];
我们接下来采用C/C++混编,我们像字符串我们不使用STL当中的数据结构(string),因为我们后面用到的许多接口都是系统调用,而系统调用都是C式的系统调用,如果我们用纯C++去写很多代码时,就可能要适配一下C接口,比如说这个C接口是指针,上层使用的是string,那么这时候,这个接口就需要适配一下,而且有许多格式适配的要求在里面。
我们对于命令行读取,可以使用fgets实现:
fgets
是 C 标准库中的一个函数,用于从指定的文件流中读取一行文本。它将读取的文本存储在用户提供的字符数组中,是文件输入操作中非常常用的函数。
char *fgets(char *str, int n, FILE *stream);
//ls -a -l->"ls -a -l"char *c = fgets(commandline,sizeof(commandline),stdin);if(c==NULL){//失败return 1;}//获取成功printf("echo %s\n",commandline);//用于回显,测试我们写的有无bug
我们测试发现:
这里应该注意的是:我们printf的时候加了一个"\n",那么就不应该有空行的,不然不加"\n"的话,那么下一条命令行应该是紧紧跟在"-al"后面的,这是因为:我们进行命令行输入的时候,按了回车键,我们不想让整个命令行带回车,我们可以将字符数组的最后一个元素设置为0:
commandline[strlen(commandline)-1]=0;
清理"\n":
这里没有strlen为0越界的情况,因为我们起码是要按一次回车键的,strlen最少是1的,这个问题我们不需要担心!!!
接下来,我们往优化的方向去实现与修改:
我们先来认识一个C函数:
snprintf
是 C 语言中一个非常有用的函数,用于将格式化的数据写入字符串,并且可以指定最大写入长度,从而避免缓冲区溢出的安全风险。它是 sprintf
函数的安全替代品。
int snprintf(char *str, size_t size, const char *format, ...);
参数:
-
str
:指向字符数组的指针,用于存储生成的字符串。 -
size
:指定str
数组的最大长度,包括结尾的空字符\0
。 -
format
:格式字符串,指明了后续参数如何格式化。 -
...
:可变参数,根据format
字符串中的格式说明符,提供相应的数据。
返回值:
-
成功时,
snprintf
返回写入字符串中的字符数,不包括结尾的空字符\0
。 -
如果输出被截断(即
size
小于所需空间),则返回一个大于或等于size
的值。 -
如果发生错误,返回一个负值。
我们可以进行不同功能的分开实现,而且Shell是一个死循环,不停的在等待用户输入。
1.输出命令行提示符:
//命令行格式
#define FORMAT "[%s@%s %s]# "//初始化命令行
bool MakeCommandLine(char cmd_prompt[], int size)
{snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());return true;
}//打印命令行提示符
void PrintCommandPrompt()
{char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}
//1.输出命令行提示符PrintCommandPrompt();
2.获取用户输入的命令:
我们可以使用Ctrl+删除键来删除已经输入的数据!!!
//用户输入是否成功
bool GetCommandLine(char *out, int size)
{//ls -a -l->"ls -a -l"char *c = fgets(out, size, stdin);if (c == NULL){//失败return false;}//获取成功out[strlen(out) - 1] = 0;if (strlen(out) == 1){return false;}return true;
}
//2.获取用户输入的命令char commandline[COMMAND_SIZE];if (!GetCommandLine(commandline, COMMAND_SIZE)){continue; //输入有问题就重新输入}
分享一下我的查bug的痛苦,就是下面这个,大家以后也可以多多注意一下:
main
函数中的 if(!GetCommandLine(commandline,COMMAND_SIZE));
:
这里的分号 ;
会导致 if
语句立即结束,不会执行 continue
。修改为 if(!GetCommandLine(commandline,COMMAND_SIZE)) continue;
。
3.命令行分析"ls -a -l"->"ls" "-a" "-l":
为的是未来要使用程序替换,这种形式的参数,方便调用!!!
我们将一个字符串进行拆分有很多做法,我们使用比较原生的方式,首先,我们要拆分成什么形式?我们拆分成多个元素后,又改如何快速的找到对应的元素呢?找到每一个元素?
我们可以定义一个全局的g_argv[ ](全局的默认是初始化为0的),我们就为我们的代码提供了一个全局的命令行参数表,我们知道,Shell默认要获取用户的输入,然后将用户的输入的信息构建成一张命令行参数表,这张表是在Shell内部维护的。我们就可以通过这种方式实现。(就是将一个字符串变成一个指针数组):
我们可以使用一个函数接口:
strtok
是 C 语言标准库中的一个函数,用于分割字符串。它搜索字符串,找出由分隔符(也称为“delimiters”)分隔的部分,并返回指向下一个“token”(分隔的部分)的指针。strtok
函数通常用于将一行文本分解成单独的单词或命令参数。
char *strtok(char *str, const char *delim);
第一次截取了ls,而且strtok只能切一次,切成功的话,返回下一个字串的起始地址,要切历史字串的话,需要传NULL/nullptr,而不是继续commandline!!!
//命令行分析
bool CommandParse(char *commandline)
{
#define SEP " "g_argc = 0;//"ls -a -l"->"ls" "-a" "-l"g_argv[g_argc++] = strtok(commandline, SEP); // 截取了ls,strtok只能切一次,切成功的话,返回下一个字串的起始地址while (g_argv[g_argc++] = strtok(NULL, SEP)); // 最后会切到NULL// 修正:确保g_argc在最后一次分割后不递增g_argc--;return g_argv>0 ? true : false;}
在内部定义宏并不是说这个宏是局部的,而是为了:所见即所得,就这个接口自己要用!!!只是一种代码风格。
//3.命令行分析"ls -a -l"->"ls" "-a" "-l",未来要使用程序替换,这种形式的参数,方便调用!!!if(!CommandParse(commandline)){continue;//如果解析失败,不执行以下代码了,解析成功才可执行!!!}
4.执行命令
我们不想让自己去执行任务,因为我自己也有要执行的代码,这时候,我们要创建子进程来帮忙了。我们创建出来派他做两件事:
- 做我的一部分事情;
- 去执行全新的程序(进程替换)。
我的事情已经ok了,这时候子进程就去执行新的程序吧:exec()类接口调用。
//执行命令
int Execute()
{pid_t id = fork();//创建子进程if(id == 0){//childexecvp(g_argv[0],g_argv);exit(1);}//fatherpid_t rid = waitpid(id,NULL,0);(void)rid;//rid使用一下,让编译器不报警return 0;
}
//4.执行命令Execute();
现在,我们就可以完成命令的执行了!!!
我们现在来完善上面的路径问题:(这里我们使用C++接口)
// / | /a/b/c
std::string DirName(const char *pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH)return SLASH;auto pos = dir.rfind(SLASH);//从后向前找if(pos == std::string::npos)return "BUG?";return dir.substr(pos+1);//+1是不想要体现"/"
}//初始化命令行
bool MakeCommandLine(char cmd_prompt[], int size)
{snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());return true;
}
这里还是体现指针的位置要认真注意。
4.1内建命令
上面只是一个补充,接下来,我们执行该程序发现,我们"cd ..",并没有达到我们预期的效果:
因为目前说有的命令都是子进程去执行的,所以我们执行"cd .."时,是子进程把自己的pwd出路径改了,父进程没有改呀!!!
我们在真正的命令行当中cd执行后,更改的是父进程bash的路径,往后再创建其他子进程,就会在新路径下运行了,因为所以的子进程的PCB都是拷贝自父进程的!!!所以遇到cd这样的命令,是不能够让子进程去执行的,而是要让父进程自己亲自执行,自己亲自执行之后,把自己的路径切掉,不要再创建子进程了,这种命令,我们称之为:内建命令。
在类 Unix 系统中,像 cd
(更改当前目录)、exit
(退出 shell)、set
(设置环境变量)等命令通常不通过创建子进程来执行,而是直接在当前 shell 进程中执行。这些命令被称为“内建命令”(built-in commands)或“shell 内建”(shell builtins)。
内建命令的特点:
-
直接执行:内建命令直接在 shell 进程中执行,不需要创建新的子进程。
-
修改 shell 状态:这些命令通常修改 shell 的内部状态,如当前目录、环境变量、别名等。
-
效率:由于不需要创建新的进程,执行效率较高。
为什么需要内建命令:
-
状态修改:某些命令需要修改 shell 自身的状态(如
cd
修改当前目录),这些状态是进程级的,不能通过子进程来修改父进程的状态。 -
避免资源浪费:如果每个命令都创建一个子进程,会导致大量的进程创建和销毁,消耗系统资源。内建命令避免了这种资源浪费。
-
安全性:直接在 shell 进程中执行可以避免潜在的安全问题,如子进程的权限提升等。
所以我们接下来要做的是根据我们提取,解析出来的命令,我们要判断这个命令是否是内建命令,所以我们在shell当中应该存在一个接口:检测并处理内建命令:
实现内建命令:
在实现一个简单的命令行解释器时,可以通过以下步骤来处理内建命令:
-
命令解析:将用户输入的命令行解析成命令和参数。(已经在g_argv表中保存了)
-
内建命令检查:检查解析出的命令是否为内建命令。
-
执行内建命令:如果是内建命令,直接在当前进程中执行相应的操作。
-
执行外部命令:如果不是内建命令,通过
fork
和exec
创建的子进程来执行外部程序。
我们要怎么才能够让父进程亲自执行呢,所以我们要来认识一个接口:
在C语言中,chdir()
函数用于改变当前进程的工作目录。它是一个标准的POSIX函数,定义在<unistd.h>
头文件中。
#include <unistd.h>int chdir(const char *path);
-
path
:目标目录的路径,可以是绝对路径(如/home/user/documents
),也可以是相对路径(如../projects
)。
我们就可以简单实现cd命令:
//检测并处理内建命令
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];if(where == "-"){// Todu}else if(where == "~"){// Todu}else{chdir(where.c_str());}}return true;}return false;
}
我们执行之后发现:
我们是实现了cd的功能,但是我们的命令行显示的路径并没有跟着变化!!!
我们cd命令执行完毕后,程序进入循环,然后进行命令行构建和打印,构建命令行打印的时候,对应的输出命令行是需要获取用户名/主机名/当前工作路径。
这时候,我们应该思考一个问题: 当一个进程他的当前工作路径发生变化的时候,那么系统当中还存在一个环境变量叫做PWD,所以是我的进程的当前工作路径先变,环境变量再变,还是环境变量先变,我的当前工作路径再变呢?我们需要搞清楚顺序!!!
其实是我们的进程的路径先变了,然后系统/我的Shell再把环境变量更新了,更新工作一般由Shell来做,但是我们现在的代码还没有做这个工作(也就是文章到这里我们自主的Shell还没有更新环境变量的能力手段),所以我们发现我们当前进程的工作路径他发生变化了,但是环境变量却没有变化,所以我们每次从环境变量获取,都是获取到老的路径,所以我们采用环境变量去获取路径的GetPwd()就不太好了,这时候我们应该使用系统调用了:
在C语言中,getcwd()
函数用于获取当前工作目录(Current Working Directory,CWD)。它是一个标准的POSIX函数,定义在<unistd.h>
头文件中。通过getcwd()
,你可以获取当前进程的工作目录路径,并将其存储到一个字符串中。
#include <unistd.h>char *getcwd(char *buf, size_t size);
-
buf
:一个字符数组,用于存储当前工作目录的路径。 -
size
:buf
的大小(以字节为单位),确保足够存储路径字符串。
const char* pwd = getcwd(cwd,sizeof(cwd));//cwd是我们为了测试在全局定义的:char pwd[1024]
我们测试就可以发现:
虽然我们解决了命令行的路径问题,但是我们执行env的时候,发现环境变量的PWD并没有随着发生改变,真正的Shell是会发生改变的,我们可以调整以下代码:
//获取当前路径
const char *GetPwd()
{//const char* pwd = getenv("PWD");const char* pwd = getcwd(cwd,sizeof(cwd));if(pwd != nullptr){snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",pwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}
接下来,我们可以优化:命令封装,将cd命令封装成一个接口:
//command
bool Cd()
{if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else{std::string where = g_argv[1];if(where == "-"){// Todu}else if(where == "~"){// Todu}else{chdir(where.c_str());}}return true;
}
我们之前有认识到:
echo $?
用于显示最近一个程序执行后的退出码。我们内建命令不考虑,因为他只能是成功的,而我们一个命令是否执行成功,我们要看Execute()执行的指定命令,而且我们在真正的Shell中使用echo命令可以打印环境变量,还有字符串,所以echo本质上也是属于一个内建命令。这时,我们应该要清楚:
在我们Linux系统当中,有的命令即是内建,又是外置命令,这么理解吧:这个命令被实现了两次Shell自己内部一份,应用级的系统层面上也有一份,就像cd,echo,我们可以which
命令可以找到外置命令的路径,但无法找到内建命令的路径:
$ which echo
$ which /bin/echo
/bin/echo
为什么要这样呢?其实磁盘上的这种内建命令的镜像(上面这种)跟我们没关系,我们未来写代码的时候,还需要让我们的系统支持一个东西叫做Shell脚本,所以他为了能够支持Shell脚本,很多命令在磁盘上要有对应的二进程的文件存在,这样Shell脚本在写的时候就会让子进程执行任何命令,不然只有Shell自己认识Shell命令,磁盘当中没有对应的命令,比如说cd命令的话,那么脚本的子进程就无法执行这样的cd命令了。
某些命令既作为内建命令又作为外置命令存在,是为了在性能优化和脚本兼容性之间取得平衡。内建命令提供了快速执行和对Shell内部状态的直接操作,而外置命令则确保了脚本在子进程中的可执行性和跨平台兼容性。这种设计使得Shell脚本能够更高效地运行,同时保持了系统的灵活性和通用性。
今天我们不做区分,我们写的是Shell,我们认为,我们今天要执行的cd/echo/....都是内建命令的用法,我们先来退出码的实现:对Execute()接口的丰富:
//执行命令
int Execute()
{pid_t id = fork();//创建子进程if(id == 0){//childexecvp(g_argv[0],g_argv);exit(1);}int status = 0;//fatherpid_t rid = waitpid(id,&status,0);//(void)rid;//rid使用一下,让编译器不报警if(rid > 0){lastcode = WEXITSTATUS(status);//拿到退出码}return 0;
}
除了echo,我们还有一个export导环境变量,export内建命令接口的实现需要实现,说到环境变量,我们知道Shell内部维护了两张表:命令行参数表,还有环境变量表:
Shell环境变量表是从父Shell继承而来的。在Unix和Linux系统中,环境变量是一个全局的变量集合,用于存储系统和用户配置信息,例如路径、用户主目录、语言设置等。当一个新进程(如子Shell)被创建时,它会从父进程(父Shell)继承环境变量的副本。
Shell启动的时候,从系统中获取环境变量,但是我们今天自己的shell是没有这一套的,没有从相关配置去读取,我做不到,因为那个配置文件是Shell脚本,这是要求我们新学一门语言的!!!
I can't do it !!!
我们的自主实现的shell的环境变量信息应该从父shell统一来。
我们要初始化我们的环境变量表就应该将系统的环境变量表拿出来,使用extern char** environ;
打开我们的shell程序,应该初始化,就是将父shell的环境变量表继承下来: (我们真正的Shell是通过配置文件来的。)
//环境变量表
char *g_env[MAXENVP];
int g_envs = 0;//环境变量的个数
//获取环境变量表
void InitEnv()
{extern char** environ;memset(g_env,0,sizeof(g_env));//本来要从配置文件来的,今天直接从父Shell来://1.获取环境变量for(int i=0;environ[i];i++){//1.1申请空间g_env[i] = (char*)malloc(strlen(environ[i])+1);strcpy(g_env[i],environ[i]);g_envs++;}g_env[g_envs++] = "HAHA=for_test";g_env[g_envs] = NULL;//2.导成环境变量for(int i=0;g_env[i];i++){putenv(g_env[i]);}//environ是C语言提供的全局变量,这时候就可以指向父进程的环境变量表了,一旦fork之后,子进程就照样拿到全局变量environ,就可以把所有的环境变量给拿到了environ = g_env;
}
export
命令用于将变量从当前Shell的局部变量提升为环境变量。这些环境变量会被存储在全局的环境变量表中,从而可以被当前Shell及其所有子进程继承。
在我们的自定义Shell中,环境变量表是全局的。这意味着所有在当前Shell中通过export
命令设置的环境变量都会被存储在这个全局表中。这个全局表是通过environ
变量来管理的。
environ
是C语言标准库提供的一个全局变量,它是一个指向字符串数组的指针,每个字符串表示一个环境变量(格式为KEY=VALUE
)。在自定义Shell中,我们可以将environ
指向我们的全局环境变量表,这样就可以通过标准的C库函数(如getenv()
、putenv()
等)来操作环境变量。
当父Shell调用fork()
创建子进程时,子进程会继承父进程的环境变量表。这是因为fork()
会复制父进程的内存空间(包括全局变量environ
),因此子进程会自动获得父进程的环境变量表。
在子进程中,environ
变量仍然指向父进程的环境变量表。因此,子进程可以通过标准的C库函数访问和使用这些环境变量。这种继承机制确保了子进程能够使用父进程设置的环境变量,从而保证了程序的正确运行。
我们的shell还需要支持重定向,还有管道的实现,这个我们等学的知识面更广了再来谈谈。
我们还有一个alias:
在Linux中,alias
是一个非常有用的Shell特性,用于为命令创建别名(Aliases)。别名允许用户为复杂的命令或命令组合定义一个简短的名称,从而简化命令行操作。别名通常用于提高工作效率,尤其是当需要频繁执行某些命令时。
比如说:" ls -l "就是被映射为" ll ",我们对命令起别名是使用alias:
alias 别名='命令'
这里的alias也是一个内建命令,我们可以使用hash表(别名映射表)来实现:
//别名映射表
std::unordered_map<std::string, std::string> alias_list;
更多的功能实现我们可以自己实现接口添加,以下是本篇的完整代码:
源代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <unordered_map> // hash#define COMMAND_SIZE 1024// 命令行格式
#define FORMAT "[%s@%s %s]# "// 下面是Shell定义的全局数据#define MAXARGC 128
#define MAXENVP 100// 命令行参数/参数表
char *g_argv[MAXARGC];
int g_argc = 0;// 环境变量表
char *g_env[MAXENVP];
int g_envs = 0; // 环境变量的个数// 别名映射表
std::unordered_map<std::string, std::string> alias_list;// last exit code
int lastcode = 0;// for test
char cwd[1024];
char cwdenv[1024];// last_cwd 用于记录上一次的工作目录
char last_cwd[1024] = {0};// 获取用户名
const char *GetUserName() {const char *name = getenv("USER");return name == NULL ? "None" : name;
}// 获取主机名
const char *GetHostName() {const char *hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}// 获取当前路径
const char *GetPwd() {const char *pwd = getcwd(cwd, sizeof(cwd));if (pwd != nullptr) {snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", pwd);putenv(cwdenv);}return pwd == NULL ? "None" : pwd;
}// 获取家目录
const char *GetHome() {const char *home = getenv("HOME");return home == NULL ? "None" : home;
}// 获取环境变量表
void InitEnv() {extern char **environ;memset(g_env, 0, sizeof(g_env));// 从父Shell获取环境变量for (int i = 0; environ[i]; i++) {g_env[i] = strdup(environ[i]); // 使用 strdup 复制字符串g_envs++;}g_env[g_envs++] = strdup("HAHA=for_test"); // 添加一个测试环境变量g_env[g_envs] = NULL;// 将环境变量表赋给 environenviron = g_env;
}// cd 命令
bool Cd() {if (g_argc == 1) {// 如果没有指定路径,默认切换到家目录std::string home = GetHome();if (home.empty()) {std::cerr << "cd: HOME not set" << std::endl;return false;}chdir(home.c_str());} else {std::string where = g_argv[1];if (where == "~") {// 切换到家目录std::string home = GetHome();if (home.empty()) {std::cerr << "cd: HOME not set" << std::endl;return false;}chdir(home.c_str());} else if (where == "-") {// 切换到上一次的工作目录if (last_cwd[0] != '\0') {chdir(last_cwd);} else {std::cerr << "cd: no previous directory saved" << std::endl;return false;}} else {// 切换到指定目录if (chdir(where.c_str()) != 0) {std::cerr << "cd: " << where << ": No such file or directory" << std::endl;return false;}}}// 更新 last_cwd 为当前目录getcwd(last_cwd, sizeof(last_cwd));return true;
}// echo 命令
bool Echo() {if (g_argc == 2) {std::string opt = g_argv[1];if (opt == "$?") {std::cout << lastcode << std::endl;lastcode = 0; // 清0} else if (opt[0] == '$') {std::string env_name = opt.substr(1);const char *env_value = getenv(env_name.c_str());if (env_value) {std::cout << env_value << std::endl;} else {std::cout << std::endl;}} else {std::cout << opt << std::endl;}}return true;
}// export 命令
bool Export() {if (g_argc < 2) {std::cerr << "export: not enough arguments" << std::endl;return true;}for (int i = 1; i < g_argc; ++i) {std::string env_str = g_argv[i];size_t equal_pos = env_str.find('=');if (equal_pos == std::string::npos) {std::cerr << "export: invalid variable name" << std::endl;return true;}std::string key = env_str.substr(0, equal_pos);std::string value = env_str.substr(equal_pos + 1);char *env_entry = new char[key.size() + value.size() + 2];sprintf(env_entry, "%s=%s", key.c_str(), value.c_str());putenv(env_entry);g_env[g_envs++] = env_entry;}return true;
}// alias 命令
bool Alias() {if (g_argc == 1) {// 显示所有别名for (const auto &entry : alias_list) {std::cout << entry.first << "=" << entry.second << std::endl;}} else if (g_argc == 2) {// 删除别名std::string nickname = g_argv[1];if (alias_list.find(nickname) != alias_list.end()) {alias_list.erase(nickname);} else {std::cerr << "alias: " << nickname << ": not found" << std::endl;}} else if (g_argc == 3) {// 添加别名std::string nickname = g_argv[1];std::string target = g_argv[2];alias_list[nickname] = target;} else {std::cerr << "alias: invalid arguments" << std::endl;}return true;
}// 获取当前路径的父目录
std::string DirName(const char *pwd) {std::string dir = pwd;if (dir == "/") return "/";auto pos = dir.rfind('/');if (pos == std::string::npos) return "/";return dir.substr(0, pos);
}// 初始化命令行
bool MakeCommandLine(char cmd_prompt[], int size) {snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());return true;
}// 打印命令行提示符
void PrintCommandPrompt() {char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}// 获取用户输入的命令
bool GetCommandLine(char *out, int size) {char *c = fgets(out, size, stdin);if (c == NULL) {return false;}out[strcspn(out, "\n")] = 0; // 去掉换行符if (strlen(out) == 0) {return false;}return true;
}// 命令行分析
bool CommandParse(char *commandline) {g_argc = 0;g_argv[g_argc++] = strtok(commandline, " ");while ((g_argv[g_argc++] = strtok(NULL, " ")));g_argc--;// 检查别名std::string cmd = g_argv[0];auto it = alias_list.find(cmd);if (it != alias_list.end()) {// 如果是别名,替换为实际命令std::string new_cmd = it->second;delete[] g_argv[0]; // 释放旧的命令g_argv[0] = new char[new_cmd.size() + 1];strcpy(g_argv[0], new_cmd.c_str());}return g_argc > 0;
}// 打印解析后的命令和参数
void PrintArgv() {for (int i = 0; i < g_argc; ++i) {printf("g_argv[%d]->%s\n", i, g_argv[i]);}
}// 检测并处理内建命令
bool CheckAndExecBuiltin() {std::string cmd = g_argv[0];if (cmd == "cd") {return Cd();} else if (cmd == "echo") {return Echo();} else if (cmd == "export") {return Export();} else if (cmd == "alias") {return Alias();}return false;
}
// 执行命令
int Execute() {pid_t id = fork();if (id == 0) {execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0) {lastcode = WEXITSTATUS(status);}return 0;
}// 清理函数
void Clear() {for (int i = 0; i < g_envs; ++i) {free(g_env[i]);}
}int main()
{//Shell启动的时候,从系统中获取环境变量InitEnv();while (1) //命令行不会停止,要不断获得用户输入{//1.输出命令行提示符PrintCommandPrompt();//2.获取用户输入的命令char commandline[COMMAND_SIZE];if (!GetCommandLine(commandline, COMMAND_SIZE)){continue; //输入有问题就重新输入}//3.命令行分析"ls -a -l"->"ls" "-a" "-l",未来要使用程序替换,这种形式的参数,方便调用!!!if(!CommandParse(commandline)){continue;//如果解析失败,不执行以下代码了,解析成功才可执行!!!}//sub_4.检测并处理内建命令if(CheckAndExecBuiltin()){continue;}//4.执行命令Execute();}//清理函数Clear();return 0;
}// #include <iostream>
// #include <cstdio>
// #include <cstring>
// #include <cstdlib>
// #include <unistd.h>
// #include <sys/types.h>
// #include <sys/wait.h>
// #include <string>
// #include <unistd.h>
// #include <unordered_map>//hash// #define COMMAND_SIZE 1024// //命令行格式
// #define FORMAT "[%s@%s %s]# "// //下面是Shell定义的全局数据// #define MAXARGC 128
// #define MAXENVP 100// //命令行参数/参数表
// char *g_argv[MAXARGC];
// int g_argc = 0;// //环境变量表
// char *g_env[MAXENVP];
// int g_envs = 0;//环境变量的个数// //别名映射表
// std::unordered_map<std::string, std::string> alias_list;// //last exit code
// int lastcode = 0;// //for test
// char cwd[1024];
// char cwdenv[1024];// //获取用户名
// const char *GetUserName()
// {
// const char *name = getenv("USER");
// return name == NULL ? "None" : name;
// }// //获取主机名
// const char *GetHostName()
// {
// const char* hostname = getenv("HOSTNAME");
// return hostname == NULL ? "None" : hostname;
// }// //获取当前路径
// const char *GetPwd()
// {
// //const char* pwd = getenv("PWD");
// const char* pwd = getcwd(cwd,sizeof(cwd));
// if(pwd != nullptr)
// {
// snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",pwd);
// putenv(cwdenv);
// }
// return pwd == NULL ? "None" : pwd;
// }// //获取家目录
// const char*GetHome()
// {
// const char* home = getenv("HOME");
// return home == NULL ? "None" : home;
// }// //获取环境变量表
// void InitEnv()
// {
// extern char** environ;
// memset(g_env,0,sizeof(g_env));// //本来要从配置文件来的,今天直接从父Shell来:
// //1.获取环境变量
// for(int i=0;environ[i];i++)
// {
// //1.1申请空间
// g_env[i] = (char*)malloc(strlen(environ[i])+1);
// strcpy(g_env[i],environ[i]);
// g_envs++;
// }
// g_env[g_envs++] = "HAHA=for_test";
// g_env[g_envs] = NULL;// //2.导成环境变量
// for(int i=0;g_env[i];i++)
// {
// putenv(g_env[i]);
// }
// //environ是C语言提供的全局变量,这时候就可以指向父进程的环境变量表了,一旦fork之后,子进程就照样拿到全局变量environ,就可以把所有的环境变量给拿到了
// environ = g_env;
// }// //command
// bool Cd()
// {
// if(g_argc == 1)
// {
// std::string home = GetHome();
// if(home.empty()) return true;
// chdir(home.c_str());
// }
// else
// {
// std::string where = g_argv[1];
// if (where == "~")
// {
// std::string home = GetHome();
// if (!home.empty())
// {
// chdir(home.c_str());
// }
// }
// else if (where == "-") {
// // 这里需要记录上一次的工作目录
// // 假设有一个全局变量 `char* last_cwd` 用于存储上一次的工作目录
// if (last_cwd != nullptr)
// {
// chdir(last_cwd);
// }
// }
// else
// {
// chdir(where.c_str());
// }
// }
// return true;
// }// bool Echo()
// {
// if(g_argc == 2)
// {
// //echo "hello world"
// //echo $?
// //echo $PATH
// std::string opt = g_argv[1];
// if(opt == "$?")
// {
// std::cout<<lastcode<<std::endl;
// lastcode = 0;//这里要注意清0!!!
// }
// else if(opt[0] == '$')
// {
// std::string env_name = opt.substr(1);//去掉第一个字符剩下的就是环境变量的名字
// const char *env_value = getenv(env_name.c_str());
// if(env_value)//不为空
// {
// std::cout<<env_value<<std::endl;
// }
// }
// else
// {
// std::cout<<opt<<std::endl;
// }
// }
// return true;
// }// bool Export()
// {
// if (g_argc < 2) {
// std::cerr << "export: not enough arguments" << std::endl;
// return true;
// }
// for (int i = 1; i < g_argc; ++i) {
// std::string env_str = g_argv[i];
// size_t equal_pos = env_str.find('=');
// if (equal_pos == std::string::npos) {
// std::cerr << "export: invalid variable name" << std::endl;
// return true;
// }
// std::string key = env_str.substr(0, equal_pos);
// std::string value = env_str.substr(equal_pos + 1);
// char* env_entry = new char[key.size() + value.size() + 2];
// sprintf(env_entry, "%s=%s", key.c_str(), value.c_str());
// putenv(env_entry);
// g_env[g_envs++] = env_entry; // 将新环境变量添加到环境变量表
// }
// return true;
// }// bool Alias()
// {
// if (g_argc < 3) {
// std::cerr << "alias: not enough arguments" << std::endl;
// return true;
// }
// std::string nickname = g_argv[1];
// std::string target = g_argv[2];
// alias_list[nickname] = target; // 将别名映射添加到哈希表
// return true;
// }// // / | /a/b/c
// std::string DirName(const char *pwd)
// {
// #define SLASH "/"
// std::string dir = pwd;
// if(dir == SLASH)return SLASH;
// auto pos = dir.rfind(SLASH);//从后向前找
// if(pos == std::string::npos)return "BUG?";
// return dir.substr(pos);//+1是不想要体现"/"
// }// //初始化命令行
// bool MakeCommandLine(char cmd_prompt[], int size)
// {
// snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
// //snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());// return true;
// }// //打印命令行提示符
// void PrintCommandPrompt()
// {
// char prompt[COMMAND_SIZE];
// MakeCommandLine(prompt, sizeof(prompt));
// printf("%s", prompt);
// fflush(stdout);
// }// //用户输入是否成功
// bool GetCommandLine(char *out, int size)
// {
// //ls -a -l->"ls -a -l"
// char *c = fgets(out, size, stdin);
// if (c == NULL)
// {
// //失败
// return false;
// }
// //获取成功
// out[strlen(out) - 1] = 0;
// if (strlen(out) == 0)
// {
// return false;
// }
// return true;
// }// //命令行分析
// bool CommandParse(char *commandline)
// {
// #define SEP " "
// g_argc = 0;
// //"ls -a -l"->"ls" "-a" "-l"
// g_argv[g_argc++] = strtok(commandline, SEP); // 截取了ls,strtok只能切一次,切成功的话,返回下一个字串的起始地址
// while (g_argv[g_argc++] = strtok(NULL, SEP)); // 最后会切到NULL// // 修正:确保g_argc在最后一次分割后不递增
// g_argc--;// return g_argc>0 ? true : false;
// }// //打印解析后的命令和参数
// void PrintArgv()
// {
// for (int i = 0; i < g_argc; ++i) {
// printf("g_argv[%d]->%s\n", i, g_argv[i]);
// }
// }// //检测并处理内建命令
// bool CheckAndExecBuiltin()
// {
// std::string cmd = g_argv[0];
// if(cmd == "cd")
// {
// return Cd();
// }
// else if(cmd == "echo")
// {
// return Echo();
// }
// else if(cmd == "export")
// {
// //找不到新增
// //找到了覆盖
// return Export();
// }
// else if(cmd == "alias")
// {
// return Alias();
// }
// return false;
// }// //执行命令
// int Execute()
// {
// pid_t id = fork();//创建子进程
// if(id == 0)
// {
// //child
// execvp(g_argv[0],g_argv);
// exit(1);
// }
// int status = 0;
// //father
// pid_t rid = waitpid(id,&status,0);
// //(void)rid;//rid使用一下,让编译器不报警
// if(rid > 0)
// {
// lastcode = WEXITSTATUS(status);//拿到退出码
// }
// return 0;
// }// //别名检查
// void CheakAlias()
// {
// // 检测别名
// for (int i = 0; i < g_argc; ++i) {
// auto it = alias_list.find(g_argv[i]);
// if (it != alias_list.end()) {
// // 替换别名为实际命令
// delete[] g_argv[i];
// g_argv[i] = new char[it->second.size() + 1];
// strcpy(g_argv[i], it->second.c_str());
// }
// }
// }// //clean
// void Clear() {
// for (int i = 0; i < g_envs; ++i) {
// free(g_env[i]);
// }
// }
// int main()
// {
// //Shell启动的时候,从系统中获取环境变量
// //但是我们今天自己的shell是没有这一套的,没有从相关配置去读取,我做不到,因为那个配置文件是Shell脚本,这是要求我们新学一门语言的!!!
// //我们的自主实现的shell的环境变量信息应该从父shell统一来
// InitEnv();// while (1) //命令行不会停止,要不断获得用户输入
// {
// //1.输出命令行提示符
// PrintCommandPrompt();// //2.获取用户输入的命令
// char commandline[COMMAND_SIZE];
// if (!GetCommandLine(commandline, COMMAND_SIZE))
// {
// continue; //输入有问题就重新输入
// }
// //printf("echo %s\n", commandline); //用于回显,测试我们写的有无bug// //3.命令行分析"ls -a -l"->"ls" "-a" "-l",未来要使用程序替换,这种形式的参数,方便调用!!!
// if(!CommandParse(commandline))
// {
// continue;//如果解析失败,不执行以下代码了,解析成功才可执行!!!
// }// //PrintArgv();// //检测别名
// // 检测别名
// CheakAlias();// //sub_4.检测并处理内建命令
// if(CheckAndExecBuiltin())
// {
// continue;
// }// //4.执行命令
// Execute();
// }
// //清理函数
// Clear();
// return 0;
// }
相关文章:
自主Shell命令行解释器
什么是命令行 我们一直使用的"ls","cd","pwd","mkdir"等命令,都是在命令行上输入的,我们之前对于命令行的理解: 命令行是干啥的?是为我们做命令行解释的。 命令行这个东西实际上是我们…...
git笔记-简单入门
git笔记 git是一个分布式版本控制系统,它的优点有哪些呢?分为以下几个部分 与集中式的版本控制系统比起来,不用担心单点故障问题,只需要互相同步一下进度即可。支持离线编辑,每一个人都有一个完整的版本库。跨平台支持…...
重新刷题求职2-DAY1
DAY1 1.704. 二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 最普通的二分查找,查用的习惯左闭右…...
浅析DDOS攻击及防御策略
DDoS(分布式拒绝服务)攻击是一种通过大量计算机或网络僵尸主机对目标服务器发起大量无效或高流量请求,耗尽其资源,从而导致服务中断的网络攻击方式。这种攻击方式利用了分布式系统的特性,使攻击规模更大、影响范围更广…...
路径规划之启发式算法之二十九:鸽群算法(Pigeon-inspired Optimization, PIO)
鸽群算法(Pigeon-inspired Optimization, PIO)是一种基于自然界中鸽子群体行为的智能优化算法,由Duan等人于2014年提出。该算法模拟了鸽子在飞行过程中利用地标、太阳和磁场等导航机制的行为,具有简单、高效和易于实现的特点,适用于解决连续优化问题。 更多的仿生群体算法…...
SwiftUI 在 Xcode 预览修改视图 FetchedResults 对象的属性时为什么会崩溃?
概览 从 SwiftUI 诞生那天起,让秃头码农们又爱又恨的 Xcode 预览就在界面调试中扮演了及其重要的角色。不过,就是这位撸码中的绝对主角却尝尝在关键时刻“掉链子”。 比如当修改 SwiftUI 视图中 FetchedResults 对象的属性时,Xcode 预览可能…...
pytorch实现基于Word2Vec的词嵌入
PyTorch 实现 Word2Vec(Skip-gram 模型) 的完整代码,使用 中文语料 进行训练,包括数据预处理、模型定义、训练和测试。 1. 主要特点 支持中文数据,基于 jieba 进行分词 使用 Skip-gram 进行训练,适用于小数…...
MVC 文件夹:架构之美与实际应用
MVC 文件夹:架构之美与实际应用 引言 MVC(Model-View-Controller)是一种设计模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种架构模式不仅提高了代码的可维护性和可扩展性,而且使得开发流程更加清晰。本文将深入探讨MVC文…...
Python在线编辑器
from flask import Flask, render_template, request, jsonify import sys from io import StringIO import contextlib import subprocess import importlib import threading import time import ast import reapp Flask(__name__)RESTRICTED_PACKAGES {tkinter: 抱歉&…...
The Simulation技术浅析(四):随机数生成
随机数生成技术 是 The Simulation 中的核心组成部分,广泛应用于蒙特卡洛模拟、密码学、统计建模等领域。随机数生成技术主要分为 伪随机数生成器(PRNG,Pseudo-Random Number Generator) 和 真随机数生成器(TRNG,True Random Number Generator)。 1. 伪随机数生成器(PR…...
centos stream 9 安装 libstdc++-static静态库
yum仓库中相应的镜像源没有打开,libstdc-static在CRB这个仓库下,但是查看/etc/yum.repos.d/centos.repo,发现CRB镜像没有开启。 解决办法 如下图开启CRB镜像, 然后执行 yum makecache yum install glibc-static libstdc-static…...
Java自定义IO密集型和CPU密集型线程池
文章目录 前言线程池各类场景描述常见场景案例设计思路公共类自定义工厂类-MyThreadFactory自定义拒绝策略-RejectedExecutionHandlerFactory自定义阻塞队列-TaskQueue(实现 核心线程->最大线程数->队列) 场景1:CPU密集型场景思路&…...
Shell $0
个人博客地址:Shell $0 | 一张假钞的真实世界 我们已经知道在Shell中$0表示Shell脚本的文件名,但在有脚本调用的情形中,子脚本中的$0会是什么值呢?我们通过下面的实例来看。 已测试系统列表: Mac OS X EI Capitan 1…...
C++ Primer 标准库类型string
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
51c嵌入式~电路~合集25
我自己的原文哦~ https://blog.51cto.com/whaosoft/13241709 一、“开关电源”和“普通电源”的区别 什么叫开关电源 随着电力电子技术的发展和创新,使得开关电源技术也在不断地创新。目前,开关电源以小型、轻量和高效率的特点被广泛应用几乎所有的电…...
Vue3学习笔记-Vue开发前准备-1
一、安装15.0或更高版本的Node.js node -v npm -v 二、创建Vue项目 npm init vuelatest 三、Vue项目结构 node_modules: Vue项目运行的依赖文件public:资源文件夹package.json:信息描述文件...
架构技能(四):需求分析
需求分析,即分析需求,分析软件用户需要解决的问题。 需求分析的下一环节是软件的整体架构设计,需求是输入,架构是输出,需求决定了架构。 决定架构的是软件的所有需求吗?肯定不是,真正决定架构…...
51单片机 05 矩阵键盘
嘻嘻,LCD在RC板子上可以勉强装上,会有一点歪。 一、矩阵键盘 在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式;采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态。…...
服务SDK三方新版中央仓库和私服发布详解
预备信息Github仓库发布Gradle版本匹配Gradle项目构建全局变量定义Gradle项目Nexus仓库配置与发布过程Gradle项目发布至Sonatype中央仓库配置过程总结当我们在实现一个项目技术总结、工具类封装或SDK封装,通常是为了方便开发者使用特定服务或平台而提供的一组工具和API。您可能…...
jmeter响应数据编码设置
jmeter响应数据编码设置 如果查看结果树的响应数据存在中文乱码,可以尝试以下方法: 1、找到jmeter的bin目录下的 jmeter.properties 文件,修改如下配置: 去掉sampleresult.default.encodingUTF-8前面的#号 2、保存文件后&#x…...
FPGA学习篇——开篇之作
今天正式开始学FPGA啦,接下来将会编写FPGA学习篇来记录自己学习FPGA 的过程! 今天是大年初六,简单学一下FPGA的相关概念叭叭叭! 一:数字系统设计流程 一个数字系统的设计分为前端设计和后端设计。在我看来࿰…...
Leetcode:680
1,题目 2,思路 首先就是判断它不发生改变会不会是回文如果不是回文,那么俩个指针从前往后与从后往前做对比如果俩字符不同,那就俩种选择,一种是保留前面的字符去掉后面字符,另一种是其反然后俩种选择只要满…...
Python教学:文档处理及箱线图等
代码1: import os import pandas as pd import numpy as py import os.path from os import listdir import openpyxl from openpyxl import Workbook import re import matplotlib.pyplot as plt # 导入matplotlib的绘图模块,用于可视化 cwdos.getcwd…...
SQLGlot:用SQLGlot解析SQL
几十年来,结构化查询语言(SQL)一直是与数据库交互的实际语言。在一段时间内,不同的数据库在支持通用SQL语法的同时演变出了不同的SQL风格,也就是方言。这可能是SQL被广泛采用和流行的原因之一。 SQL解析是解构SQL查询…...
C++STL(一)——string类
目录 一、string的定义方式二、 string类对象的容量操作三、string类对象的访问及遍历操作四、string类对象的修改操作五、string类非成员函数 一、string的定义方式 string是个管理字符数组的类,其实就是字符数组的顺序表。 它的接口也是非常多的。本章介绍一些常…...
C++ Primer 迭代器
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
排序算法--归并排序
归并排序是分治法的经典实现,适合大规模数据排序,尤其适合需要稳定排序的场景(如数据库排序) #include <stdlib.h> // 用于动态内存分配 // 合并两个已排序的子数组 void merge(int arr[], int left, int mid, int right) …...
深入探讨DICOM医学影像中的WADO服务及其具体实现
1. 引言 随着数字化医学影像技术的普及,如何高效、安全地存储、管理和共享医学影像数据成为医疗行业亟待解决的关键问题。DICOM(Digital Imaging and Communications in Medicine)作为国际公认的医学影像标准,在全球范围内广泛应…...
自定义数据集 使用paddlepaddle框架实现逻辑回归
导入必要的库 import numpy as np import paddle import paddle.nn as nn 数据准备: seed1 paddle.seed(seed)# 1.散点输入 定义输入数据 data [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6…...
信息学奥赛一本通 2112:【24CSPJ普及组】地图探险(explore) | 洛谷 P11228 [CSP-J 2024] 地图探险
【题目链接】 ybt 2112:【24CSPJ普及组】地图探险(explore) 洛谷 P11228 [CSP-J 2024] 地图探险 【题目考点】 1. 模拟 2. 二维数组 3. 方向数组 在一个矩阵中,当前位置为(sx, sy),将下一个位置与当前位置横纵坐…...
xxl-job 在 Java 项目的使用 以一个代驾项目中的订单模块举例
能搜到这里的最起码一定知道 xxl-job 是用来干什么的,我就不多啰嗦怎么下载以及它的历史了 首先我们要知道 xxl-job 这个框架的结构,如下图: xxl-job-master:xxl-job-admin:调度中心xxl-job-core:公共依赖…...
javaEE-8.JVM(八股文系列)
目录 一.简介 二.JVM中的内存划分 JVM的内存划分图: 堆区:编辑 栈区:编辑 程序计数器:编辑 元数据区:编辑 经典笔试题: 三,JVM的类加载机制 1.加载: 2.验证: 3.准备: 4.解析: 5.初始化: 双亲委派模型 概念: JVM的类加…...
模型/O功能之提示词模板
文章目录 模型/O功能之提示词模板什么是提示词模板提示词模板的输入和输出 使用提示词模板构造提示词 模型/O功能之提示词模板 在LangChain框架中,提示词不是简单的字符串,而是一个更复杂的结构,是一个“提示词工程”。这个结构中包含一个或多…...
[Proteus仿真]基于51单片机的智能温控系统
[Proteus仿真]基于51单片机的智能温控系统 基于51单片机的智能温控系统:DS18B20精准测温LCD1602双屏显示三键设置上下限声光报警,支持温度校准、抗干扰设计、阈值记忆。 一.仿真原理图 二.模块介绍 温度采集模块(DS18B20࿰…...
掌握 HTML5 多媒体标签:如何在所有浏览器中顺利嵌入视频与音频
系列文章目录 01-从零开始学 HTML:构建网页的基本框架与技巧 02-HTML常见文本标签解析:从基础到进阶的全面指南 03-HTML从入门到精通:链接与图像标签全解析 04-HTML 列表标签全解析:无序与有序列表的深度应用 05-HTML表格标签全面…...
ChatGPT与GPT的区别与联系
ChatGPT 和 GPT 都是基于 Transformer 架构的语言模型,但它们有不同的侧重点和应用。下面我们来探讨一下它们的区别与联系。 1. GPT(Generative Pre-trained Transformer) GPT 是一类由 OpenAI 开发的语言模型,基于 Transformer…...
浅谈线段树
文章同步发布于洛谷,建议前往洛谷查看。 前言 蒟蒻终于学会线段树(指【模板】线段树 1 1 1)啦! 线段树思想 我们先来考虑 P3372(基础线段树模板题)给的操作: 区间修改(增加&am…...
深度解读 Docker Swarm
一、引言 随着业务规模的不断扩大和应用复杂度的增加,容器集群管理的需求应运而生。如何有效地管理和调度大量的容器,确保应用的高可用性、弹性伸缩和资源的合理分配,成为了亟待解决的问题。Docker Swarm 作为 Docker 官方推出的容器集群管理工具,正是在这样的背景下崭露头…...
在线知识库的构建策略提升组织信息管理效率与决策能力
内容概要 在线知识库作为现代企业信息管理的重要组成部分,具有显著的定义与重要性。它不仅为组织提供了一个集中存储与管理知识的平台,还能够有效提升信息检索的效率,促进知识的创新和利用。通过这样的知识库,企业可以更好地应对…...
网件r7000刷回原厂固件合集测评
《网件R7000路由器刷回原厂固件详解》 网件R7000是一款备受赞誉的高性能无线路由器,其强大的性能和可定制性吸引了许多高级用户。然而,有时候用户可能会尝试第三方固件以提升功能或优化网络性能,但这也可能导致一些问题,如系统不…...
为什么命令“echo -e “\033[9;0]“ > /dev/tty0“能控制开发板上的LCD不熄屏?
为什么命令"echo -e “\033[9;0]” > /dev/tty0"能控制开发板上的LCD不熄屏? 在回答这个问题前请先阅读我之前写的与tty和终端有关的博文 https://blog.csdn.net/wenhao_ir/article/details/145431655 然后再来看这条命令的解释就要容易些了。 这条…...
vscode软件操作界面UI布局@各个功能区域划分及其名称称呼
文章目录 abstract检查用户界面的主要区域官方文档关于UI的介绍 abstract 检查 Visual Studio Code 用户界面 - Training | Microsoft Learn 本质上,Visual Studio Code 是一个代码编辑器,其用户界面和布局与许多其他代码编辑器相似。 界面左侧是用于访…...
【Java基础-42.3】Java 基本数据类型与字符串之间的转换:深入理解数据类型的转换方法
在 Java 开发中,基本数据类型与字符串之间的转换是非常常见的操作。无论是从用户输入中读取数据,还是将数据输出到日志或界面,都需要进行数据类型与字符串之间的转换。本文将深入探讨 Java 中基本数据类型与字符串之间的转换方法,…...
【ActiveMq RocketMq RabbitMq Kafka对比】
以下是 ActiveMQ、RocketMQ、RabbitMQ 和 Kafka 的对比表格,从复杂性、功能、性能和适用场景等方面进行整理: 特性ActiveMQRocketMQRabbitMQKafka开发语言JavaJavaErlangScala/Java协议支持AMQP、STOMP、MQTT、OpenWire 等自定义协议AMQP、STOMP、MQTT …...
csapp笔记3.6节——控制(1)
本节解决了x86-64如何实现条件语句、循环语句和分支语句的问题 条件码 除了整数寄存器外,cpu还维护着一组单个位的条件码寄存器,用来描述最近的算数和逻辑运算的某些属性。可检测这些寄存器来执行条件分支指令。 CF(Carry Flag)…...
网站快速收录:如何优化网站音频内容?
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/60.html 为了优化网站音频内容以实现快速收录,以下是一些关键的策略和步骤: 一、高质量音频内容创作 原创性: 确保音频内容是原创的,避免使…...
音视频入门基础:RTP专题(8)——使用Wireshark分析RTP
一、引言 通过Wireshark可以抓取RTP数据包,该软件可以从Wireshark Go Deep 下载。 二、通过Wireshark抓取RTP数据包 首先通过FFmpeg将一个媒体文件转推RTP,生成RTP流: ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec copy -an -f rtp …...
4-图像梯度计算
文章目录 4.图像梯度计算(1)Sobel算子(2)梯度计算方法(3)Scharr与Laplacian算子4.图像梯度计算 (1)Sobel算子 图像梯度-Sobel算子 Sobel算子是一种经典的图像边缘检测算子,广泛应用于图像处理和计算机视觉领域。以下是关于Sobel算子的详细介绍: 基本原理 Sobel算子…...
深入解析 Redis AOF 机制:持久化原理、重写优化与 COW 影响
深入解析 Redis AOF 机制:持久化原理、重写优化与 COW 影响 1. 引言2. AOF 机制详解2.1 AOF 解决了什么问题?2.2 AOF 写入机制2.2.1 AOF 的基本原理2.2.2 AOF 运行流程2.2.3 AOF 文件刷盘策略 3. AOF 重写机制3.1 AOF 文件为什么会变大?3.2 解…...
机器学习day8
自定义数据集 ,使用朴素贝叶斯对其进行分类 代码 import numpy as np import matplotlib.pyplot as pltclass1_points np.array([[2.1, 2.2], [2.4, 2.5], [2.2, 2.0], [2.0, 2.1], [2.3, 2.3], [2.6, 2.4], [2.5, 2.1]]) class2_points np.array([[4.0, 3.5], …...