Linux下shell的简易实现

一. 总述

Linux操作系统下没有图像化界面,都是通过shell来处理命令和指令,这里使用简单的150行代码实现一个简单的shell,主要作用就是帮助读者理解shell的运行原理。

二.知识储备

Linux下的知识主要是进程创建,还有文件系统,主要涉及到文件的打开,读写追加等基本的操作,还有文件描述符表,总之就是对文件系统和进程要有一定的知识储备,还有进程替换,当然还有一些API接口,比如说dup2,chdir,还有strtok,fgets等等。

三.思路分析

整体思路就是父进程充当shell的角色,从stdin读取,然后使用strtok分割,然后使用进程替换,让子进程去执行对应的代码。

四.代码实现

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>
#define NUM 1024
#define OPT_NUM 64
#define NONE_REDIR  0          //默认输出重定向为0
#define INPUT_REDIR 1   
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
void trimSpace(char* start)    //消除空格,文件重定向的时候
{
  while(isspace(*start))
  {
    start++;
  }
}
int redirType=NONE_REDIR;
char lineCommand[NUM];
char *myargv[OPT_NUM];          //定义了一个指针数组
int lastCode=0;
int lastSig=0;
char* redirFile=NULL;//设重定向的文件名称
//实现命令行解释器

void commandCheck(char*commands)
{
 assert(commands);
 char*start=commands;
 char*end=commands+strlen(commands);
 while(start<end) 
 {
   if(*start=='>')
   {
      *start='\0';
      start++;
      if(*start=='>')
      {
        //"ls -a >> file.log"
        redirType=APPEND_REDIR; 
        start++;
      }
      else
      {
        //"ls -a >    file.log"
        redirType=OUTPUT_REDIR;
      }
      trimSpace(start);
      redirFile=start;
      break;
   }
   else if(*start=='<')
   {
      //"cat < file.txt"
      *start='\0';
      start++;
      trimSpace(start);
      //填写重定向信息
      redirType=INPUT_REDIR;
      redirFile=start;
      break;
   }
   else
   {
     start++; //指向下一个位置
   }
 }
}
int main()
{
  while(1)
  {
     redirType=0;
     redirFile=NULL;//这个地方要做一个初始化,因为是全局变量,父进程就是那个死循环是不会改变的。
     //输出提示符
     printf("用户名@主机名 当前路径#");
     fflush(stdout);
     //用户输入,输入的时候,会输入\n,键盘最后回车就是\n
     char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin); //sizeof会计算所有占用的空间
     assert(s!=NULL);
     //清楚最后一个\n
     lineCommand[strlen(lineCommand)-1]=0;//消除最后一个'\n
     commandCheck(lineCommand);
     //无论处理什么,都是要进行字符串切割
     myargv[0]=strtok(lineCommand," ");
     // 如果没有字串了strtok->NULL.恰好myargv要求最后一个得是NULL
     int i=1;
     if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)
     {
        myargv[i++]=(char*)"--color=auto";   //注意这个地方加上强制类型转换
     }
     while(myargv[i++]=strtok(NULL," "));
     myargv[i]=NULL;
     if(myargv[0]!=NULL &&strcmp(myargv[0],"cd")==0)
     {
      //后面跟的是路径,所以后面肯定不为NULL
      if(myargv[1]!=NULL)
      {
        //如果是cd命令,不需要创建子进程,让shell自己执行对应的接口
        chdir(myargv[1]);//注意这个时候是父进程调用的,这个就是改变父进程的工作目录,这个是一个系统调用
        continue;
      }
     }
     if(myargv[0]!=NULL&&myargv[1]!=NULL&&strcmp(myargv[0],"echo")==0)
     {
      if(strcmp(myargv[1],"$?")==0)
      {
        printf("%d,%d\n",lastCode,lastSig);
      }
      else
      {
        printf("%s\n",myargv[1]);
      }
      continue;
     }
     //测试时候成功,条件编译
# ifdef DEBUG
     for(int i=0;myargv[i];i++)
     {
       printf("myargv[%d]:%s\n",i,myargv[i]);
     }
# endif
     //执行命令,都是使用子进程去执行命令的
     pid_t id=fork();
     assert(id!=-1);
     if(id==0)
     {
        //因为命令是子进程执行的,真正重定向的工作一定是子进程来完成
        //然是如何重定向,由父进程提供
        switch(redirType)
        {
          case NONE_REDIR:
            break;
          case INPUT_REDIR:
            {
              int fd=open(redirFile,O_RDONLY);   
              if(fd<0)
              {
                perror("open");
                exit(2);//终止子进程
              }
              dup2(fd,0);  //这个地方是写,重定向到读端
            }
            break;
          case OUTPUT_REDIR:
          case APPEND_REDIR:
            {
              int flags=O_WRONLY|O_CREAT;
              if(redirType==APPEND_REDIR)
              {
                flags |=O_APPEND;
              }
              else
              {
                flags|=O_TRUNC;
              }
              int fd=open(redirFile,flags,0666);
              if(fd<0)
              {
                perror("open");
                exit(2);//直接终止进程
              }
              dup2(fd,1);
            }
            break;
          default:
            break;
        }
        execvp(myargv[0],myargv);  //以数组的形式和使用指定的环境变量的方式
        exit(-1);
     }
     int status=0;
     pid_t ret=waitpid(id,&status,0);
     assert(ret>0);
     lastCode=((status>>8)&0xFF);
     lastSig=(status&0x7F);
  }  
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值