虚拟shell终端程序

来源:互联网 发布:pl sql developer 64位 编辑:程序博客网 时间:2024/06/11 00:36
 

一,实验内容

实现一个简单的shell终端程序

功能:能在虚拟shell界面下响应一些简单的shell命令

二.对linux环境shell工作流程的分析

提示输入

获取用户指令

解析指令

寻找命令文件

执行命令

 

 (1)提示输入

2)获取用户指令

3)解析指令

4)寻找命令文件

5)执行命令

三.对上述五个过程的详细说明

(1)在虚拟的shell界面中出现命令提示符($#

path = get_current_dir_name();

        printf("%s>$",path);

说明:函数get_current_dir_name:获取当前目录名,返回值是一个指向当前目录绝对路径的字符串指针

2)获取用户指令:获取用户在命令提示符后面输入的命令和参数的最大长度

/*开始获取输入*/

        li_inputlen = 0;

        lc_char = getchar();

        while (lc_char !='/n'){

            if(li_inputlen < BUFFERSIZE)

               buffer[li_inputlen++] = lc_char;

           lc_char = getchar();

      }

      

       /*命令超长处理*/

      if (li_inputlen >= BUFFERSIZE){

           printf("Your command is too long! Please re-enter your command!/n");

           li_inputlen = 0;       /*reset */

           continue;

      }

       else

           buffer[li_inputlen] = '/0';/*加上串结束符号,形成字串*/

 

       /*将命令从缓存拷贝到input*/

       input = (char *) malloc(sizeof(char) * (li_inputlen+1));

       strcpy(input,buffer);

说明:在这个过程中,我们主要是接收用户键入的命令,但是在这里我们必须考虑用户键入命令过长超出了BUFFERSIZE的范围。此段程序没有太多需要解释,主要就是对命令的接收,已经对异常输入的处理,引入input是为了下一步的处理需要。

3)解析指令:对用户输入的命令进行解析,解析出命令名

/* 获取命令和参数并保存在arg*/

      for (i = 0,j = 0,k = 0;i <= li_inputlen;i++){

              /*管道和重定向单独处理*/

           if (input[i] == '<' || input[i] == '>' || input[i] =='|'){

                if (input[i] == '|')

                    pipel(input,li_inputlen);

                else

                    redirect(input,li_inputlen);

                is_bj = 1;

                  break;

           }

   /*处理空格、TAB和结束符。不用处理‘/n',因为回车符并没有写入buffer*/

           if (input[i] == ' ' || input[i] =='/t' || input[i] == '/0'){

              if (j == 0) /*这个条件可以略去连在一起的多个空格或者tab*/

                  continue;

              else{

                     buffer[j++] = '/0';

                         arg[k] = (char *) malloc(sizeof(char)*j);

                     /*将指令或参数从缓存拷贝到arg*/

                        strcpy(arg[k],buffer);

                   j = 0;       /*准备取下一个参数*/

                     k++;

              }

           }    

           else{      

              /*如果字串最后是‘&',则置后台运行标记为1*/

              if (input[i] == '&' && input[i+1] == '/0'){

                  is_back = 1;

                  continue;

              }

              buffer[j++] = input[i];

           }    

        }

        

       free(input);/*释放空间*/

说明:上述程序所实现的功能是将存在同一个字符串里的命令和参数分离,在做上述操作之前对输入输出重定向以及管道进行了单独处理

4)寻找命令文件:每个命令的执行都必须依靠对应的可执行文件,路径存放在用户的path环境变量中;

/*如果输入的指令是leave则退出while,即退出程序*/

        if (strcmp(arg[0],"leave") == 0 ){

            printf("bye-bye/n");

            break;

       }

/*如果输入的指令是about则显示作者信息,同时结束本条命令的解析过程*/

    if (strcmp(arg[0]," about") == 0 ){

            printf("copyright by shikeshike13@163.com/n");

            continue;

       }

        

       if (is_bj == 0){   /*非管道、重定向指令*/

/*在使用xxec执行命令的时候,最后的参数必须是NULL指针,

 *所以将最后一个参数置成空值*/

            arg[k] = (char *) 0;

              /*判断指令arg[0]是否存在*/

           if (is_fileexist(arg[0]) == -1 ){

                printf("This command is not found?!/n");

                for(i=0;i<k;i++)

                        free(arg[i]);

                continue;

           }

说明:首先对特殊指令leaveabout的处理,然后寻找可执行文件,如果不存在,释放空间,进入下一条指令的处理流程。

5)执行命令:可通过fork()系统调用创建一个进程来完成执行命令的任务,具体的命令执行用exev()函数。

