参考网址: http://blog.chinaunix.net/uid-8996150-id-2011653.html
大家每天都在用shell,每天都用它执行命令,有没有想过shell是怎么实现的呢?今天我们就来模拟实现以下myshell。
我们可以先回忆一下我们用shell的时候分为了几个过程:
1、输出提示符,显示[用户名@主机名 当前所在目录名]$(#代表超级用户,$代表普通用户)
2、输入命令(特殊命令 cd 和exit特殊处理)
3、命令分割
4、用fork创建新进程
5、调用exec函数,同时在这个时候才去完成了命令的合法检测,如果execv里传过来的实参,execv找不到,则报错,调用命令失败。
那我们函数的大体框架就是:
//全局变量
char oldpwd[128]={0}; //是为了实现 '-'进入上一次操作的目录,需要记录,每次跳转页需要更新
char *Argv[10]={0}; //将命令分割之后,Argv[0]是用户输入命令路径 ,后面的是参数,分割存储起来为调
char *Argv[10]={0}; //将命令分割之后,Argv[0]是用户输入命令路径 ,后面的是参数,分割存储起来为调
//用execv传更多参数信息,execv会对Argv中的每个成员进行遍历,尝试匹配每一个成员,也就完成了参数实现
int main()
{
//打印提示信息
while(1) //我们使用shell的时候它这个是一直会显示,除非我们退出bash
{
Print_Flag(); //打印提示信息
char cmd[128]={0}; //保存输入的命令
fgets(cmd,128,stdin); //获取用户输入的命令
cmd[strlen(cmd)-1]=0; //fget函数会获得最后一个输入'\n',处理掉最后一个'\n'
fgets(cmd,128,stdin); //获取用户输入的命令
cmd[strlen(cmd)-1]=0; //fget函数会获得最后一个输入'\n',处理掉最后一个'\n'
if(strlen(cmd) == 0) //如果什么都没输显示提示符,再来一遍这个过程
{
continue;
}
{
continue;
}
}
//如果输入的是cd的话,需要特殊处理,因为它就不是在/mabash/bin调用可执行文件这么简单,它得切换 //到其他的目录下,这个需要系统提供的函数支持
if(strncmp(cmd,"cd",2) == 0)
{
My_Cd(cmd);
continue;
}
if(strncmp(cmd,"cd",2) == 0)
{
My_Cd(cmd);
continue;
}
//exit也需要特殊处理,我们在bash中调用exit其实就是退出bash,同样如果我们是想退出mybash,其实
//就是想退出这个子进程
if(strncmp(cmd,"exit",4) == 0)
{
exit(0);
}
if(strncmp(cmd,"exit",4) == 0)
{
exit(0);
}
//我们需要把命令切割一下,分离 命令和参数,才能调用正确的可执行文件
Cut_Cmd(cmd);
//创建新的进程
pid_t n=fork();
assert(n!=-1);
if(n == 0)
{
char path[128]="/home/ningcan/project/myshell/mybin";
if(strstr(Argv[0],"/")!= NULL) //如果它带有‘/’,认为它是绝对路径,就直接用这个路径
pid_t n=fork();
assert(n!=-1);
if(n == 0)
{
char path[128]="/home/ningcan/project/myshell/mybin";
if(strstr(Argv[0],"/")!= NULL) //如果它带有‘/’,认为它是绝对路径,就直接用这个路径
{
memset(path,0,128);
strcpy(path,Argv[0]);
}
else //如果没有,说明用户给的是相对路径,我们把原来写好的绝对路径连接上
{
strcat(path,Argv[0]);
}
execv(path,Argv);
perror("exec :");
exit(0);
}
else
{
wait(NULL);
}
}
memset(path,0,128);
strcpy(path,Argv[0]);
}
else //如果没有,说明用户给的是相对路径,我们把原来写好的绝对路径连接上
{
strcat(path,Argv[0]);
}
execv(path,Argv);
perror("exec :");
exit(0);
}
else
{
wait(NULL);
}
}
下面我们来说说各个模块的具体实现:
打印提示信息
void Printf_Flag() //打印提示信息
{
uid_t uid=getuid(); // uid_t,它定义在头文件sys/types.h中。它通常是一个小整数
{
uid_t uid=getuid(); // uid_t,它定义在头文件sys/types.h中。它通常是一个小整数
char flag='$';
if(uid == 0) //如果是超级用户标识为#,0代表超级用户
{
flag='#';
}
if(uid == 0) //如果是超级用户标识为#,0代表超级用户
{
flag='#';
}
struct passwd *pw=getpwuid(uid); //函数原型:struct passwd *pw=getpwuid(uid_t uid);
assert(pw != NULL);
struct utsname hostname;
uname(&hostname); //这里需要我们自己定义一个结构体,然后通过传指针解引用,将值带出来
uname(&hostname); //这里需要我们自己定义一个结构体,然后通过传指针解引用,将值带出来
char hname[128]={0};
strcpy(hname,hostname.nodename);
int i=0;
while(hname[i] != '.') i++;
hname[i]='\0';
strcpy(hname,hostname.nodename);
int i=0;
while(hname[i] != '.') i++;
hname[i]='\0';
//这里是为了获得当前所在目录名
char path[128]={0};
getcwd(path,127);//char *getcwd(char *buf, size_t size); //注释3
char *p=&path[strlen(path)-1]; //将指针p指向path的最后一个元素
getcwd(path,127);//char *getcwd(char *buf, size_t size); //注释3
char *p=&path[strlen(path)-1]; //将指针p指向path的最后一个元素
//为了解决在家目录的时候系统用~代替目录名的情况 , pw_dir就是passwd中提供的家目录信息
if(strcmp(path,pw->pw_dir) == 0)
{
p="~";
}
if(strcmp(path,pw->pw_dir) == 0)
{
p="~";
}
//解决在根目录下我们普通的处理会忽视掉最右边的'/'的情况
else if(strcmp(path,"/") == 0)
{
p=path;
}
else if(strcmp(path,"/") == 0)
{
p=path;
}
//普通情况的处理
else
{
while(*p != '/')
{
p--;
}
++p; //我们只需要显示名字,不要最后的'/',所以就把p++
}
else
{
while(*p != '/')
{
p--;
}
++p; //我们只需要显示名字,不要最后的'/',所以就把p++
}
printf("mybash[%s@%s %s]%c ",pw->pw_name,hname,p,flag);
fflush(stdout);
fflush(stdout);
}
注释1:还有一个是getunam函数,和getpwuid类似,getpwuid可以传递 一个 uid来在/etc/passwd中搜索,我没有看过getpwuid怎样实现的,但是我猜测可能/etc/passwd 是通过链表或者数组存储, 然后链表里的数据成员或者数组里的数据成员都是一个struct passwd类型的结构体。如果是链表,则通过逐一搜索每个节点中的UID看是否匹配,这样搜索的时间复杂度得到到O(n),但是插入删除新用户信息快。如果匹配成功就将这一条记录的信息填入创建的静态局部struct
passwd类型的变量。
这里为什么是静态局部变量?
因为我们要返回一个一个passwd 类型的指针,那我们如果返回的是局部变量,如果大家看过一些汇编代码,应该知道局部变量在函数栈帧中开辟,那我们返回的时候将指向函数栈帧中局部变量的地址放到寄存器,让寄存器带出临时变量的地址,一退出getpwuid这个函数,栈帧被销毁,寄存器带出来的临时变量指向的那片区域就已经不属于我们在getpwuid中定义的临时变量了,一旦有新的数据需要入栈,我们原来指向的那片区域内容被覆盖,那就会发生错误。因为静态变量有和源程序一样的生命周期,所以当getpwuid返回的时候,指针指向的那片区域还在,还可以访问,所以不会有什么问题。
因为我们要返回一个一个passwd 类型的指针,那我们如果返回的是局部变量,如果大家看过一些汇编代码,应该知道局部变量在函数栈帧中开辟,那我们返回的时候将指向函数栈帧中局部变量的地址放到寄存器,让寄存器带出临时变量的地址,一退出getpwuid这个函数,栈帧被销毁,寄存器带出来的临时变量指向的那片区域就已经不属于我们在getpwuid中定义的临时变量了,一旦有新的数据需要入栈,我们原来指向的那片区域内容被覆盖,那就会发生错误。因为静态变量有和源程序一样的生命周期,所以当getpwuid返回的时候,指针指向的那片区域还在,还可以访问,所以不会有什么问题。
还有一个问题是,这些都是建立在我猜测它是创建了一个局部静态变量的情况,也有可能是将指针直接指向系统中备份那份配置文件,设置为只读,但是我觉得这样可能会有危险,而且灵活性差,所以我更加倾向于第一种猜测,如果大家知道可以给我纠正哦!不胜感激! getuname也是类似的,只是根据名字搜索。
注释2: struct utsname
{
char sysname[_UTSNAME_SYSNAME_LENGTH]; //当前操作系统名
char nodename[_UTSNAME_NODENAME_LENGTH]; //网络上的名称
char release[_UTSNAME_RELEASE_LENGTH]; //当前发布级别
char version[_UTSNAME_VERSION_LENGTH]; //当前发布版本
char machine[_UTSNAME_MACHINE_LENGTH]; //当前硬件体系类型
{
char sysname[_UTSNAME_SYSNAME_LENGTH]; //当前操作系统名
char nodename[_UTSNAME_NODENAME_LENGTH]; //网络上的名称
char release[_UTSNAME_RELEASE_LENGTH]; //当前发布级别
char version[_UTSNAME_VERSION_LENGTH]; //当前发布版本
char machine[_UTSNAME_MACHINE_LENGTH]; //当前硬件体系类型
#if _UTSNAME_DOMAIN_LENGTH - 0
# ifdef __USE_GNU
char domainname[_UTSNAME_DOMAIN_LENGTH]; //当前域名
# else
char __domainname[_UTSNAME_DOMAIN_LENGTH];
# endif
#endif
};
# ifdef __USE_GNU
char domainname[_UTSNAME_DOMAIN_LENGTH]; //当前域名
# else
char __domainname[_UTSNAME_DOMAIN_LENGTH];
# endif
#endif
};
我们这里用到了它的 nodename的成员信息。
注释3:如果大家对getcwd的实现感兴趣,大家可以进入这个网址看一下。
http: //blog.youkuaiyun.com/baidu_35085676/articl e/details/52002579
//分割命令
void Cut_Cmd(char *cmd)
{
int i=0;
memset(Argv,0,sizeof(Argv)); //初始化,防止上一次参数还在,影响这一次的结果
char *p=strtok(cmd," ");
while(p != NULL)
{
Argv[i++]=p;
p=strtok(NULL ," "); //详细说明可以参考http://blog.youkuaiyun.com/liuintermilan/article/details/6280816
}
{
int i=0;
memset(Argv,0,sizeof(Argv)); //初始化,防止上一次参数还在,影响这一次的结果
char *p=strtok(cmd," ");
while(p != NULL)
{
Argv[i++]=p;
p=strtok(NULL ," "); //详细说明可以参考http://blog.youkuaiyun.com/liuintermilan/article/details/6280816
}
}
//处理特殊命令cd
void My_Cd(char *cmd)
{
char *p=cmd;
while(*p != 0)
{
if(*p==' ' && *(p+1)!=' ') 这个就算cd 后面有很多空然后接参数或者直接没有参数都没有问题
{
p++;
break;
}
p++;
}
if(strlen(p) == 0 || strcmp(p,"~")== 0) //cd 后面没有参数或者是跟着~ ,进入家目录
{
struct passwd *pw=getpwuid(getuid());
assert(pw != NULL);
if(-1 == chdir(pw->pw_dir)) //切换家目录失败就退出,pw_dir 进入家目录
{
perror("cd ::");
return;
}
}
else if(strcmp(p ,"-") == 0) //cd 后面加- ,表示进入上一次目录
{
if(strlen(oldpwd)==0) //表示还没有上一次路径,这是第一个路径
{
printf("OLDPWD not set\n");
return ;
}
else
{
if(-1 == chdir(oldpwd)) //切换失败就退出
{
perror("cd - ::");
return ;
}
}
}
else
{
if(-1 == chdir(p)) //不然就切换到cd指定的路径中了
{
perror("cd :: ");
return ;
}
}
strcpy(oldpwd,nowpath);
{
//cd path
// cd .. 上一级目录
// cd ~ 家目录
// cd - 上一次的目录
char nowpath[128]={0}; //记录现在的地址,一旦切入新的目录成功,现在的路径就是上一次的路径
getcwd(nowpath,127); //保存现在的路径
char nowpath[128]={0}; //记录现在的地址,一旦切入新的目录成功,现在的路径就是上一次的路径
getcwd(nowpath,127); //保存现在的路径
char *p=cmd;
while(*p != 0)
{
if(*p==' ' && *(p+1)!=' ') 这个就算cd 后面有很多空然后接参数或者直接没有参数都没有问题
{
p++;
break;
}
p++;
}
if(strlen(p) == 0 || strcmp(p,"~")== 0) //cd 后面没有参数或者是跟着~ ,进入家目录
{
struct passwd *pw=getpwuid(getuid());
assert(pw != NULL);
if(-1 == chdir(pw->pw_dir)) //切换家目录失败就退出,pw_dir 进入家目录
{
perror("cd ::");
return;
}
}
else if(strcmp(p ,"-") == 0) //cd 后面加- ,表示进入上一次目录
{
if(strlen(oldpwd)==0) //表示还没有上一次路径,这是第一个路径
{
printf("OLDPWD not set\n");
return ;
}
else
{
if(-1 == chdir(oldpwd)) //切换失败就退出
{
perror("cd - ::");
return ;
}
}
}
else
{
if(-1 == chdir(p)) //不然就切换到cd指定的路径中了
{
perror("cd :: ");
return ;
}
}
strcpy(oldpwd,nowpath);
}
上面可能这样混杂着注释,大家不好复制源代码调试,我们再整理一下无注释的源代码
#include
#include
#include
#include //getcwd
#include
#include // getpwuid() 、uid_t
#include//uname
#include// struct passwd
#include
#include
#include //getcwd
#include
#include // getpwuid() 、uid_t
#include//uname
#include// struct passwd
char oldpwd[128]={0};
char *Argv[10]={0};
char *Argv[10]={0};
void Printf_Flag()
{
int uid=getuid();
char flag='$';
if(uid == 0)
{
flag='#';
}
struct passwd *pw=getpwuid(uid);
assert(pw != NULL);
{
int uid=getuid();
char flag='$';
if(uid == 0)
{
flag='#';
}
struct passwd *pw=getpwuid(uid);
assert(pw != NULL);
struct utsname hostname;
uname(&hostname);
char hname[128]={0};
strcpy(hname,hostname.nodename);
int i=0;
while(hname[i] != '.') i++;
hname[i]='\0';
uname(&hostname);
char hname[128]={0};
strcpy(hname,hostname.nodename);
int i=0;
while(hname[i] != '.') i++;
hname[i]='\0';
char path[128]={0};
getcwd(path,127);
char *p=&path[strlen(path)-1];
if(strcmp(path,pw->pw_dir) == 0)
{
p="~";
}
else if(strcmp(path,"/") == 0)
{
p=path;
}
else
{
while(*p != '/')
{
p--;
}
++p;
}
getcwd(path,127);
char *p=&path[strlen(path)-1];
if(strcmp(path,pw->pw_dir) == 0)
{
p="~";
}
else if(strcmp(path,"/") == 0)
{
p=path;
}
else
{
while(*p != '/')
{
p--;
}
++p;
}
printf("mybash[%s@%s %s]%c ",pw->pw_name,hname,p,flag);
fflush(stdout);
fflush(stdout);
}
void Cut_Cmd(char *cmd)
{
int i=0;
memset(Argv,0,sizeof(Argv));
char *p=strtok(cmd," ");
while(p != NULL)
{
Argv[i++]=p;
p=strtok(NULL ," ");
}
void Cut_Cmd(char *cmd)
{
int i=0;
memset(Argv,0,sizeof(Argv));
char *p=strtok(cmd," ");
while(p != NULL)
{
Argv[i++]=p;
p=strtok(NULL ," ");
}
}
void My_Cd(char *cmd)
{
void My_Cd(char *cmd)
{
char nowpath[128]={0};
getcwd(nowpath,127);
char *p=cmd;
while(*p != 0)
{
if(*p==' ' && *(p+1)!=' ')
{
p++;
break;
}
p++;
}
if(strlen(p) == 0 || strcmp(p,"~")== 0)
{
struct passwd *pw=getpwuid(getuid());
assert(pw != NULL);
if(-1 == chdir(pw->pw_dir))
{
perror("cd ::");
return;
}
}
else if(strcmp(p ,"-") == 0)
{
if(strlen(oldpwd)==0)
{
printf("OLDPWD not set\n");
return ;
}
else
{
if(-1 == chdir(oldpwd))
{
perror("cd - ::");
return ;
}
}
}
else
{
if(-1 == chdir(p))
{
perror("cd :: ");
return ;
}
}
strcpy(oldpwd,nowpath);
}
void main()
{
while(1)
{
Printf_Flag();
char cmd[128]={0};
fgets(cmd,128,stdin);
cmd[strlen(cmd)-1]=0;
void main()
{
while(1)
{
Printf_Flag();
char cmd[128]={0};
fgets(cmd,128,stdin);
cmd[strlen(cmd)-1]=0;
if(strlen(cmd) == 0)
{
continue;
}
if(strncmp(cmd,"cd",2) == 0)
{
My_Cd(cmd);
continue;
}
if(strncmp(cmd,"exit",4) == 0)
{
exit(0);
}
Cut_Cmd(cmd);
pid_t n=fork();
assert(n!=-1);
if(n == 0)
{
char path[128]="/home/ningcan/project/myshell/mybin";
if(strstr(Argv[0],"/")!= NULL)
{
memset(path,0,128);
strcpy(path,Argv[0]);
}
else
{
strcat(path,Argv[0]);
}
execv(path,Argv);
perror("exec :");
exit(0);
}
else
{
wait(NULL);
}
}
}
{
continue;
}
if(strncmp(cmd,"cd",2) == 0)
{
My_Cd(cmd);
continue;
}
if(strncmp(cmd,"exit",4) == 0)
{
exit(0);
}
Cut_Cmd(cmd);
pid_t n=fork();
assert(n!=-1);
if(n == 0)
{
char path[128]="/home/ningcan/project/myshell/mybin";
if(strstr(Argv[0],"/")!= NULL)
{
memset(path,0,128);
strcpy(path,Argv[0]);
}
else
{
strcat(path,Argv[0]);
}
execv(path,Argv);
perror("exec :");
exit(0);
}
else
{
wait(NULL);
}
}
}