模拟实现shell

      参考网址: http://blog.chinaunix.net/uid-8996150-id-2011653.html
      大家每天都在用shell,每天都用它执行命令,有没有想过shell是怎么实现的呢?今天我们就来模拟实现以下myshell。
     我们可以先回忆一下我们用shell的时候分为了几个过程:
     1、输出提示符,显示[用户名@主机名 当前所在目录名]$(#代表超级用户,$代表普通用户)
     2、输入命令(特殊命令  cd 和exit特殊处理)
     3、命令分割
     4、用fork创建新进程
     5、调用exec函数,同时在这个时候才去完成了命令的合法检测,如果execv里传过来的实参,execv找不到,则报错,调用命令失败。
     那我们函数的大体框架就是:
//全局变量
char oldpwd[128]={0};   //是为了实现 '-'进入上一次操作的目录,需要记录,每次跳转页需要更新
       char *Argv[10]={0};       //将命令分割之后,Argv[0]是用户输入命令路径 ,后面的是参数,分割存储起来为调
//用execv传更多参数信息,execv会对Argv中的每个成员进行遍历,尝试匹配每一个成员,也就完成了参数实现
     int main()
{
     //打印提示信息
      while(1)                                      //我们使用shell的时候它这个是一直会显示,除非我们退出bash
    {
          Print_Flag();                           //打印提示信息
          char cmd[128]={0};               //保存输入的命令
                fgets(cmd,128,stdin);            //获取用户输入的命令
                cmd[strlen(cmd)-1]=0;         //fget函数会获得最后一个输入'\n',处理掉最后一个'\n'
         if(strlen(cmd) == 0)              //如果什么都没输显示提示符,再来一遍这个过程
               {
                      continue;
               }
    }

    
//如果输入的是cd的话,需要特殊处理,因为它就不是在/mabash/bin调用可执行文件这么简单,它得切换   //到其他的目录下,这个需要系统提供的函数支持
              if(strncmp(cmd,"cd",2) == 0)    
           {
                     My_Cd(cmd);
                     continue;
          }

//exit也需要特殊处理,我们在bash中调用exit其实就是退出bash,同样如果我们是想退出mybash,其实   
//就是想退出这个子进程
           if(strncmp(cmd,"exit",4) == 0) 
         {
                    exit(0);
         }
   

//我们需要把命令切割一下,分离 命令和参数,才能调用正确的可执行文件
  Cut_Cmd(cmd);
  

//创建新的进程
          pid_t n=fork();
          assert(n!=-1);
          if(n == 0)
        {
            char path[128]="/home/ningcan/project/myshell/mybin";
            if(strstr(Argv[0],"/")!= NULL)   //如果它带有‘/’,认为它是绝对路径,就直接用这个路径
           {
                   memset(path,0,128);
                   strcpy(path,Argv[0]);
                   }
          else                                      //如果没有,说明用户给的是相对路径,我们把原来写好的绝对路径连接上
          {
                  strcat(path,Argv[0]);
          }
   
                 execv(path,Argv);
                 perror("exec :");
                 exit(0);
          }
          else
         {
                 wait(NULL);
         }
        }

下面我们来说说各个模块的具体实现:
打印提示信息
void Printf_Flag()                   //打印提示信息
{
        uid_t uid=getuid();               // uid_t,它定义在头文件sys/types.h中。它通常是一个小整数
 char flag='$';
        if(uid == 0)                         //如果是超级用户标识为#,0代表超级用户
           {
              flag='#';
        }
    
       struct passwd *pw=getpwuid(uid);    //函数原型:struct passwd *pw=getpwuid(uid_t uid);
 
      assert(pw != NULL); 


struct utsname hostname;
       uname(&hostname);       //这里需要我们自己定义一个结构体,然后通过传指针解引用,将值带出来
char hname[128]={0};
       strcpy(hname,hostname.nodename);
       int i=0;
       while(hname[i] != '.')  i++; 
       hname[i]='\0';


//这里是为了获得当前所在目录名
 char path[128]={0};
       getcwd(path,127);//char *getcwd(char *buf, size_t size);   //注释3
       char *p=&path[strlen(path)-1];  //将指针p指向path的最后一个元素

//为了解决在家目录的时候系统用~代替目录名的情况 , pw_dir就是passwd中提供的家目录信息
 if(strcmp(path,pw->pw_dir) == 0)          
 {
  p="~";
 }
//解决在根目录下我们普通的处理会忽视掉最右边的'/'的情况
 else if(strcmp(path,"/") == 0)
 {
  p=path;
 }
//普通情况的处理
 else
 {
  while(*p != '/')
  {
   p--;
  }
  ++p;          //我们只需要显示名字,不要最后的'/',所以就把p++
 }
 printf("mybash[%s@%s %s]%c ",pw->pw_name,hname,p,flag);
 fflush(stdout);
}

注释1:还有一个是getunam函数,和getpwuid类似,getpwuid可以传递 一个 uid来在/etc/passwd中搜索,我没有看过getpwuid怎样实现的,但是我猜测可能/etc/passwd 是通过链表或者数组存储, 然后链表里的数据成员或者数组里的数据成员都是一个struct passwd类型的结构体。如果是链表,则通过逐一搜索每个节点中的UID看是否匹配,这样搜索的时间复杂度得到到O(n),但是插入删除新用户信息快。如果匹配成功就将这一条记录的信息填入创建的静态局部struct passwd类型的变量。
      这里为什么是静态局部变量?
      因为我们要返回一个一个passwd 类型的指针,那我们如果返回的是局部变量,如果大家看过一些汇编代码,应该知道局部变量在函数栈帧中开辟,那我们返回的时候将指向函数栈帧中局部变量的地址放到寄存器,让寄存器带出临时变量的地址,一退出getpwuid这个函数,栈帧被销毁,寄存器带出来的临时变量指向的那片区域就已经不属于我们在getpwuid中定义的临时变量了,一旦有新的数据需要入栈,我们原来指向的那片区域内容被覆盖,那就会发生错误。因为静态变量有和源程序一样的生命周期,所以当getpwuid返回的时候,指针指向的那片区域还在,还可以访问,所以不会有什么问题。
       还有一个问题是,这些都是建立在我猜测它是创建了一个局部静态变量的情况,也有可能是将指针直接指向系统中备份那份配置文件,设置为只读,但是我觉得这样可能会有危险,而且灵活性差,所以我更加倾向于第一种猜测,如果大家知道可以给我纠正哦!不胜感激! getuname也是类似的,只是根据名字搜索。 

注释2: struct utsname
    {
   
    char sysname[_UTSNAME_SYSNAME_LENGTH]; //当前操作系统名
   
    char nodename[_UTSNAME_NODENAME_LENGTH]; //网络上的名称
   
    char release[_UTSNAME_RELEASE_LENGTH]; //当前发布级别
   
    char version[_UTSNAME_VERSION_LENGTH]; //当前发布版本
   
    char machine[_UTSNAME_MACHINE_LENGTH]; //当前硬件体系类型
#if _UTSNAME_DOMAIN_LENGTH - 0
   
# ifdef __USE_GNU
    char domainname[_UTSNAME_DOMAIN_LENGTH]; //当前域名
# else
    char __domainname[_UTSNAME_DOMAIN_LENGTH];
# endif
#endif
};
我们这里用到了它的 nodename的成员信息。
注释3:如果大家对getcwd的实现感兴趣,大家可以进入这个网址看一下。                      
                                http: //blog.youkuaiyun.com/baidu_35085676/articl e/details/52002579


//分割命令
void Cut_Cmd(char *cmd)
{
 
 int i=0;
 memset(Argv,0,sizeof(Argv));   //初始化,防止上一次参数还在,影响这一次的结果
 char *p=strtok(cmd," ");          
 while(p != NULL)
 {
  Argv[i++]=p;
  p=strtok(NULL ," ");  //详细说明可以参考http://blog.youkuaiyun.com/liuintermilan/article/details/6280816
 }
}

//处理特殊命令cd
void My_Cd(char *cmd)
{
 //cd path
// cd ..    上一级目录
// cd ~    家目录
// cd -     上一次的目录 
 char nowpath[128]={0};          //记录现在的地址,一旦切入新的目录成功,现在的路径就是上一次的路径
 getcwd(nowpath,127);            //保存现在的路径

 char *p=cmd;
 while(*p != 0)
 {
  if(*p==' ' && *(p+1)!=' ')    这个就算cd 后面有很多空然后接参数或者直接没有参数都没有问题
  {
   p++;
   break;
  }
  p++;
 }
 if(strlen(p) == 0 || strcmp(p,"~")== 0)       //cd  后面没有参数或者是跟着~ ,进入家目录
 {
  struct passwd *pw=getpwuid(getuid());
  assert(pw != NULL);
  if(-1 == chdir(pw->pw_dir))                       //切换家目录失败就退出,pw_dir 进入家目录
  {
   perror("cd ::");
   return;
  } 
 }
 else if(strcmp(p ,"-") == 0)                    //cd  后面加- ,表示进入上一次目录
 {
  if(strlen(oldpwd)==0)                         //表示还没有上一次路径,这是第一个路径
  {
   printf("OLDPWD not set\n");
   return ;
  }                                                         
  else
  {
   if(-1 == chdir(oldpwd))                    //切换失败就退出
   {
    perror("cd - ::");
    return ;
   }
  }
 }
 else
 {
  if(-1 == chdir(p))                          //不然就切换到cd指定的路径中了       
  {
   perror("cd :: ");
   return ;
  }
 }

 strcpy(oldpwd,nowpath);           
}


上面可能这样混杂着注释,大家不好复制源代码调试,我们再整理一下无注释的源代码
#include
#include
#include
#include //getcwd
#include
#include // getpwuid() 、uid_t
#include//uname
#include// struct passwd
char oldpwd[128]={0};
char *Argv[10]={0};
void Printf_Flag()
{
 int uid=getuid();
 
 char flag='$';
 if(uid == 0)
 {
  flag='#';
 }
 
 struct passwd *pw=getpwuid(uid);
 assert(pw != NULL);
 struct utsname hostname;
 uname(&hostname);
 
 char hname[128]={0};
 strcpy(hname,hostname.nodename);
 int i=0;
 while(hname[i] != '.')  i++;
 hname[i]='\0';
 char path[128]={0};
 getcwd(path,127);
 char *p=&path[strlen(path)-1];
 if(strcmp(path,pw->pw_dir) == 0)
 {
  p="~";
 }
 else if(strcmp(path,"/") == 0)
 {
  p=path;
 }
 else
 {
  while(*p != '/')
  {
   p--;
  }
  ++p;
 }
 printf("mybash[%s@%s %s]%c ",pw->pw_name,hname,p,flag);
 fflush(stdout);
}
void Cut_Cmd(char *cmd)
{
 
 int i=0;
 memset(Argv,0,sizeof(Argv));
 char *p=strtok(cmd," ");
 while(p != NULL)
 {
  Argv[i++]=p;
  p=strtok(NULL ," ");
 }
}
void My_Cd(char *cmd)
{
 
 char nowpath[128]={0};
 getcwd(nowpath,127);
 
 char *p=cmd;
 while(*p != 0)
 {
  if(*p==' ' && *(p+1)!=' ')
  {
   p++;
   break;
  }
  p++;
 }
 if(strlen(p) == 0 || strcmp(p,"~")== 0)
 {
  struct passwd *pw=getpwuid(getuid());
  assert(pw != NULL);
  if(-1 == chdir(pw->pw_dir))
  {
   perror("cd ::");
   return;
  } 
 }
 else if(strcmp(p ,"-") == 0)
 {
  if(strlen(oldpwd)==0)
  {
   printf("OLDPWD not set\n");
   return ;
  }
  else
  {
   if(-1 == chdir(oldpwd))
   {
    perror("cd - ::");
    return ;
   }
  }
 }
 else
 {
  if(-1 == chdir(p))
  {
   perror("cd :: ");
   return ;
  }
 }
 strcpy(oldpwd,nowpath);
}
void main()
{
 while(1)
 {
  Printf_Flag();
  char cmd[128]={0};
  fgets(cmd,128,stdin);
  cmd[strlen(cmd)-1]=0;
  if(strlen(cmd) == 0)
  {
   continue;
  }
 
  if(strncmp(cmd,"cd",2) == 0)
  {
   My_Cd(cmd);
   continue;
  }
  if(strncmp(cmd,"exit",4) == 0)
  {
   exit(0);
  }
  Cut_Cmd(cmd);
  
  pid_t n=fork();
  assert(n!=-1);
  if(n == 0)
  {
 
   char path[128]="/home/ningcan/project/myshell/mybin";
   if(strstr(Argv[0],"/")!= NULL)
   {
    memset(path,0,128);
    strcpy(path,Argv[0]);
   }
   else
   {
    strcat(path,Argv[0]);
   }
   
   execv(path,Argv);
   perror("exec :");
   exit(0);
  }
  else
  {
   wait(NULL);
  }
 }
}






    
      









 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值