🔥个人主页: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;
}