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

Linux《自主Shell命令行解释器》

在上一篇的进程控制当中我们已经了解了进程退出、进程等待以及进程替换的相关概念,那么在了解了这些的概念之后接下来在本篇当中我们就可以结合之前我们学习的知识来实现一个自主的Shell命令行解释器,通过Shell的实现能让我们进一步的理解操作系统当中的shell外壳是如何将我们输入的指令执行起来的。接下来就开始本篇的学习,一起加油吧!!!


 1.实现目标

在本篇当中我们实现的myshell目标是实现一个与Linux当中类似的命令行解释器,也就是能实现先获取用户输入的内容;之后再进行命令行解析;之后再进行相应的指令执行。并且除了能处理普通的指令外还能处理echo、cd等内建命令

2.实现myhsell

在实现myshell的过程当中只使用C的方式在一些部分的代码实现会较为繁琐,因此在本篇当中实现myshell的过程当中会采取C与C++混编的方式。

首先创建一个名为myshell的目录

在该目录当中创建一个名为myshell.cc的文件来存储实现的源代码,再创建对应的makefile文件

2.1 命令行提示符实现 

在实现myshell的第一步就是先来实现和Linux当中的命令行类似的命令行提示符,在用户每次输入对应的的指令之前都需要将对于的命令行提示符先输出出来。

 接下来就试着来实现命令行的提示符,通过Linux当中大的命令行提示符就可以看出需要输出对应当前登录的用户名、当前的主机名以及当前所在的路径

以上的这些数据从环境变量表当中都可以获取,以下就先来实现获取以上三个环境变量的函数。

#include<iostream>                                                                                                                                   
#include<cstdio>  
#include<stdlib.h>
#include<cstring>//获取当前登录的用户名
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;
}

得到了以上对应的环境变量之后接下来就要分析如何将得到的环境变量输入到命令行提示符当中,通过此时还需要构建对应的命令行提示符的格式。 

观察Linux当中的命令行提示符就可以看到是划分为三个部分的,那么接下来就着实现对应命令行提示符的格式。

注:在此还需要解决一个小细节就是在命令行提示符当中最后的文件需要是当前路径的末尾文件,而以上创建的GetPwd得到的是完整的路径,这就需要对以上得到的路径再进行切割。

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "   //将得到的当前路径当中得到最后的文件名
std::string DirName(const char* pwd)
{//得到当前路径以/之后的文件名
#define SLASH "/"std::string dir=pwd;if(dir==SLASH)return SLASH;auto pos=dir.rfind(SLASH);//当pos的返回值为npos时此时出现bug                                                                                                               if(pos==std::string::npos)return "BUG";return dir.substr(pos+1);
}//构建命令行提示符的输出格式    
void MakeCommandLine( char cmd_prompt[],int size )    
{    snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());    
}    //输出命令行提示符    
void  PrintCommandPrompt()    
{    char prompt[COMMAND_SIZE];    MakeCommandLine(prompt,sizeof(prompt));    printf("%s",prompt);    fflush(stdout);                                                                                                                                  
}    

以上函数实现之后接下来就可以main函数当中调用对应的PrintCommandPrompt函数。在此需要在用户输入对应数据之后再输出命令行提示符就需要死循环的调用PrintCommandPrompt函数。

