(1)清楚命令解释器的含义:
在计算机科学中,Shell俗称壳,是指“为使用者提供操作界面”的软件(command interprter,命令解析器)。它接受用户命令,然后调用相应的应用程序
(2)清楚系统中命令的分类,分为:
内置命令:exit cd
普通命令(子进程帮助完成的):ls pwd ps cp
Shell脚本中命令运行时若不用创建子进程则是内置命令;—shell把它们当成脚本中的函数处理; 内置命令在目录列表时是看不见的,它们由Shell本身提供。
所以在bash命令解释器的实现中,处理内置命令和普通命令需要分开处理,普通命令用“fork()+exec(让子进程执行exec)”来实现,让子进程去处理
(3)主函数以及相关函数大致思路:
1>
while(1)//因为命令解释器不会自动退出,所以需要让它是死循环
{
printf("stu@quzijie:/usr/bin $");//模拟Linux系统下打印用户名相对路径
fgets(buff,128,stdin);//用fgets函数获取从键盘内输入的命令
//分割命令(这里使用strtok分割函数)
char *myargv[10]={0};//myargv是获取到的命令,用数组进行保存
char *cmd=myargv[0];//myargv[0]就是命令,后面的是它所带的参数
if(cmd:cd)...//cd,exit特殊情况特殊处理
if(cmd:exit...)
//其他普通命令:fork()+exec(让子进程执行exec)
}
2>strtok字符串分割函数介绍:
char*s=strtok(buff," ");//strtok有 一个指针记录分到哪里
s=strtok(NULL," ");//传空指针的原因是内部有一个记录的,是一个静态变量
s=strtok(NULL," ");
NULL s=strtok(NULL," ");
该函数中定义了一个静态变量,一个用来记录分割到哪部分的指针,设置为静态变量的原因是如果是局部变量,第一次使用完就会销毁掉,所以需要使用静态变量
(4)相关注意问题
1>buff[strlen(buff)-1]='\0';
这句主要是为了在退出时不会让程序没有反应,“strcmp(cmd,"exit")”,在这个比较函数中,如果不加上面那句那么键盘上输入"exit",是"exit\n"和"exit\0"比较,所以要用"buff[strlen(buff)-1]='\0';"把"exit\n"变为"exit\0"就会比较成功,让程序直接退出
下图是没加那句话时"cmd"的结果,第一张图片显示cmd="exit\n",所以第二张图片在运行时就不能退出,因为"exit\n"和"exit\0"不相等
下图是用gdb调试出来的结果,输入“exit”第二张也无法退出
加上“buff[strlen(buff)-1]='\0';”这句话之后,在键盘输入"exit"按回车之后那句话会把cmd变成"exit\0",所以会比较成功,程序会直接退出。
2>子进程里面的“exit(0)”能不能不要
不能。原因是如果把这句话删掉,假如输入一个错误的命令,子进程不会直接退出,输入"exit"后有子进程和父进程两个进程,所以需要输入两次"exit"这个程序才会退出。如果输入不止一个错误命令,那么就会产生很多进程,无法一次性退出。所以不能不要子进程中的"exit(0)"
3>获取用户名,主机名,路径,是管理员还是普通用户
printf("%s@%s %s%s")为了区分把系统的冒号改为空格
(5)具体代码实现
#include<stdio.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<pwd.h>
#include<wait.h>
#define ARG_MAX 10
#define PATH_BIN "/home/stu/class04/test15/mybin/"
//这个是自己写的命令存放位置,把它定义成宏
//这个是字符串分割函数,对输入的命令实现分割
char* get_cmd(char*buff,char*myargv[])
{
if(buff==NULL||myargv==NULL)
{
return NULL;
}
char *s=strtok(buff," ");//字符串分割函数strtok,上面有解释
int i=0;
while(s!=NULL)
{
myargv[i++]=s;
s=strtok(NULL," ");
}
return myargv[0];
//这个是普通命令实现时调用的函数,如果是普通命令,就由子进程来实现,
顺序是先fork一个子进程,由子进程在相关命令存储位置去调用
void run_cmd(char*path,char*myargv[])
{
if(path==NULL||myargv==NULL)
{
return;
}
pid_t pid=fork();
assert(pid!=-1);
if(pid==-1)
{
printf("pid_t error\n");
}
if(pid==0)
{
//execvp(path,myargv);这句话本来的意思是直接从系统中调用命令,那么所有命令在自己写的
myshell系统中都会实现
char pathname[128]={0};
if(strncmp(path,"/",1)==0||strncmp(path,"./",2)==0)//这句话是模仿系统,"."和".."
文件不打印,这两个文件是系统中默认存在的不会打印,所以自己在处理的时候也不打印
{
strcpy(pathname,path);
}
else
{
strcpy(pathname,PATH_BIN);
strcat(pathname,path);
}
execv(pathname,myargv);
perror("execvp error\n");
exit(0);
}
else
{
wait(NULL);//防止出现僵死进程
}
}
void printf_info()//获取用户名,主机名,路径,是管理员还是普通用户
{
char*user_str="$";
int user_id=getuid();
if(user_id==0)
{
user_str="#";
}
struct passwd*ptr=getpwuid(user_id);
if(ptr==NULL)
{
printf("mybash1.0>> ");
fflush(stdout);
return;
}
char hostname[128]={0};
if(gethostname(hostname,128)==-1)
{
printf("mybash1.0>> ");
fflush(stdout);
return;
}
char dir[256]={0};
if(getcwd(dir,256)==NULL)
{
printf("mybash1.0>> ");85 fflush(stdout);
return;
}
//下面是字体颜色的设置
printf("\033[1;32m%s@%s\033[0m \033[1;34m %s\033[0m%s",ptr->pw_name,hostna me,dir,user_str);
fflush(stdout);
}
int main()
{
while(1)
{
printf_info();
fflush(stdout);
char buff[128]={0};
fgets(buff,128,stdin);
buff[strlen(buff)-1]='\0'; 102 char*myargv[ARG_MAX]={0};
char*cmd=get_cmd(buff,myargv);
if(cmd==NULL)
{
continue;
}
//两个内置命令
else if(strcmp(cmd,"cd")==0)
{
if(myargv[1]!=NULL)
{
if(chdir(myargv[1])==-1)
{
perror("cd err!\n");
}
}
}
else if(strcmp(cmd,"exit")==0)
{
break;
}
//其他普通命令统一调用函数实现
else
{
run_cmd(buff,myargv);
}
}
exit(0);
}
(6)mybin中部分代码实现
1>clear.c的实现
#include<stdio.h>
int main()
{
printf("\033[2J\033[0;0H");
}
2>ls.c的实现
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<dirent.h>
#include<string.h>
#include<sys/stat.h>
int main()
{
char path[256]={0};
if(getcwd(path,256)==NULL)
{
perror("getcwd error\n");
exit(1);
}
DIR*pdir=opendir(path);
if(pdir==NULL)
{
printf("opendir error!\n");
exit(0);
}
struct dirent*s=NULL;
while((s=readdir(pdir))!=NULL)
{
if(strncmp(s->d_name,".",1)==0)
{
continue;
}
struct stat filestat;
stat(s->d_name,&filestat);
if(S_ISDIR(filestat.st_mode))
{
printf("\033[1;34m%s\033[0m ",s->d_name);
}
else
{
if(filestat.st_mode&(S_IXUSR|S_IXGRP|S_IXOTH))
{
printf("\033[1;32m%s\033[0m ",s->d_name);
}
else
{
printf("%s ",s->d_name);
}
}
}
printf("\n");
closedir(pdir);
exit(0);
}
3>mycp.c的实现
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char*argv[])
{
if(argc!=3)
{
printf("argc error\n");
}
char *file_name=argv[1];
char *newfile_name=argv[2];
int fdr=open(file_name,O_RDONLY);
int fdw=open(newfile_name,O_WRONLY|O_CREAT,0600);
if(fdr==-1||fdw==-1)
{
exit(0);
}
char buff[256]={0};
int num=0;
while((num=read(fdr,buff,256))>0)
{
write(fdw,buff,num); 24 }
close(fdr);
close(fdw);
exit(0);
}
4>mykill.c的实现
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<signal.h>
#include<unistd.h>
int main(int argc,char*argv[])
{
if(argc!=3)
{
printf("argc error!\n");
return -1;
}
int pid=0;
int sig=0;
sscanf(argv[1],"%d",&pid);
sscanf(argv[2],"%d",&sig);
if(kill(pid,sig)==-1)
{
printf("kill error!\n");
}
}
5>myps.c的实现
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc,char*argv[],char*envp[])
{
printf("main pid=%d\n",getpid());
char*myargv[]={"ps","-f",0};
execve("/usr/bin/ps",myargv,envp);
exit(0);
}
6>pwd.c的实现
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
char path[256]={0};
if(getcwd(path,256)==NULL)
{
perror("getcwd error\n");
exit(1);
}
printf("%s\n",path);
exit(0);
}
(7)命令解释器实现演示
mybin里面的命令:
自己写的和系统的用冒号和空格做了区分: