myshell

开始之间,先写一个可能错误百出的框架

设置屏蔽信号
创建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;
}

你可能感兴趣的:(myshell)