int main()
{while(true){//1.输出命令行提示符PrintCommandPrompt();//进行阻塞,或者会死循环打印命令行提示符char arr[1024];                                                                                                                              scanf("%s",arr);//……}return 0;
}

 将以上的myshell.cc进行编译链接出名为myshell的可执行程序,运行myshell就可以看到可以输出命令行提示符了

2.1 获取用户输入的命令

以上实现了自定义shell当中的命令行提示符后接下来就从标准输入流当中获取用户输入的数据

实现的函数代码如下所示:

#include<cstring>//从标准输入流当中获取用户输入的内容    
bool GetCommandLine(char* out,int size)    
{                                      char* c=fgets(out,size,stdin);     if(c==NULL)return false;           //使得数组最后以\0结尾                                                                                                                           out[strlen(out)-1]=0;              if(strlen(out)==0)return false;    return true;                    }         

注:以上使用到了strlen就需要在myshell.cc当中包含的头文件添加上cstring 

实现了对应的函数之后接下来在main函数当中调用该函数。

int main()    
{                                                                                                                                                    //从父进程当中获取环境变量表    //InitEnv();    while(true)    {    //1.输出命令行提示符    PrintCommandPrompt();    //2.获取用户输入的命令    //创建数组存储用户输出的数据    char commandline[COMMAND_SIZE];    if(!GetCommandLine(commandline,sizeof(commandline)))  //当以上的函数返回值为false时就说明用户为输入有效的命令,那么此时就不再执行之后的代码  continue;    }return 0;
}

2.3 命令行解析

以上创建了commandline数组来存储用户输入的命令,那么接下来就需要对获得的命令进行解析并存储至命令行参数表当中。

因此接下来首先要做的是创建对应的命令行参数表。

//1.命令行参数表                                                                                                                                     
#define MAXARGC 128 //指向命令行参数表的指针   
char* g_argv[MAXARGC];//统计命令行参数的变量g_argc    
int g_argc=0;    

在自定义shell当中创建了命令行接下来就来实现命令行解析的功能

//进行对用户输入的命令进行解析    
bool CommandParse(char *commandline)    
{    
#define SEP " "    g_argc=0;    //使用strtok进行参数的分割,之后再将参数存储至命令行参数表当中    g_argv[g_argc++]=strtok(commandline,SEP);    while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));    //最终统计的参数个数会比实际的多一个,此时需要将g_argc减一    g_argc--;    //最后通过判断g_argc的个数来判断用户是否输入有效的命令                                                                                           return g_argc>0? true:false;       
}             

以上在命令行参数解析功能的实现当中就使用到strtok函数来实现,在分隔不同的参数时以" "为标识。 

接下来就在mian函数当中调用以上的函数

int main()    
{                                                                                                                                                    //从父进程当中获取环境变量表    //InitEnv();    while(true)    {    //1.输出命令行提示符    PrintCommandPrompt();    //2.获取用户输入的命令    //创建数组存储用户输出的数据    char commandline[COMMAND_SIZE];    if(!GetCommandLine(commandline,sizeof(commandline)))  //当以上的函数返回值为false时就说明用户为输入有效的命令,那么此时就不再执行之后的代码  continue;//3.命令行解析if(!CommandParse(commandline))//当以上的函数返回值为false时就说明用户为输入有效的命令,那么此时就不再执行之后的代码  continue;    }return 0;
}

那么对应以上实现的命令行解析是否正确呢?要验证那么这时就需要创建一个Print的函数来打印g_argv获取的。

void Print()    
{    for(int i=0;g_argv[i];i++)    {    printf("argv[%d]->%s\n",i,g_argv[i]);    }    printf("argv:%d\n",g_argc);    }    

实现了Print函数之后接下来就在每次完成命令行就将命令行参数表输出进行验证实现的命令行解析工作是否正确。

运行myshell,接下来输入几个指令。

通过以上输出的结果可以看出实现的命令行的解析是符合我们的要求的。

2.4 检查并处理内建命令

以上已经实现了命令行解析的功能,在此之后在命令行参数表当中已经存储了用户输入的命令。但是接下来还是不能执行对应的指令,而是需要处理cd、echo等内建命令

内建命令就是指当前需要执行的指令不是由父进程创建子进程之后由子进程来执行的,而是直接由父进程执行。

 以下就来实现处理内建命令的函数CheckAndExecBuiltin,在该函数当中会处理echo、cd、export三个内建命令,其他的内建命令可自主实现。

//检查用户输入的指令是否为内建命令bool  CheckAndExecBuiltin()
{    std::string cmd=g_argv[0];if(cmd=="cd"){cd();            return true;}else if(cmd=="echo"){                     Echo();return true;          }         else if(cmd=="export"){Export();return true;}         else{                                                                                                                                            //……}
return false;
}

注:在以上使用到string,在此就要在myshell.cc的头文件当中添加string 

main函数内实现

int main()
{//从父进程当中获取环境变量表InitEnv();while(true){//1.输出命令行提示符    PrintCommandPrompt();//2.获取用户输入的命令                                                                                                                                                               //创建数组存储用户输出的数据    char commandline[COMMAND_SIZE];    if(!GetCommandLine(commandline,sizeof(commandline)))    continue;    //3.命令行分析    if(!CommandParse(commandline))    continue;    //4.检测并处理内建命令    if(CheckAndExecBuiltin())    continue;    }    return 0;
}

cd指令

通过之前的学习我们知道cd实现的是路径的切换,那么要在myshell当中实现该功能先在myshell当中构建对应的环境变量表。Linux中Shell外壳一开始生成环境变量表的数据其实是从配置文件当中得到的,我们实现的myshell也是可以从配置文件当中获取但是这样的操作很繁琐因此在接下来当中的myshell的环境变量表是直接从父进程当中获取

首先来创建对应的环境变量表

//2.环境变量表    
#define MAX_ENVS 200    
char* g_env[MAX_ENVS];            
int g_envs=0;       

接下来在myshell程序执行的开始就从父进程当中将对应的环境变量表导入当前进程当中。

//导入父进程的环境变量表至当前进程当中
void  InitEnv()
{extern char** environ;memset(g_env,0,sizeof(g_env));g_envs=0;//获取环境变量for(int i=0;environ[i];i++){g_env[i]=(char*)malloc(strlen(environ[i])+1);strcpy(g_env[i],environ[i]);g_envs++;}g_env[g_envs]=NULL;//导入环境变量for(int i=0;g_env[i];i++){putenv(g_env[i]);}//修改全局指针envrionenviron=g_env;}

 

int main()
{//从父进程当中获取环境变量表InitEnv();while(true){//1.输出命令行提示符PrintCommandPrompt();//2.获取用户输入的命令//创建数组存储用户输出的数据char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline,sizeof(commandline)))continue;//3.命令行分析                                                                                                                                                                       if(!CommandParse(commandline))continue;Print();//4.检测并处理内建命令if(CheckAndExecBuiltin())continue;}return 0;
}

以上就实现了从程序刚开始执行的时候就从父进程当中获取环境变量表,那么接下来我们就可以来实现cd指令了

在cd指令当中有一个选项是很特殊的,那就 - ,当用户使用cd - 的时候shell需要切换到最近的一次路径,那么要实现该功能就需要在使用一部分的空间来存储上一次的路径

//使用prev_pwd存储最近一次的路径                                                                                                                                                             
std::string prev_pwd;   

除此之外当我们身处的路径改变时,环境变量表当中的PWD也是需要改变的,那么此时就在运行完对应的cd指令之后修改环境变量表当中的PWD

以下就是按照以上要求实现的Cd函数

//执行cd指令
bool cd()
{//判断执行的是否是cd -,是的话就获取当前的当前的路径if(!(g_argc==2 && (strcmp(g_argv[1],"-")==0))){//获取当前路径prev_pwd=GetPwd();}//判断命令行参数的个数if(g_argc==1){//命令行参数的个数为1就说明用户输出的命令是cd,此时就直接返回当前用户的家目录即可std::string home=GetHome();if(home.empty())return true;chdir(home.c_str());}else{//使用变量where得到第二个参数                                                                                                                                                        std::string where=g_argv[1];//当第二个参数为-时执行的命令是返回最近一次的路径if(where=="-"){std::string tmp=GetPwd();std::cout<<prev_pwd<<std::endl;chdir(prev_pwd.c_str());prev_pwd=tmp;}//当第二个参数是~时执行的命令是返回当前用户的家目录else if(where=="~"){std::string home=GetHome();//std::string homestr=home.substr(0);// std::cout<<home<<std::endl;chdir(home.c_str());}                                                                                                                                                                                    //不是以上的情况就将路径切换为用户指定的路径else{chdir(where.c_str());}}//更新环境变量表int pwd_idx = -1;for(int i = 0; g_env[i] != NULL; i++){if (strncmp(g_env[i], "PWD=", 4) == 0){pwd_idx = i;break;}}// 获取当前工作目录char* cwd = getcwd(NULL, 0);if (cwd == NULL){return "None"; // 错误处理}// 构建新的 PWD 环境变量字符串size_t len = strlen(cwd) + 5; // "PWD=" + 字符串长度 + 1char* pwd_str =(char*) malloc(len);                                                                                                                                                      if (pwd_str == NULL){free(cwd);return "None"; // 内存分配失败}snprintf(pwd_str, len, "PWD=%s", cwd);free(cwd);// 更新环境变量表if (pwd_idx != -1){free(g_env[pwd_idx]); // 释放旧的 PWD 条目//printf("%s\n",prev_pwd);g_env[pwd_idx] = pwd_str; // 设置新的 PWD 条目} else{// 确保有空间添加新环境变量if (g_envs < MAX_ENVS - 1){g_env[g_envs++] = pwd_str;g_env[g_envs] = NULL; // 确保列表以 NULL 结尾} }return true;
}

以上就使用到chdir,chdir系统调用的作用就是将当前的路径切换为指定的路径

注:在使用chdir时需要引用头文件#include<unostdd.h>,因此要在myshell当中添加上。 

以上我们就编写了内建命令当中cd的部分,接下来就编译myshell.cc文件运行myshell看看是否符合我们的要求。

echo指令

要实现内建命令echo就需要处理echo $? 和 echo $环境变量名 若不是这两种情况就只需要将用户输入的第二个参数再输出到命令行上。

要实现echo $? 就需要在myshell当中创建一个全局的变量来存储上一次执行程序的退出码

//上一次执行的程序的退出码                                                                                                                                                                   
int lastcode=0;      

 

Echo函数实现的代码如下所示:

//执行echo指令
void Echo()
{//判断用户输入的参数个数是否为2if(g_argc==2){//将用户输入的第二个参数存储到opt当中std::string opt=g_argv[1];if(opt=="$?"){//输出错误码lastcode的值std::cout<<lastcode<<std::endl;//重新将lastcode设置为0lastcode=0;}else if(opt[0]=='$'){//输出对应环境变量的数据std::string env_name=opt.substr(1);const char*env_vlue=getenv(env_name.c_str());if(env_vlue)std::cout<<env_vlue<<std::endl;}else{//不为以上的情况就直接将用户输入的第二个参数输出std::cout<<opt<<std::endl;}                                                                                                                                                                                    }
}

 

export指令

export的作用就是将用户新创建的环境变量导入到环境变量表当中,实现的代码如下所示

//执行export指令    
bool Export()    
{    char* newenv =(char*)malloc(strlen(g_argv[1])+1);    strcpy(newenv,g_argv[1]);    //std:: cout<<g_argv[1]<<std::endl;    // std:: cout<<newenv<<std::endl;    g_env[g_envs++]=newenv;    g_env[g_envs]=NULL;    return true;    }    

注:在以上的export当中没有考虑要添加的环境变量之前已经在环境变量表当中存在需要将原来的环境变量覆盖,在此若要实现可自主实现看看。

以上我们就编写了内建命令当中export的部分,接下来就编译myshell.cc文件运行myshell看看是否符合我们的要求。

2.5 执行命令

当输入的命令不是内建命令之后就会执行到最后的步骤5,此时就需要通过创建子进程的方式再结合进程替换来执行普通的命令

在此在Execute内实现普通的命令执行,实现的代码如下所示

//创建子进程执行命令  
int  Execute()                                                                                                                                                                               
{                               //创建子进程                pid_t pid=fork();           if(pid==0)                  {                           //在子进程当中进行进程替换  execvp(g_argv[0],g_argv);  exit(1);  }  //进行进程等待  int status=0;  pid_t rid=waitpid(pid,&status,0);  if(rid>0)  {  //设置进程退出码lastcode值  lastcode=WEXITSTATUS(status);  }  return 0;  }  

 注:在使用fork和wait因此要在myshell当中添加上头文件#include<sys/types.h>和         #include<sys/wait.h>。 

 

 在main函数当中调用该函数

int main()
{//从父进程当中获取环境变量表InitEnv();while(true){//1.输出命令行提示符    PrintCommandPrompt();//2.获取用户输入的命令                                                                                                                                                               //创建数组存储用户输出的数据    char commandline[COMMAND_SIZE];    if(!GetCommandLine(commandline,sizeof(commandline)))    continue;    //3.命令行分析    if(!CommandParse(commandline))    continue;    //4.检测并处理内建命令    if(CheckAndExecBuiltin())    continue;   //5.执行命令Execute();}    return 0;
}

 

以上我们就编写了执行命令部分,接下来就编译myshell.cc文件运行myshell看看是否符合我们的要求。

 

3. myshell完整代码

#include<iostream>
#include<cstdio>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<cstring>
#include<sys/wait.h>//上一次执行的程序的退出码
int lastcode=0;#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# " //1.命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc=0;//2.环境变量表
#define MAX_ENVS 200
char* g_env[MAX_ENVS];
int g_envs=0;//使用prev_pwd存储最近一次的路径
std::string prev_pwd;//获取当前登录的用户名
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()
{static char* cur_pwd=nullptr;if(cur_pwd!=NULL){free(cur_pwd);}cur_pwd=getcwd(NULL,0);return cur_pwd==NULL? "None":cur_pwd;
}//得到当前用户的家目录
const char* GetHome()
{const char* home=getenv("HOME");return home==NULL?"":home;
}//导入父进程的环境变量表至当前进程当中
void  InitEnv()
{extern char** environ;memset(g_env,0,sizeof(g_env));g_envs=0;//获取环境变量for(int i=0;environ[i];i++){g_env[i]=(char*)malloc(strlen(environ[i])+1);strcpy(g_env[i],environ[i]);g_envs++;}g_env[g_envs]=NULL;//导入环境变量for(int i=0;g_env[i];i++){putenv(g_env[i]);}environ=g_env;}//执行cd指令
bool cd()
{//判断执行的是否是cd -,是的话就获取当前的当前的路径if(!(g_argc==2 && (strcmp(g_argv[1],"-")==0))){//获取当前路径prev_pwd=GetPwd();}//判断命令行参数的个数if(g_argc==1){//命令行参数的个数为1就说明用户输出的命令是cd,此时就直接返回当前用户的家目录即可std::string home=GetHome();if(home.empty())return true;chdir(home.c_str());}else{//使用变量where得到第二个参数std::string where=g_argv[1];//当第二个参数为-时执行的命令是返回最近一次的路径if(where=="-"){std::string tmp=GetPwd();std::cout<<prev_pwd<<std::endl;chdir(prev_pwd.c_str());prev_pwd=tmp;}//当第二个参数是~时执行的命令是返回当前用户的家目录else if(where=="~"){std::string home=GetHome();//std::string homestr=home.substr(0);// std::cout<<home<<std::endl;chdir(home.c_str());}//不是以上的情况就将路径切换为用户指定的路径else{chdir(where.c_str());}}//更新环境变量表int pwd_idx = -1;for(int i = 0; g_env[i] != NULL; i++){if (strncmp(g_env[i], "PWD=", 4) == 0){pwd_idx = i;break;}}// 获取当前工作目录char* cwd = getcwd(NULL, 0);if (cwd == NULL){return "None"; // 错误处理}// 构建新的 PWD 环境变量字符串size_t len = strlen(cwd) + 5; // "PWD=" + 字符串长度 + 1char* pwd_str =(char*) malloc(len);if (pwd_str == NULL){free(cwd);return "None"; // 内存分配失败}snprintf(pwd_str, len, "PWD=%s", cwd);free(cwd);// 更新环境变量表if (pwd_idx != -1){free(g_env[pwd_idx]); // 释放旧的 PWD 条目//printf("%s\n",prev_pwd);g_env[pwd_idx] = pwd_str; // 设置新的 PWD 条目} else{// 确保有空间添加新环境变量if (g_envs < MAX_ENVS - 1){g_env[g_envs++] = pwd_str;g_env[g_envs] = NULL; // 确保列表以 NULL 结尾} }return true;
}//执行echo指令
void Echo()
{//判断用户输入的参数个数是否为2if(g_argc==2){//将用户输入的第二个参数存储到opt当中std::string opt=g_argv[1];if(opt=="$?"){//输出错误码lastcode的值std::cout<<lastcode<<std::endl;lastcode=0;}else if(opt[0]=='$'){//输出对应环境变量的数据std::string env_name=opt.substr(1);const char*env_vlue=getenv(env_name.c_str());if(env_vlue)std::cout<<env_vlue<<std::endl;}else{//不为以上的情况就直接将用户输入的第二个参数输出std::cout<<opt<<std::endl;}}
}//执行export指令
bool Export()
{char* newenv =(char*)malloc(strlen(g_argv[1])+1);strcpy(newenv,g_argv[1]);//std:: cout<<g_argv[1]<<std::endl;// std:: cout<<newenv<<std::endl;g_env[g_envs++]=newenv;g_env[g_envs]=NULL;return true;}//将得到的当前路径当中得到最后的文件名
std::string DirName(const char* pwd)
{//得到当前路径以/之后的文件名
#define SLASH "/"std::string dir=pwd;if(dir==SLASH)return SLASH;auto pos=dir.rfind(SLASH);//当pos的返回值为npos时此时出现bugif(pos==std::string::npos)return "BUG";return dir.substr(pos+1);
}//构建命令行提示符的输出格式
void MakeCommandLine( char cmd_prompt[],int size )
{snprintf(cmd_prompt,size,FORMAT,GetUserName(),GetHostName(),DirName(GetPwd()).c_str());
}//输出命令行提示符
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[strlen(out)-1]=0;if(strlen(out)==0)return false;return true;}//进行对用户输入的命令进行解析
bool CommandParse(char *commandline)
{
#define SEP " "g_argc=0;//使用strtok进行参数的分割,之后再将参数存储至命令行参数表当中g_argv[g_argc++]=strtok(commandline,SEP);while((bool)(g_argv[g_argc++]=strtok(nullptr,SEP)));//最终统计的参数个数会比实际的多一个,此时需要将g_argc减一g_argc--;//最后通过判断g_argc的个数来判断用户是否输入有效的命令return g_argc>0? true:false;
}void Print()
{for(int i=0;g_argv[i];i++){printf("argv[%d]->%s\n",i,g_argv[i]);}printf("argv:%d\n",g_argc);for(int i=0;g_env[i];i++){printf("argv[%d]->%s\n",i,g_env[i]);}
}//检查用户输入的指令是否为内建命令bool  CheckAndExecBuiltin()
{std::string cmd=g_argv[0];if(cmd=="cd"){cd(); return true;}else if(cmd=="echo"){Echo();return true;}else if(cmd=="export"){Export();return true;}         else{//……}
return false;
}//创建子进程执行命令
int  Execute()
{//创建子进程pid_t pid=fork();if(pid==0){//在子进程当中进行进程替换execvp(g_argv[0],g_argv);exit(1);}//进行进程等待int status=0;pid_t rid=waitpid(pid,&status,0);if(rid>0){//设置进程退出码lastcode值lastcode=WEXITSTATUS(status);}return 0;}int main()
{//从父进程当中获取环境变量表InitEnv();while(true){//1.输出命令行提示符PrintCommandPrompt();//2.获取用户输入的命令//创建数组存储用户输出的数据char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline,sizeof(commandline)))continue;//3.命令行分析if(!CommandParse(commandline))continue;// Print();//4.检测并处理内建命令if(CheckAndExecBuiltin())continue;//5.执行命令Execute();// Print();//char arr[1024];//scanf("%s",arr);}return 0;
}

以上就是本篇的全部内容了,接下来我们将开始基础IO的学习,未完待续……

相关文章:

Linux《自主Shell命令行解释器》

在上一篇的进程控制当中我们已经了解了进程退出、进程等待以及进程替换的相关概念&#xff0c;那么在了解了这些的概念之后接下来在本篇当中我们就可以结合之前我们学习的知识来实现一个自主的Shell命令行解释器&#xff0c;通过Shell的实现能让我们进一步的理解操作系统当中的…...

设置IDEA打开新项目使用JDK17

由于最近在学习Spring-AI&#xff0c;所以JDK8已经不适用了&#xff0c;但是每次创建新项目都还是JDK8&#xff0c;每次调来调去很麻烦 把Projects和SDKs都调整为JDK17即可 同时&#xff0c;Maven也要做些更改&#xff0c;主要是添加build标签 <build><plugins>&…...

Vue百日学习计划Day36-42天详细计划-Gemini版

总目标: 在 Day 36-42 理解组件化开发的思想&#xff0c;熟练掌握 Vue 组件的注册、Props、Events、v-model、Slots、Provide/Inject 等核心概念和实践&#xff0c;能够构建可复用和易于维护的组件结构。 所需资源: Vue 3 官方文档 (组件基础): https://cn.vuejs.org/guide/es…...

Python对JSON数据操作

在Python中&#xff0c;对JSON数据进行增删改查及加载保存操作&#xff0c;主要通过内置的json模块实现。 一、基础操作 1. 加载JSON数据 • 从文件加载 使用json.load()读取JSON文件并转换为Python对象&#xff08;字典/列表&#xff09;&#xff1a; import json with open…...

upload靶场1-5关

网上的解析有一些题目对应不上&#xff0c;比如第五关说是 空格 点 空格绕过 &#xff0c;我这里就无法成功解析&#xff0c;但大小写绕过就成功了&#xff0c;慢慢会把后面的关卡也写出来 这里建议开一台win7的虚拟机&#xff0c;在上面搭建靶场&#xff0c;可以省很多麻烦 …...

网络传输(ping命令,wget命令,curl命令),端口

网络传输&#xff1a; ping命令&#xff1a;检查指定的网络服务器是否是可联通状态 语法&#xff1a;ping 【-c num】IP或主机名 -c&#xff1a;是检查的次数&#xff0c;不使用-c&#xff0c;将无限次持续检查 wget命令&#xff1a;wget是非交互式的文件下载器&#xff0…...

upload-labs靶场通关详解:第10关

一、分析源代码 $is_upload false; $msg null; if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array(".php",".php5",".php4",".php3",".php2",".html",".htm",".ph…...

深入解析`lsof`命令:查看系统中打开文件与进程信息

1、lsof的基本概念 lsof &#xff08;List Open Files&#xff09; 提供了一种方式来查看系统上哪些进程正在访问哪些文件&#xff0c;能够显示文件类型、文件名、文件描述符、所属进程等详细信息。 在类Unix系统中&#xff0c;几乎所有的操作都与文件相关联&#xff0c;文件不…...

C++ 与 Python 内存分配策略对比

内存管理是编程中的一个核心概念&#xff0c;它直接影响程序的性能、稳定性和资源利用率。C 和 Python 作为两种广泛使用的编程语言&#xff0c;在内存分配和管理方面采用了截然不同的策略。 C 内存分配策略 C 赋予程序员对内存的精细控制能力&#xff0c;同时也带来了更大的…...

TB开拓者策略交易信号闪烁根因及解决方法

TB开拓者策略信号闪烁分析 TB开拓者策略交易信号闪烁根因 TB开拓者策略交易信号闪烁根因分析 信号闪烁是交易策略开发中常见的问题&#xff0c;特别是在TB(TradeBlazer)开拓者等平台上。以下是信号闪烁的主要根因分析&#xff1a; 主要根因 未来函数问题 使用了包含未来信息…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(24):受身形

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(24):受身形 1、前言(1)情况说明(2)工程师的信仰2、知识点(1)うけみけい 受身形(2)復習(ふくしゅう):3、单词(1)日语(2)日语片假名单词4、相近词练习5、单词辨析记录6、总结1、前言 (1)情况说明 自己在今…...

牛客网NC209794:使徒袭来

牛客网NC209794:使徒袭来 题目背景 问题分析 数学建模 设三位驾驶员的战斗力分别为 a, b, c已知条件&#xff1a;a b c n (n为输入的正整数)目标&#xff1a;求 a b c 的最小值 解题思路 根据算术-几何平均值不等式(AM-GM不等式)&#xff0c;对于任意正实数a, b, c&a…...

命令行登录 MySQL 报 Segmentation fault 故障解决

问题描述&#xff1a;对 mysql8.0.35 源码进行 make&#xff0c;由于一开始因为yum源问题少安装依赖库 库&#xff0c;在链接时遇到错误 undefined reference to&#xff0c;后来安装了相关依赖库&#xff0c;再次 make 成功。于是将 mysqld 启动&#xff0c;再用 mysql -u roo…...

2025ICPC邀请赛南昌游记

滚榜时候队伍照片放的人家的闹麻了&#xff0c;手机举了半天 。 最后银牌700小几十罚时&#xff0c;rank60多点。 参赛体验还行&#xff0c;队长是福建人&#xff0c;说感觉这个热度是主场作战哈哈哈哈。空调制冷确实不太行吧。 9s过A是啥&#xff0c;没见过&#xff0c;虽然…...

kotlin flow的写法

以下是 Android 开发中 Kotlin Flow 的常见使用模式和操作符的完整中文总结&#xff1a; 1. 基本 Flow 创建方式 // 从多个值创建 val flow1 flowOf(1, 2, 3)// 使用 flow 构建器 val flow2 flow {emit(1)delay(100)emit(2) }// 从集合创建 val flow3 listOf(1, 2, 3).asFl…...

springboot+mybatis或mybatisplus在进行%name%的前后模糊查询时如何放防止sql注入

在使用 Spring Boot 配合 MyBatis 或 MyBatis-Plus 进行数据库操作时&#xff0c;确保防止 SQL 注入是非常重要的。对于 %name% 样式的前后模糊查询&#xff0c;以下是几种有效的方法来防止 SQL 注入&#xff1a; 1. 使用 MyBatis 的 <if> 标签和 #{} 占位符 MyBatis 默…...

基于51单片机教室红外计数灯光控制—可蓝牙控制

基于51单片机智能教室灯光 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 本系统由STC89C52单片机时钟芯片DS1302液晶屏LCD1602光敏电阻红外对管LED灯模块按键模块蓝牙模块构成 具体…...

HTTPS、SSL证书是啥?网站“安全小锁”的入门科普

你有没有发现&#xff0c;浏览网页时&#xff0c;有些网站地址栏前面会出现一个小锁的图标&#x1f512;&#xff0c;而有些网站却没有&#xff1f;这个小锁其实代表着网站用了“HTTPS”&#xff0c;是比普通“HTTP”更安全的协议。今天&#xff0c;我们就来聊聊HTTPS、SSL证书…...

大模型备案中的安全考量:筑牢数字时代的安全防线

在数字化浪潮席卷全球的当下&#xff0c;大模型技术凭借强大的数据分析、模式识别与语言理解生成能力&#xff0c;成为推动产业变革、提升社会运转效率的关键力量。从智能客服降本增效&#xff0c;到医疗影像精准诊断&#xff0c;再到金融风险智能预测&#xff0c;大模型正重塑…...

Linux句柄数过多问题排查

以下是Linux句柄数过多问题的排查与解决方法整理&#xff1a; 一、检测句柄使用情况 1‌.查看系统限制‌ 单个进程限制&#xff1a;ulimit -n 系统级总限制&#xff1a;cat /proc/sys/fs/file-max 2‌.统计进程占用量‌ 查看指定进程&#xff1a;lsof -p <PID> | wc -…...

Python训练第三十天

DAY 30 模块和库的导入 知识点回顾&#xff1a; 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑&#xff1a;找到根目录&#xff08;python解释器的目录和终端的目录不一致&#xff09; 总结&#xff1a;导入包的核心就是找到目录&#xff0c;只有理解了py…...

Java资源管理与防止泄漏:从SeaTunnel源码看资源释放

资源管理是 Java 开发中常被忽视却至关重要的一环。本文从 SeaTunnel 案例出发&#xff0c;探讨 Java 中如何正确管理资源&#xff0c;防止资源泄漏。 SeaTunnel 中的一次修复 Apache SeaTunnel 项目中的 HiveSink 组件曾存在一个典型的资源泄漏隐患。修复前后的代码对比如下…...

Notepad++ 学习(三)使用python插件编写脚本:实现跳转指定标签页(自主研发)

目录 一、先看成果二、安装Python Script插件三、配置Python脚本四、使用脚本跳转标签页方法一&#xff1a;通过菜单运行方法二&#xff1a;设置快捷键&#xff08;推荐&#xff09; 五、注意事项六、进阶使用 官网地址&#xff1a; https://notepad-plus-plus.org/Python Scri…...

PYTHON训练营DAY30

库的导入 一、导入整个 import 库 二、从库中导入特征项 from 库 import XXX 三、非标准导入&#xff1a;导入整个库 from 库 import * 四、导入自定义的库 &#xff08;一&#xff09;项目 创建一个打招呼的库 # greet.py def say_hello(name):return f"你好&a…...

Linux 文件(2)

文章目录 1. 文件描述符1.1 文件描述符是什么1.2 文件描述符如何分配 2 重定向2.1 输出重定向2.2 输入重定向2.3 使用dup2进行重定向 3. 文件、父子进程和进程替换 1. 文件描述符 1.1 文件描述符是什么 什么是文件描述符呢&#xff1f; 我们先来看之前所介绍的系统级别的文件…...

netcore项目使用winforms与blazor结合来开发如何按F12,可以调出chrome devtool工具辅助开发

就是像在开发网页那样&#xff0c;可以使用devtool工具辅助开发。可查看页面css&#xff0c;js等。我在网上看解决办法。没一个有用的。自己找了一个。不需要单独在页面写多余的代码 我的program.cs中有服务注册代码增加 3行代码。 #if DEBUGservices.AddBlazorWebViewDevelo…...

CSS attr() 函数详解

attr() 是 CSS 中的一个函数&#xff0c;用于获取 HTML 元素的属性值并在样式中使用。虽然功能强大&#xff0c;但它的应用有一些限制和注意事项。 基本语法 element::pseudo-element {property: attr(attribute-name); } 可用场景 1. 在伪元素的 content 属性中使用&#…...

人生如戏、戏如人生

今早&#xff0c;6&#xff1a;30起床给一家人弄早餐&#xff0c;然后听到了老公的一声大喊&#xff1a;”半小时了&#xff0c;你干什么了“&#xff0c;原来孩子说她在理书包&#xff0c;被老公骂了。 最近几天&#xff0c;老公脾气变得很差&#xff0c;孩子每天都会被老公骂…...

Java迭代器知识点详解

在 Java 编程中&#xff0c;迭代器&#xff08;Iterator&#xff09;是一种用于遍历集合&#xff08;如 List、Set、Map 等&#xff09;元素的接口。它提供了一种统一的方式来访问集合中的元素&#xff0c;而无需暴露集合的内部结构。以下是关于 Java 迭代器的详细知识点&#…...

免费开放试乘体验!苏州金龙自动驾驶巴士即将上线阳澄数谷

近日&#xff0c;苏州自动驾驶巴士线路——阳澄数谷示范线正式上线&#xff0c;即日起向全民免费开放试乘体验&#xff01; 在苏州工业园区地铁3号线倪浜•阳澄数谷站外&#xff0c;一辆辆黑、白配色的小巴正在道路上有条不紊地行驶。与普通公交不同的是&#xff0c;小巴造型奇…...

Kotlin 协程

第一个协程程序 协程是可暂停计算的一个实例。它在概念上类似于线程&#xff0c;因为它需要运行一个代码块&#xff0c;该代码块与其他代码并发运行。然而&#xff0c;协程并不绑定到任何特定的线程。它可以在一个线程中暂停执行&#xff0c;并在另一个线程中恢复执行。 协程…...

MySQL函数触发:函数处理与触发器自动化应用

引言 各位数据库爱好者们好&#xff01;今天我们要探索MySQL中两个强大的自动化工具——函数和触发器 &#x1f680;。函数就像数据库中的"瑞士军刀"&#xff0c;能帮你高效处理各种数据&#xff1b;而触发器则是数据库的"自动感应器"&#xff0c;能在数据…...

数据可视化热图工具:Python实现CSV/XLS导入与EXE打包

在数据分析工作中,热图(Heatmap)是一种非常直观的可视化工具,能够清晰展示数据矩阵中的数值分布和相关性。本文将介绍如何使用Python构建一个支持CSV/XLS文件导入、热图生成并可打包为EXE的桌面应用程序。 核心功能设计 我们的热图工具将包含以下核心功能: 支持CSV和Excel…...

CUDA Stream的进阶用法:流水线并行的资源竞争解决方案

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生专属优惠。 引言&#xff1a;多任务推理场景的挑战 GPU在AI推理服务器的典型负载特征&#xff08;并行模…...

Eclipse Java 开发调优:如何让 Eclipse 运行更快?

Eclipse Java 开发调优&#xff1a;如何让 Eclipse 运行更快&#xff1f; 在 Java 开发领域&#xff0c;Eclipse 是一款被广泛使用的集成开发环境&#xff08;IDE&#xff09;。然而&#xff0c;随着项目的日益庞大和复杂&#xff0c;Eclipse 的运行速度可能会逐渐变慢&#x…...

大模型(1)——基本概念

文章目录 一、大模型的定义与概念二、大模型的原理与技术核心三、大模型的应用领域四、市面上常用的大模型1. 生成类模型&#xff08;文本/代码/图像&#xff09;2. 理解类模型&#xff08;文本/语义&#xff09;3. 多模态模型4. 国产大模型 五、总结与趋势 一、大模型的定义与…...

MYSQL故障排查和环境优化

一、MySQL故障排查 1. 单实例常见故障 &#xff08;1&#xff09;连接失败类问题 ERROR 2002 (HY000): Cant connect to MySQL server 原因&#xff1a;MySQL未启动或端口被防火墙拦截。 解决&#xff1a;启动MySQL服务&#xff08;systemctl start mysqld&#xff09;或开放…...

sts下载安装

windows下STS&#xff08;Spring Tools Suite&#xff0c;自带spring插件的eclipse&#xff09;的下载与安装_sts下载-CSDN博客Spring Boot安装与配置教程_spring boot安装配置-CSDN博客...

Baklib知识中台架构设计与智能服务实践

知识中台架构四库体系解析 现代企业知识管理系统的核心在于构建结构化知识资产池&#xff0c;Baklib通过独创的四库体系——知识库、流程库、案例库、模型库——实现知识资源的全生命周期管理。其中&#xff0c;知识库作为基础层&#xff0c;聚合文档、图谱等显性知识&#xf…...

TinyEngine 2.5版本正式发布:多选交互优化升级,页面预览支持热更新,性能持续跃升!

前言 TinyEngine低代码引擎使开发者能够定制低代码平台。它是低代码平台的底座&#xff0c;提供可视化搭建页面等基础能力&#xff0c;既可以通过线上搭配组合&#xff0c;也可以通过cli创建个人工程进行二次开发&#xff0c;实时定制出自己的低代码平台。适用于多场景的低代码…...

嵌入式系统:技术演进、应用领域发展趋势全面解析

嵌入式系统&#xff1a;技术演进、应用领域及发展趋势全面解析 一、引言 在当今数字化时代&#xff0c;嵌入式系统已悄然融入到我们生活的方方面面&#xff0c;从日常使用的智能手机、智能家电&#xff0c;到汽车中的各种控制系统&#xff0c;乃至工业生产中的自动化设备、航…...

软件工程-项目管理

进度管理 又叫时间管理&#xff0c;确定进度目标&#xff0c;在与质量、成本目标协调的基础上&#xff0c;实现工期目标 具体包括以下过程&#xff1a; 活动定义&#xff1a;确定完成各项交付成果需要展开的具体活动活动排序&#xff1a;活动之间的先后关系和逻辑关系活动资…...

计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 13.几何着色器(二)爆炸效果修改图元类型

1. 爆炸效果 这是几何着色器中的一个常见应用&#xff0c;我们使用几何着色器来模拟爆炸效果。 我们希望将每个三角形沿其表面法向量向外移动&#xff0c;则实际上是将组成环面的三角形向外“爆炸”。 我们只需取三个顶点的法向量&#xff0c;然后将其相加并求平均&#xff…...

需求频繁变更?AI 驱动的自动化解决方案实践

在软件开发的实际场景中&#xff0c;需求频繁变更已成为困扰开发团队的常见难题。每一次需求变动&#xff0c;都意味着代码的重新编写、调试与测试&#xff0c;不仅消耗大量时间和人力成本&#xff0c;还可能引发一系列连锁反应&#xff0c;影响项目进度与质量。而飞算 JavaAI …...

ffmpeg转码后的视频有横条纹和彩虹横条等乱彩问题

原因&#xff1a;ffmpeg安装的时&#xff0c;./configure 添加了–disable-asm参数&#xff0c;导致色彩异常。 **解决方法&#xff1a;**移除该参数–disable-asm。 注意&#xff1a;不加该参数可能提示no yasm或nasm等问题&#xff0c;解决该问题即可。大多是环境变量没有配置…...

QT6 源(111):阅读与注释菜单栏 QMenuBar,进行属性与成员函数测试,信号与槽函数测试,并给出源码

&#xff08;1&#xff09; &#xff08;2&#xff09; &#xff08;3&#xff09; &#xff08;4&#xff09; &#xff08;5&#xff09; &#xff08;6&#xff09; &#xff08;7&#xff09;以下源代码来自于头文件 qmenubar . h &#xff1a; #ifndef QMENUBAR_H #defi…...

达梦数据库对json字段进行操作

在达梦数据库中&#xff0c;字段类型不能显式定义为JSON&#xff0c;通常以VARCHAR或TEXT类型存储JSON字符串&#xff0c;可以通过内置的JSON函数对JSON格式数据进行操作和条件过滤。 1. 创建表并插入JSON数据 假设字段类型为VARCHAR&#xff0c;存储JSON字符串&#xff1a; -…...

基于vue框架的东莞市二手相机交易管理系统5yz0u(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,相机分类,二手相机 开题报告内容 基于FlaskVue框架的东莞市二手相机交易管理系统开题报告 一、研究背景与意义 1.1 研究背景 随着数字技术的迅猛发展和摄影文化的广泛普及&#xff0c;相机已成为人们记录生活、表达创意的重要工…...

谷歌 NotebookLM 即将推出 Sparks 视频概览:Gemini 与 Deep Research 加持,可生成 1 - 3 分钟 AI 视频

近期&#xff0c;谷歌旗下的 NotebookLM 即将推出一项令人瞩目的新功能 ——Sparks 视频概览。这一功能借助 Gemini 与 Deep Research 的强大能力&#xff0c;能够生成 1 - 3 分钟的 AI 视频&#xff0c;为用户带来全新的内容创作与信息获取体验。 NotebookLM&#xff1a;AI 笔…...

MCP详解

前言:AI能力跃迁的“最后一公里” 当前,大语言模型(LLMs)已展现出文本生成、逻辑推理等通用能力,但其与真实世界的交互仍存在瓶颈。传统模式下,开发者需为每个外部工具(如数据库、API、文件系统)编写定制化接口,导致开发周期长、兼容性差、安全风险高。而​​模型上下…...