/* fork a sub-process to run the execution file */

           if ((pid = fork()) ==0)          /*子进程*/

            execv(buffer,arg);

           else  /*父进程*/

            if (is_back == 0)  /*并非后台执行指令*/

                waitpid(pid,&status,0);

                    

                /*释放申请的空间*/

           for (i=0;i<k;i++)

               free(arg[i]);

        }

说明:执行程序的过程相对比较简单,调用fork()创建进程,exev()执行,处理完成之后千万不要忘了释放申请的内存空间。因为本程序为无限循环,如果不释放空间,多次执行后可能会产生大量垃圾,影响系统执行速度。

四.程序中出现的部分函数

1)根据环境变量,查找可执行文件

int is_fileexist(char *comm)

{

    char *path,*p;

    int i;

 

    i = 0;

/*使用getenv函数来获取系统环境变量,用参数PATH表示获取路径*/

    path = getenv("PATH");

    p = path;

    while (*p != '/0'){

              /*路径列表使用‘:’来分隔路径*/

              if (*p != ':')

          buffer[i++] = *p;

       else{

          buffer[i++] = '/';

          buffer[i] = '/0';

/*将指令和路径合成,形成pathname,并使用access函数来判断该文件是否存在*/

          strcat(buffer,comm);

               

          if (access(buffer,F_OK) == 0)  /*文件被找到*/

                     return 0;

          else    

                     /*继续寻找其它路径*/

                      i = 0;

       }      

       p++;

    }

/*搜索完所有路径,依然没有找到则返回-1*/

    return -1;

}

2)输入输出重定向的处理

/*感谢Liecs以及wKernel在这个函数上给我的提示*/

int redirect(char *in,int len)

{

    char *argv[30],*filename[2];

    pid_t pid;

    int i,j,k,fd_in,fd_out,is_in = -1,is_out = -1,num = 0;

    int is_back = 0,status=0;

 

/*这里是重定向的命令解析过程,其中filename用于存放重定向文件,

 *is_in, is_out分别是输入重定向标记和输出重定向标记*/

    for (i = 0,j = 0,k = 0;i <= len;i++){

        if (in[i]==' '||in[i]=='/t'||in[i]=='/0'||in[i] =='<'||in[i]=='>'){

            if (in[i] == '>' || in[i] == '<'){

/*重定向指令最多'<''>'各出现一次,因此num最大为2

 *否则认为命令输入错误*/

                if (num < 3){

                   num ++;

                   if (in[i] == '<')

                       is_in = num - 1;

                   else

                       is_out = num - 1;

                           

                 /*处理命令和重定向符号相连的情况,比如ls>a*/

                  if (j > 0 && num == 1)  {

                      buffer[j++] = '/0';

                      argv[k] = (char *) malloc(sizeof(char)*j);

                      strcpy(argv[k],buffer);

                      k++;

                      j = 0;

                    }

                }

               else{

                   printf("The format is error!/n");

                   return -1;

               }

            }    

           if (j == 0)

               continue;

           else{

              buffer[j++] = '/0';

              /*尚未遇到重定向符号,字符串是命令或参数*/

               if (num == 0){

                  argv[k] = (char *) malloc(sizeof(char)*j);

                  strcpy(argv[k],buffer);

                  k++;

              }

              /*是重定向后符号的字符串,是文件名*/

              else{

                  filename[status] = (char *) malloc(sizeof(char)*j);

                  strcpy(filename[status++],buffer);

              }

              j = 0;       /*initate*/

            }

        }

        else{      

           if (in[i] == '&' && in[i+1] == '/0'){

               is_back = 1;

               continue;

           }

           buffer[j++] = in[i];

        }    

    }

 

    argv[k] = (char *) 0;

 

    if (is_fileexist(argv[0]) == -1 ){

        printf("This command is not founded!/n");

        for(i=0;i<k;i++)

            free(argv[i]);

        return 0;

    }

 

    if ((pid = fork()) ==0){

              /*存在输出重定向*/

        if (is_out != -1)

            if((fd_out=open(filename[is_out],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR))==-1){

                printf("Open out %s Error/n",filename[is_out]);

                return -1;

            }

 

              /*存在输入重定向*/

        if (is_in != -1)

            if((fd_in=open(filename[is_in],O_RDONLY,S_IRUSR|S_IWUSR))==-1){

                printf("Open in %s Error/n",filename[is_out]);

                return -1;

            }

 

        if (is_out != -1)

               /*使用dup2函数将标准输出重定向到fd_out上,dup2(int oldfd,int newfd)实现的

       *是把oldfd所指的文件描述符复制到newfd。若newfd为一已打开的文件描述词,

         *newfd所指的文件会先被关闭,dup2复制的文件描述词与原来的文件描述词

         *共享各种文件状态*/

            if(dup2(fd_out,STDOUT_FILENO)==-1){

                printf("Redirect Standard Out Error/n");

                exit(1);

            }

 

           if (is_in != -1)

             if(dup2(fd_in,STDIN_FILENO)==-1){

                printf("Redirect Standard Out Error/n");

                exit(1);

             }

        execv(buffer,argv);

    }

    else

        if (is_back == 0)  /*run on the TOP*/

            waitpid(pid,&status,0);

 

    for (i=0;i<k;i++)

        free(argv[i]);

 

    if (is_in != -1){

        free(filename[is_in]);

        close(fd_in);

    }

    if (is_out != -1){

        free(filename[is_out]);

        close(fd_out);

    }

    return 0;

}

