通过几个模型学习shell命令解释器,并尝试自己编写简易的shell
一、确定main函数
明确要做的功能,基本确定几个大函数:
void InitialEnv(); //initial environment
void PrintSignal(); //print information
void GetInput();
int CommandTransform(); //return 1 for error
void Clear(); //clear last command
其中,自己做的前面两个函数可以合并(读者可自行优化)
int main(void)
{
printf("\n Hello my guester!\n");
while(TRUE){
InitialEnv(); //initial comply's emvironment
PrintSignal(); //print sth to recognize user to input commands
GetInput();
if (strcmp(cmd[0],"exit")==0) //exit
{
printf("\n Good bye!\n");
break;
}
if(CommandTransform())
printf("input error: undefined command\n");
Clear();
}
return 0;
}
大纲完成后自己就开始列写所需的全局变量了:
char *cmds; //command
int cmds_len; //command's length
char **cmd; //every commands' word
int cmd_num; //number of commands' words
int pip; //how many pipes in command
二、功能函数的实现:
先看前两个函数,功能比较简单:
把主机名和当前目录打印一下并打印输入提示符等待输入;代码如下:
void PrintSignal()
{
uid_t my_uid;
struct passwd *my_info;
my_info =getpwuid( getuid() );
char env[50];
getcwd(env,50);
printf( "%s=> %s$ ", my_info->pw_name,env);
}
其中getpwuid()函数可以得到my_uid的各个属性,在这里使用获得主机名。getcwd()函数用以输出当前目录。
void InitialEnv()
{
int i=0;
while((cmds = (char*)malloc(sizeof(char)*100))==0);
while((cmd = (char**)malloc(sizeof(char*)*20))==0);
while(i<20) {
while((cmd[i] =(char*)malloc(sizeof(char)*40))==0);
i = i + 1;
}
cmd_num = 0;
pip = 0;
}
初始化环境函数:为存储指令的字符指针申请动态空间(malloc函数)
将输入存入字符串:
void GetInput()
{
int i = 0;
int j = 0;
//freopen("/dev/tty","r",stdin);
gets(cmds);
while (cmds[i]!= '\0')
{
if(cmds[i]!=' ')
cmd[cmd_num][j++]=cmds[i];
else
{
if ((cmds[i+1]!='\0')&&(cmds[i+1]!=' '))
cmd_num++;
j = 0;
}
i++;
}
cmd_num++;
}
gets()函数得到输入的字符串 以’\n’结束
接下来实现比较复杂的解释输入的“指令”的函数,以下是包含了管道的实现的代码,可能比较繁冗,读者看思路即可:
函数思路:
当还有命令词未判断时循环:判断是否为第一个参数->判断是否包含管道->执行相应的命令(内部和外部,其中外部命令是指在unix操作系统下以文件的形式存在的命令函数,故可用fork-execve-wait-exit模型来调用,切记要让子进程exit)
int CommandTransform()
{
int fd[10][2];
int pip = -1;
int i = 0;
pid_t pid;
if (strlen(cmd[0])!=0)
while(i<cmd_num)
{
if (strcmp(cmd[i],"cd") == 0)
{
if (i==0)
{
int fp;
if (strlen(cmd[1])==0) return 0; //cd command can't use the pipe '|'
if(strlen(cmd[2])!=0) return 1;
const char* p = cmd[1];
if((fp=open(p,O_RDONLY)) < 0) //find the path
{
printf("Can not open the directory %s\n",cmd[1]);
return 0;
}
if (fchdir(fp) < 0) //call fchdir() to change dir
{
printf("cd failed,try again\n");
return 0;
}
return 0;
}
else
{
close(0);
dup2(fd[pip][0],0);;
close(fd[pip][1]);
read(fd[pip][0],cmd[i+1],40);
dup2(0,fd[pip][0]);
const char* p = cmd[i+1];
int fp;
if((fp=open(p,O_RDONLY)) < 0) //find the path
{
printf("Can not open the directory %s\n",p);
return 0;
}
if (fchdir(fp) < 0) //call fchdir() to change dir
{
printf("cd failed,try again\n");
return 0;
}
return 0;
}
}
else if (strcmp(cmd[i],"echo") == 0)
{
if (i==0)
{
if (strlen(cmd[2])==0)
{
printf("%s\n",cmd[i+1]);
return 0;
}
else if (strcmp(cmd[2],"|")==0)
{
if(pipe(fd[++pip])<0)
{
printf("pipe error!/n");
return 0;
}
if((pid = fork())<0)
{
printf("fork error!/n");
return 0;
}
else if(pid == 0)
{
close(1);
dup2(fd[pip][1],1);
close(fd[pip][0]);
write(fd[pip][1],cmd[1],40);
exit(0);
}
else if (pid >0)
{
waitpid(pid,NULL,0);
i+=3;
}
}
else return 1;
}
else
{
if(strlen(cmd[i+1])==0)
{
close(0);
dup2(fd[pip][0],0);
close(fd[pip][1]);
read(fd[pip][0],cmd[i+1],40);
dup2(0,fd[pip][0]);
printf("%s\n",cmd[i+1]);
return 0;
}
else if (cmd[i+1]!="|")
{
printf("input --error!\n");
return 0;
}
else
{
close(0);
fd[pip][0]=dup(0);
close(fd[pip][1]);
read(fd[pip][0],cmd[i+1],40);
if(pipe(fd[++pip])<0)
{
printf("pipe error!/n");
return 0;
}
close(1);
fd[pip][1] = dup(1);
close(fd[pip][0]);
write(fd[pip][1],cmd[i+1],40);
i += 2;
}
}
}
else if (strcmp(cmd[i],"wc") == 0)
{
if (i==0)
{
FILE *fp;
int c;
int res_current[3]={0};
const char* p = cmd[1];
if((fp=fopen(p,"r"))==(FILE*)NULL)
{
c=3; //"wc " takes 3 room
while(cmds[c]!='\0')
{
int flag;
res_current[2]++;
if(c=='\n')
{
res_current[0]++;
}
c++;
}
printf("line: %d word: %d character: %d\n",res_current[0],cmd_num-1,res_current[2]);
return 0;
}
c=fgetc(fp); //file-get-char function
while(c!=EOF)
{
int flag;
res_current[2]++;
if(c=='\n')
{
res_current[0]++;
}
if(c=='\t' || c== ' ' || c=='\n')
{
if(flag==0)
{
res_current[1]++;
flag=1;
}
}
else flag=0;
c=fgetc(fp);
}
fclose(fp);
printf("line: %d word: %d character: %d in '%s'\n",res_current[0],res_current[1],res_current[2],p);
return 0;
}
else
{
if((pid = fork())<0)
{
printf("fork error!/n");
return 0;
}
if(pid == 0)
{
FILE * fp;
close(0);
dup2(fd[pip][0],0);
close(fd[pip][1]);
read(fd[pip][0],cmd[i+1],40);
dup2(0,fd[pip][0]);
int res_current[3]={0};
if (strlen(cmd[i+1])==0)
{
printf("open error!\n");
return 0;
}
if((fp=fopen(cmd[i+1],"r"))==(FILE*)NULL)
{
int c=0;
while(cmd[i+1][c]!='\0')
{
int flag;
res_current[2]++;
if(c=='\n')
{
res_current[0]++;
}
if(c=='\t' || c== ' ' || c=='\n')
{
res_current[1]++;
}
c++;
}
printf("line: %d word: %d character: %d\n",res_current[0]+1,res_current[1]+1,res_current[2]);
exit(0);
}
else
{
int c=fgetc(fp); //file-get-char function
while(c!=EOF)
{
int flag;
res_current[2]++;
if(c=='\n')
{
res_current[0]++;
}
if(c=='\t' || c== ' ' || c=='\n')
{
res_current[1]++;
}
c=fgetc(fp);
}
fclose(fp);
printf("line: %d word: %d character: %d in '%s'\n",res_current[0],res_current[1],res_current[2],cmd[i+1]);
exit(0);
}
}
else if (pid > 0) waitpid(pid,NULL,0);
return 0;
}
}
else if (strcmp(cmd[i],"ls") == 0)
{
if (i==0)
{
if (strlen(cmd[1])==0)
{
char *argv[]={"ls","-al",(char*)0};
char *envp[] = {"PATH=/bin",0};
if((pid = fork())==0)
{
execve("/bin/ls",argv,envp);
exit(0);
}
else if (pid > 0) waitpid(pid,NULL,0);
return 0;
}
else if(cmd[1]=="|")
{
if(pipe(fd[++pip])<0)
{
printf("pipe error!/n");
return 0;
}
char *argv[]={"ls","-al",(char*)0};
char *envp[] = {"PATH=/bin",0};
if((pid = fork())==0)
{
close(1);
dup2(fd[pip][1],1);
close(fd[pip][0]);
execve("/bin/ls",argv,envp);
exit(0);
}
else if (pid > 0) waitpid(pid,NULL,0);
i+=2;
}
else
{
if (cmd[2]=="|")
{
if(pipe(fd[++pip])<0)
{
printf("pipe error!/n");
return 0;
}
char *argv[]={"ls","-al",(char*)0};
char *envp[] = {"PATH=/bin",0};
if((pid = fork())==0)
{
close(1);
dup2(fd[pip][1],1);
close(fd[pip][0]);
if (chdir(cmd[1])==-1)
{
printf("no such directory.\n");
exit(0);
}
execve("/bin/ls",argv,envp);
exit(0);
}
else if (pid > 0) waitpid(pid,NULL,0);
i+=2;
}
else if(strlen(cmd[2])==0)
{
char *argv[]={"ls","-al",(char*)0};
char *envp[] = {"PATH=/bin",0};
if((pid = fork())==0)
{
if (chdir(cmd[1])==-1)
{
printf("no such directory.\n");
exit(0);
}
execve("/bin/ls",argv,envp);
exit(0);
}
else if (pid > 0) waitpid(pid,NULL,0);
return 0;
}
else return 1;
}
}
else // ls i!=0
{
if(strlen(cmd[i+1])==0)
{
char *argv[]={"ls","-al",(char*)0};
char *envp[] = {"PATH=/bin",0};
close(0);
dup2(fd[pip][0],0);
close(fd[pip][1]);
read(fd[pip][0],cmd[i+1],40);
dup2(0,fd[pip][0]);
if (strlen(cmd[i+1])==0)
{
printf("error: ls needs a dir input.\n");
return 1;
}
if((pid = fork())==0)
{
if (chdir(cmd[i+1])==-1)
{
printf("no such directory.\n");
exit(0);
}
execve("/bin/ls",argv,envp);
exit(0);
}
else if (pid > 0) waitpid(pid,NULL,0);
return 0;
}
else if (cmd[i+1]=="|")
{
char *argv[]={"ls","-al",(char*)0};
char *envp[] = {"PATH=/bin",0};
close(0);
dup2(fd[pip][0],0);
close(fd[pip][1]);
read(fd[pip][0],cmd[i+1],40);
if(pipe(fd[++pip])<0)
{
printf("pipe error!/n");
return 0;
}
if((pid = fork())==0)
{
close(1);
dup2(fd[pip][1],1);
close(fd[pip][0]);
if (strlen(cmd[i+1])!=0&&chdir(cmd[i+1])==-1)
{
printf("no such directory.\n");
exit(0);
}
execve("/bin/ls",argv,envp);
exit(0);
}
else if (pid>0) waitpid(pid,NULL,0);
i+=2;
}
else return 1;
}
}else return 1;
}
}
最后一个清零函数主要来将字符串清零,以避免对下次输入造成影响。
void Clear()
{
int i=0;
memset(cmds, 0, sizeof(char *)); //clear cmds
while (i < cmd_num)
memset(cmd[i++], 0, sizeof(char *)); //clear cmd[i]
memset(cmd, 0, sizeof(char **)); //clear cmd
}
其中涉及到的头文件如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
运行截图:
至此,一个简单的shell命令解释器就完成了,有几个涉及到管道的蛋疼的功能还是有点烂尾,读者可在此基础上修改。