🔥个人主页:Quitecoder
🔥专栏:linux笔记仓
目录
- 01.输出命令行
- 02.获取用户命令字符串
- 03.命令行字符串分割
- 04.执行命令
- 05.细节修改
- 检查是否为内建命令
- 完整代码:
01.输出命令行
完成对一个shell 的编写,首先我们需要输出一个自己的命令行
我们可以通过getenv来获取环境变量
const char * GetSserName()
{const char *name = getenv("USER");if(name == NULL) return "None";return name;
}
用getnev来获取USER
拿到用户名后,第二个获取主机名
const char * GetHostName()
{const char *hostname = getenv("HOSTNAME");if(hostname==NULL) return "None";return hostname;
}
接着我们获取路路径,这里先写一个不规范的路径版本:
const char * GetCwd()
{const char *cwd = getenv("PWD");if(cwd == NULL ) return "None";return cwd;
}
后面我们再对路径进行截取
现在完成对命令行输出的编写,我们目标是将变量名放到一个输出型参数commandline中,这里需要一个函数snprintf:
void MakeCommandLine(char line[],size_t size)
{const char* username= GetUserName();const char* hostname= GetHostName();const char* cwd= GetCwd();snprintf(line,size,"[%s@%s %s]> ",username,hostname,cwd);printf("%s",line);fflush(stdout);
}
这就完成了命令行输出部分的函数,这里打印是向缓冲区打印,我们需要刷新缓冲区
02.获取用户命令字符串
用户输入的各种指令,本质就是一个字符串,我们要做的就是对字符串进行截取并且按照要求完成内容输出
我们这里不能直接用scanf来获取,因为这里scanf的分隔符为空格,我们这里想按照行来拿字符串,用fgets
我们这里将输入的回车\n
改为\0
;
我们向usercommand这个缓冲区输入来获取命令
03.命令行字符串分割
这里定义全局的存储各个命令的字符串数组,用strtok进行分割,注意!:这里strtok的第二个参数是const char *
类型的,一定是一个字符串
所以我们这里定义的分隔符必须是字符串
这里写成=,表示先赋值,再判断,分割之后,strtok会返回NULL,刚好让gArgv最后一个元素是NULL,并且while判断结束
检验结果:
04.执行命令
执行命令,我们创建子进程进行程序替换
我们将上面的代码放入一个新函数中,并让上面的过程持续进行:
05.细节修改
我们发现现在,执行cd命令是没有反应的
自定义 shell 无法运行 cd
指令的原因主要是因为 cd
是一个 内建命令,它不会创建新进程,而是直接改变当前进程的工作目录。因此,简单地使用 fork()
和 execvp()
来执行 cd
是不行的,因为 cd
会在子进程内生效,但子进程在执行完命令后会终止,所以父进程的工作目录不会改变。
在当前的代码中,所有的命令都会通过 fork()
创建子进程,并在子进程内使用 execvp()
执行。这种方法适用于外部命令,但对 cd
这样的内建命令并不适用
要让 cd
命令能够正确工作,需要在父进程中执行 cd
操作,而不是在子进程中。可以通过检查用户输入的命令是否为 cd
,如果是 cd
,则在父进程中直接使用 chdir()
系统调用来改变当前工作目录。
检查是否为内建命令
const char * GetHome()
{const char *home=getenv("HOME");if(home== NULL) return "/root";return home;
}void Cd()
{const char *path=gArgv[1];if(path==NULL)path =GetHome();chdir(path);}
int CheckBuildin()
{int yes=0;const char * enter_cmd= gArgv[0];if(strcmp("cd",enter_cmd)==0){yes=1;Cd();}return yes;
}
int main()
{int quit=0;while(!quit){//1.输出命令行MakeCommandLine();//2.获取输入命令char usercommand[SIZE];int n= GetUserCommand(usercommand,sizeof(usercommand));if(n<=0) return 1;//3.命令行字符串分割SplitCommand(usercommand,sizeof(usercommand)); //4.检查是否为内建命令;n=CheckBuildin();if(n)continue;//执行命令ExecuteCommand();}return 0;}
现在cd命令可以使用,但是环境变量还是有问题,还需要修改
char cwd[SIZE*2];void Cd()
{const char *path=gArgv[1];if(path==NULL)path =GetHome();chdir(path);snprintf(cwd,sizeof(cwd),"PWD=%s",path);putenv(cwd);
}
int CheckBuildin()
{int yes=0;const char * enter_cmd= gArgv[0];if(strcmp("cd",enter_cmd)==0){yes=1;Cd();}return yes;
}
void Cd()
{const char *path=gArgv[1];if(path==NULL)path =GetHome();chdir(path);//刷新环境变量char temp[SIZE*2];getcwd(temp,sizeof(temp));snprintf(cwd,sizeof(cwd),"PWD=%s",temp); putenv(cwd);
}
还需要更改的是,系统的shell只会显示当前路径,而我们自定义的shell会显示绝对路径
#define SkipPath(p) do{ p+= strlen(p)-1; while(*p!='/')p--;\
}while(0)void MakeCommandLine()
{char line[SIZE];const char* username= GetUserName();const char* hostname= GetHostName();const char* cwd= GetCwd();SkipPath(cwd);snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd)==1?"/":cwd+1);printf("%s",line);fflush(stdout);
}
最后加上退出码
int lastnode=0;
void ExecuteCommand()
{pid_t id=fork();if(id < 0)exit(1);else if(id == 0){execvp(gArgv[0],gArgv);exit(errno);}else {int status=0;pid_t rid = waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);if(lastcode!=0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);}}
}int CheckBuildin()
{ int yes=0; const char * enter_cmd= gArgv[0]; if(strcmp("cd",enter_cmd)==0) { yes=1; Cd(); } else if(strcmp(enter_cmd,"echo")==0&&strcmp(gArgv[1],"$?")==0) { printf("%d\n",lastcode); lastcode=0; } return yes;
}
完整代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{ p+= strlen(p)-1; while(*p!='/')p--;\
}while(0)char cwd[SIZE*2];
char *gArgv[NUM];int lastcode=0;const char * GetUserName()
{const char *name = getenv("USER");if(name == NULL) return "None";return name;
}
const char * GetHostName()
{const char *hostname = getenv("HOSTNAME");if(hostname==NULL) return "None";return hostname;
}
const char * GetCwd()
{const char *cwd = getenv("PWD");if(cwd == NULL ) return "None";return cwd;
}
const char * GetHome()
{const char *home=getenv("HOME");if(home== NULL) return "/root";return home;
}
void MakeCommandLine()
{char line[SIZE];const char* username= GetUserName();const char* hostname= GetHostName();const char* cwd= GetCwd();SkipPath(cwd);snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd)==1?"/":cwd+1);printf("%s",line);fflush(stdout);
}
int GetUserCommand(char command[],size_t n)
{char *s =fgets(command,n,stdin);if(s==NULL) return 1; command[strlen(command)-1]=ZERO;return strlen(command);
}void SplitCommand(char command[],size_t size)
{gArgv[0]=strtok(command,SEP);int index=1;while((gArgv[index++]=strtok(NULL,SEP)));(void)size;
}
void ExecuteCommand()
{pid_t id=fork();if(id < 0)exit(1);else if(id == 0){execvp(gArgv[0],gArgv);exit(errno);}else {int status=0;pid_t rid = waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);if(lastcode!=0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);}}
}
void Cd()
{const char *path=gArgv[1];if(path==NULL)path =GetHome();chdir(path);//刷新环境变量char temp[SIZE*2];getcwd(temp,sizeof(temp));snprintf(cwd,sizeof(cwd),"PWD=%s",temp);putenv(cwd);
}
int CheckBuildin()
{int yes=0;const char * enter_cmd= gArgv[0];if(strcmp("cd",enter_cmd)==0){yes=1;Cd();}else if(strcmp(enter_cmd,"echo")==0&&strcmp(gArgv[1],"$?")==0){yes=1;printf("%d\n",lastcode);lastcode=0;}return yes;
}
int main()
{int quit=0;while(!quit){//1.输出命令行MakeCommandLine();//2.获取输入命令char usercommand[SIZE];int n= GetUserCommand(usercommand,sizeof(usercommand));if(n<=0) return 1;//3.命令行字符串分割SplitCommand(usercommand,sizeof(usercommand)); //4.检查是否为内建命令;n=CheckBuildin();if(n)continue;//执行命令ExecuteCommand();}return 0;}