(3) 管道的命令解析过程

int pipel(char *input,int len)

{

  char *argv[2][30];

  int i,j,k,count,is_back = 0;

  int li_comm = 0,fd[2],fpip[2];

  char lc_char,lc_end[1];

  pid_t child1,child2;

 

/*管道的命令解析过程*/

  for (i = 0,j = 0,k = 0;i <= len;i++){

    if (input[i]== ' ' || input[i] == '/t' || input[i] == '/0' || input[i] == '|'){

      if (input[i] == '|' ) /*管道符号*/

      {

        if (j > 0)

        {

          buffer[j++] = '/0';

         /*因为管道连接的是两个指令,所以用二维数组指针来存放命令和参数,

          *li_comm是表示第几个指令*/

          argv[li_comm][k] = (char *) malloc(sizeof(char)*j);

          strcpy(argv[li_comm][k++],buffer);

        }

        argv[li_comm][k++] = (char *) 0;

        /*遇到管道符,第一个指令完毕,开始准备接受第二个指令*/

        li_comm++;

        count = k;

        k=0;j=0;

      }

      if (j == 0)

        continue;

      else

      {

        buffer[j++] = '/0';

        argv[li_comm][k] = (char *) malloc(sizeof(char)*j);

        strcpy(argv[li_comm][k],buffer);

        k++;

      }

      j = 0;   /*initate*/

    }

    else{      

      if (input[i] == '&' && input[i+1] == '/0'){

        is_back = 1;

        continue;

      }

      buffer[j++] = input[i];

    }

  }

  argv[li_comm][k++] = (char *) 0;

 

  if (is_fileexist(argv[0][0]) == -1 ){

    printf("This first command is not found!/n");

    for(i=0;i<count;i++)

      free(argv[0][i]);

    return 0;

  }

/*指令解析结束*/

 

/*建立管道*/

  if (pipe(fd) == -1 ){

    printf("open pipe error!/n");

    return -1;

  }

 

/*创建第一个子进程执行管道符前的指令,并将输出写到管道*/

  if ((child1 = fork()) ==0){

/*关闭读端*/

    close(fd[0]);

    if (fd[1] != STDOUT_FILENO){

   /*将标准输出重定向到管道的写入端,这样该子进程的输出就写入了管道*/

      if (dup2(fd[1],STDOUT_FILENO) == -1){

        printf("Redirect Standard Out Error/n");

        return -1;

      }

   /*关闭写入端*/

      close(fd[1]);

    }

    execv(buffer,argv[0]);

  }

  else{           /*父进程*/

/*先要等待写入管道的进程结束*/

    waitpid(child1,&li_comm,0);

/*然后我们必须写入一个结束标记,告诉读管道进程数据到这里就完了*/

    lc_end[0] = 0x1a;

    write(fd[1],lc_end,1);

    close(fd[1]);

 

    if (is_fileexist(argv[1][0]) == -1 ){

      printf("This command is not founded!/n");

      for(i=0;i<k;i++)

      free(argv[1][i]);

      return 0;

    }

  

/*创建第二个进程执行管道符后的指令,并从管道读输入流 */

    if ((child2 = fork()) == 0){

      if (fd[0] != STDIN_FILENO){

   /*将标准输入重定向到管道读入端*/

        if(dup2(fd[0],STDIN_FILENO) == -1){

          printf("Redirect Standard In Error!/n");

          return -1;

        }

        close(fd[0]);

      }

      execv(buffer,argv[1]);

    }

    else     /*父进程*/

      if (is_back == 0)

        waitpid(child2,NULL,0);

  }

  for (i=0;i<count;i++)

    free(argv[0][i]);

  for (i=0;i<k;i++)

    free(argv[1][i]);

  return 0;

}

 

 

六.运行界面以及结果分析

lsmkdirvi命令的使用

 

 

 

 

 

 

用虚拟shell终端打开vi编辑器

 

mkdirrmdirrmfindleave命令的使用截图