Shell
本文介绍了如何完成一个简易的shell,结尾有完整代码。
命令行
shell其实就是命令行解释器,linux中的命令行解释器如下:
首先我们需要将这段字符串显示出来,那么我们首先将它进行分解。
这三个部分的内容其实都是去环境变量中得到的,在命令行中输入env可以查看所有的环境变量,我们可以去找到相对应的环境变量。
使用getenv系统调用去获得想要的环境变量的值,需要包含头文件stdlib.h
char *getenv(const char *name);
char* GetPWD()
{
char* pwd=getenv("PWD");//获取PWD的值
if(pwd)
{
return pwd;
}
else
{
return ("None");
}
}
char* GetLogname()
{
char* Logname=getenv("LOGNAME");//获取LOGNAME的值
if(Logname)
{
return Logname;
}
else
{
return ("None");
}
}
char* GetHostname()
{
char* hostname=getenv("HOSTNAME");//获取HOSTNAME的值
if(hostname)
{
return hostname;
}
else
{
return "None";
}
}
我们将得到的字符串组合打印出来看一下。
printf("[%s@%s %s]#",GetLogname(),GetHostname(),GetPWD());
因为PWD得到的是完整路径,与linux系统给我们提供的还有一定的区别,那么我们就需要使用strtok对这个字符串进行切割。
我们只需要更改GetPWD函数即可,这个时候得到的结果就是和linux提供的相同。
char* tmp[64];
char* GetPWD()
{
const char* pwd=getenv("PWD");
if(pwd)
{
char* t=strrchr(pwd,'/')+1;
return t;
}
else
{
return ("None");
}
}
命令输入
获取命令输入实际上就是获得字符串,且要保证将空格也读入,所以使用fgets(),fgets()仅以/n为结束符。
char *fgets(char *s, int size, FILE *stream)
命令切割
以ls命令为例,ls后面可以跟很多参数,以空格作为分隔符,例如ls -a -l,我们需要将命令以空格为分隔符,将他们分割开来放入数组,从而在后续执行进程替换的时候,能传入函数正确的参数。
#define SIZE 1024
#define sep " "
char* argv[SIZE];
void Split(char* cmd)
{
int i=0;
argv[i++]=strtok(cmd,sep);
while(argv[i++]=strtok(NULL,sep));
}
非内建命令执行
我们知道shell执行一个非内建命令其实是让子进程去执行,那么我们就也需要使用fork()产生一个子进程,再在子进程中使用execvp()进行进程替换即可执行。
int execvp(const char *file, char *const argv[]);
pid_t pid=fork();
if(pid==0)
{
execvp(argv[0],argv);
}
int status=0;//输出型参数
pid_t rpid=waitpid(pid,&status,0);//接收子进程返回的结束符,防止僵尸进程
内建命令执行
所有的内建命令都是bash本身去执行的,不会创造子进程,所以我们可以使用系统调用来实现。以cd命令为例
int Buildin()
{
int ret=0;//作为是否创建子进程标识符
if(strcmp("cd",argv[0])==0)//指令对比
{
ret=1;
char* target;
char pwd[SIZE];
if(argv[1])
{
target=argv[1];
}
else
{
target=getenv("HOME");//如果cd后无输入则跳转至用户家目录
}
chdir(target);//改变当前所处路径到target
char tmp[SIZE];
getcwd(tmp,SIZE);//得到当前所处路径
snprintf(pwd,SIZE,"PWD=%s",tmp);//组合字符串,PWD=?
putenv(pwd);//改变环境变量PWD为当前路径
}
return ret;
}
主函数
int main()
{
while(1)
{
char cmd[SIZE];
printf("[%s@%s %s]#",GetLogname(),GetHostname(),GetPWD());
fgets(cmd,SIZE,stdin);
cmd[strlen(cmd)-1]=0;
Split(cmd);
if(Buildin()==0)
{
pid_t pid=fork();
if(pid==0)
{
execvp(argv[0],argv);
}
else
{
int status=1;
pid_t rpid=waitpid(pid,&status,0);
}
}
}
}
完整代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#define SIZE 1024
#define sep " "
char* argv[SIZE];
char* tmp[64];
char* GetPWD()
{
const char* pwd=getenv("PWD");
if(pwd)
{
char* t=strrchr(pwd,'/')+1;
return t;
}
else
{
return ("None");
}
}
char* GetLogname()
{
char* Logname=getenv("LOGNAME");
if(Logname)
{
return Logname;
}
else
{
return ("None");
}
}
char* GetHostname()
{
char* hostname=getenv("HOSTNAME");
if(hostname)
{
return hostname;
}
else
{
return "None";
}
}
void Split(char* cmd)
{
int i=0;
argv[i++]=strtok(cmd,sep);
while(argv[i++]=strtok(NULL,sep));
}
int Buildin()
{
int ret=0;
if(strcmp("cd",argv[0])==0)
{
ret=1;
char* target;
char pwd[SIZE];
if(argv[1])
{
target=argv[1];
}
else
{
target=getenv("HOME");
}
chdir(target);
char tmp[SIZE];
getcwd(tmp,SIZE);
snprintf(pwd,SIZE,"PWD=%s",tmp);
putenv(pwd);
}
return ret;
}
int main()
{
while(1)
{
char cmd[SIZE];
printf("[%s@%s %s]#",GetLogname(),GetHostname(),GetPWD());
fgets(cmd,SIZE,stdin);
cmd[strlen(cmd)-1]=0;
Split(cmd);
if(Buildin()==0)
{
pid_t pid=fork();
if(pid==0)
{
execvp(argv[0],argv);
}
else
{
int status=1;
pid_t rpid=waitpid(pid,&status,0);
}
}
}
}
总结
如果有什么错误的地方,欢迎指正!