开始之间,先写一个可能错误百出的框架
设置屏蔽信号
创建shell,创建一个进程
接收输入的命令,并进行解析
如果cd
则 cd
fork一个子进程,
子进程:
理解命令,并将其压缩为一个参数
执行命令
管道
创建pipe
输入输出重定向
直接执行
父进程:
若不是后台进程就是等待子进程完成
如果是后台进程就不等待
\\阻塞信号
sigset_t blockmask;
// block some signal
sigemptyset(&blockmask);
sigaddset(&blockmask,SIGINT);
sigprocmask(SIG_SETMASK,&blockmask,NULL);
接受输入的命令:
从终端输入一个字符串,将其转化为一个去掉'\n'的null-terminated 字符串
void getcmd(char* buf)
{
printf("%s","$ ");
fgets(buf,CMDSIZE,stdin);
buf[strlen(buf)-1] = '\0';
return;
}
一个难点:解析字符串
把字符串分为命令和命令之间的阻断符号(如| < 这样的东西)
原设想是要把命令根据中间的token,解析为一个个单独的不同种类的结构体。然后再 main 里面根据一个一个地根据结构体的种类执行命令。
但我的这种做法忽略了这些结构体之间的相互关系。
新设想是建立一棵树。
捋一捋翻译的优先级:如果有括号,先处理括号里的;然后再是 ;(分割),&(后台运行),|(管道),>之类的(重定向)
从父子进程的角度来看,优先级越高的越古老;从树的枝叶角度来看,优先级越高的越靠近root。
以人类处理的步骤就是:先看看优先级最高的有没有,如果有就形成能体现出这种关系的结构体,再看看那个优先级次高有没有,以此类推。
先定义一波会用到的结构体:
struct cmd{
int type;
};
struct execcmd{
int type;
char* arglt[MAXARGS];
};
struct redircmd{
int type;
int oldfd;
int mode;
char newfile[200];
struct cmd* cmd;
};
struct pipecmd{
int type;
struct cmd* left;
struct cmd* right;
};
struct listcmd{
int type;
struct cmd* left;
struct cmd* right;
};
查看开头的符号并跳过无用的空格
void peek(char**ps,char*es,char*toks)
{
char*s=*ps;
while (s
复制内容,并把自己转移到下一个间隔符号面前
void gettoken(char**ps,char*es,char**content)
{
char* p= *ps;
char* s;
peek(ps,es,"");
switch(*p)
{
case 0:break;
case '|':
case '&':
case '<':
case ';':
case '(':
case ')':
p++;
break;
case '>':
p++;
if (*p=='>')
{
p++;
}
break;
}
if (!content) return; //当content为0时,则将其忽略
s=p;
while(p
正式开始肢解命令
一层一层解析:
每一层解决一个符号,上层根据符号选择结构体,并将结构体指向下一层的子树。上下级关系取决于我们在上面排的优先级。
parseline的设定是返回一棵完整的树。它只处理后台运行和并行命令,把其他任务丢个parsepipe,parsepipe会返回一个子树。后台命令指向返回的这个子树。并行命令的左边指向已处理的子树,右边指向未处理的子树,右边再次调用parseline,因为parseline可以完美完成任务,返回一棵完整的树,所以没问题。
struct cmd* parseline(char**ps,char*es)
{
char*p = *ps;
struct cmd* cmd;
peek(&p,es,""); // skip the whitespace
cmd = parsepipe(ps,es);
if (peek(&p,es,"&"))
{
cmd = makeback(cmd);
p++;
peek(&p,es,"");
}
if (peek(&p,es,";"))
{
cmd = makelist(cmd,parseline(ps,es));
p++;
peek(&p,es,"");
}
return cmd;
}
parsepipe的任务是解决管道问题,它只处理管道,把其他任务丢给parseredir,同样地parseredir也会完美地完成它自己的任务,我们只需要聚焦于parsepipe干什么就行了。parsepipe把左指针指向处理完的子树,把右指针指向再次调用自己得到的结果。
struct cmd* parsepipe(char**ps,char*es)
{
char* p=*ps;
struct cmd* cmd;
cmd=parseredir(&p,es);
if (peek(&p,es,"|"))
{
cmd = makepipe(cmd,parsepipe(&p,es));
p++;
}
peek(&p,es,"");
return cmd;
}
接下来我遇到了问题:原计划把内容丢给parseexec,parseexec返回一个cmd结构体,然后parsereidr将制造出的redir结构体指向它。
然而制造这个结构体没有那么简单,我不但需要一个子结构体,还需要额外读入这个符号的内容(< or > or >>)以及要重定向到的文件的文件名。
为了这些额外的东西,我可以用gettoken读取这个符号的内容和要重定向的文件名。
模拟一下:
parseline->parsepipe->parseredir->parseexec
parseexec读取内容,并把指针带到下一个token。然后从开头到第一个token被封装成一个cmd。cmd被递交给parseredir,parseredir再读取一段内容,把指针带到下一个token。然后根据这次读取的内容(newfd),还有原来的cmd,封装一个新的cmd,递交给parsepipe。
问题来了,如果出现了两个重定向,那岂不就凉了。
解决办法很容易想到,把原来的if 改成 while 就行了。
主体写完了,测试了一下
debug:
每个函数中改动后末尾还要赋值一下记录指针的移动位置
发现多重重定向出问题了,第二个重定向确实open()了一个文件,但是没有往文件里写内容
有些细枝末节的地方忘记记录了,不过没关系,没记录的都不是很难。
最后的参考代码:
#include
#include
#include
#include
#include
#include
#include
#include
#define CMDSIZE 100
#define MAXARGS 10
#define EXEC 1
#define BACK 2
#define REDIR 3
#define PIPE 4
#define LIST 5
char whitespace[]=" \t\r\n\v";
char symbols[] = "<|>&;()";
void perr(char *s)
{
printf("%s:something wrong",s);
}
int peek(char**ps,char*es,char*toks)
{
char*s=*ps;
while (s':
p++;
if (*p=='>')
{
ret='+';
p++;
}
break;
default:
ret='a';
}
peek(&p,es,"");
if (!content){
*ps=p;
return ret;
}
s=p;
while(ptype = BACK;
backcmd->cmd=subcmd;
return (struct cmd*)backcmd;
}
struct cmd* makelist(struct cmd* subcmd1,struct cmd* subcmd2)
{
struct listcmd* listcmd;
listcmd = (struct listcmd*)malloc(sizeof(struct listcmd));
memset(listcmd,0,sizeof(*listcmd));
listcmd->type = LIST;
listcmd->left = subcmd1;
listcmd->right = subcmd2;
return (struct cmd*)listcmd;
}
struct cmd* makepipe(struct cmd* subcmd1,struct cmd* subcmd2)
{
struct pipecmd* pipecmd;
pipecmd = (struct pipecmd*)malloc(sizeof(struct pipecmd));
memset(pipecmd,0,sizeof((*pipecmd)));
pipecmd->type = PIPE;
pipecmd->left = subcmd1;
pipecmd->right = subcmd2;
return (struct cmd*)pipecmd;
}
struct cmd* makeredir(int oldfd,char* newfile,int mode,struct cmd* subcmd)
{
struct redircmd* redircmd;
redircmd = (struct redircmd*)malloc(sizeof(struct redircmd));
memset(redircmd,0,sizeof(*redircmd));
redircmd->type = REDIR;
redircmd->oldfd=oldfd;
redircmd->newfile=newfile;
redircmd->mode=mode;
redircmd->cmd=subcmd;
return (struct cmd*)redircmd;
}
struct cmd* makeexec(int argc,char* argv[])
{
struct execcmd* execcmd;
execcmd = (struct execcmd*)malloc(sizeof(struct execcmd));
execcmd->type = EXEC;
for (int i=0;iarglt[i]=(char*)malloc(sizeof(char)*200);
strcpy(execcmd->arglt[i],argv[i]);
}
return (struct cmd*)execcmd;
}
char prepath[500];
int main(void)
{
sigset_t blockmask;
// block some signal
sigemptyset(&blockmask);
sigaddset(&blockmask,SIGINT);
sigprocmask(SIG_SETMASK,&blockmask,NULL);
char *buf=(char*)malloc(sizeof(char)*300);
struct cmd* cmd;
getcwd(prepath,sizeof(prepath));
while (getcmd(buf))
{
while (*buf && strchr(whitespace,*buf))
buf++;
if (buf[0]=='c' && buf[1]=='d' && buf[2]==' ')
{
char path[500];
switch(*(buf+3))
{
case '~':strcpy(path,"/home/username");break;
case '-':strcpy(path,prepath);break;
default: strcpy(path,buf+3);
}
chdir(path);
strcpy(path,prepath);
}
if (fork()==0)
runcmd(analyze(buf));
wait(NULL);
}
free(buf);
return 0;
}
void runcmd(struct cmd* cmd)
{
int p[2];
struct execcmd* execcmd;
struct listcmd* listcmd;
struct backcmd* backcmd;
struct pipecmd* pipecmd;
struct redircmd* redircmd;
switch(cmd->type){
case EXEC:
execcmd=(struct execcmd*)cmd;
execvp(execcmd->arglt[0],execcmd->arglt);
break;
case LIST:
listcmd=(struct listcmd*)cmd;
if (fork()==0)
runcmd(listcmd->left);
wait(NULL);
runcmd(listcmd->right);
break;
case BACK:
backcmd=(struct backcmd*)cmd;
if (fork()==0)
runcmd(backcmd->cmd);
break;
case PIPE:
pipecmd=(struct pipecmd*)cmd;
pipe(p);
if (fork()==0)
{
dup2(p[1],1);
close(p[1]);
close(p[0]);
runcmd(pipecmd->left);
}
if (fork()==0)
{
dup2(p[0],0);
close(p[0]);
close(p[1]);
runcmd(pipecmd->right);
}
close(p[0]);
close(p[1]);
wait(NULL);
wait(NULL);
break;
case REDIR:
redircmd=(struct redircmd*)cmd;
close(redircmd->oldfd);
open(redircmd->newfile,redircmd->mode);
runcmd(redircmd->cmd);
break;
}
exit(1);
}
int getcmd(char* buf)
{
printf("%s","$ ");
fgets(buf,CMDSIZE,stdin);
buf[strlen(buf)-1] = '\0';
if (strcmp(buf,"exit")==0) return 0;
return 1;
}
struct cmd* analyze(char* buf)
{
struct cmd* cmd;
char *ps,*es;
ps = buf;
es=ps+strlen(buf);
cmd=parseline(&ps,es);
return cmd;
}
struct cmd* parseline(char**ps,char*es)
{
char*p = *ps;
struct cmd* cmd;
peek(&p,es,""); // skip the whitespace
cmd = parsepipe(&p,es);
if (peek(&p,es,"&"))
{
cmd = makeback(cmd);
p++;
peek(&p,es,"");
}
if (peek(&p,es,";"))
{
cmd = makelist(cmd,parseline(&p,es));
p++;
peek(&p,es,"");
}
*ps=p;
return cmd;
}
struct cmd* parsepipe(char**ps,char*es)
{
char* p=*ps;
struct cmd* cmd;
cmd=parseredir(&p,es);
if (peek(&p,es,"|"))
{
cmd = makepipe(cmd,parsepipe(&p,es));
p++;
}
peek(&p,es,"");
*ps=p;
return cmd;
}
struct cmd* parseredir(char**ps,char*es)
{
char*p=*ps;
struct cmd* cmd;
cmd=parseexec(&p,es);
while (peek(&p,es,"><"))
{
char token;
char* filename;
token = gettoken(&p,es,&filename);
switch(token)
{
case '<':
cmd=makeredir(0,filename,O_RDONLY,cmd);
break;
case '>':
cmd=makeredir(1,filename,O_WRONLY|O_CREAT|O_TRUNC,cmd);
break;
case '+':
cmd=makeredir(1,filename,O_WRONLY|O_CREAT|O_APPEND,cmd);
break;
}
}
*ps=p;
return cmd;
}
struct cmd* parseexec(char**ps,char*es)
{
struct cmd* cmd;
char*p=*ps;
char* command,*token;
int argc=0;
char* argv[MAXARGS];
gettoken(&p,es,&command);
token=strtok(command,whitespace);
while(token != NULL)
{
argv[argc++]=token;
token=strtok(NULL,whitespace);
}
cmd = makeexec(argc,argv);
*ps=p;
return cmd;
}