初识Linux · 自主Shell编写

打印 上一主题 下一主题

主题 529|帖子 529|积分 1587

目录
前言:
1 命令行表明器部门
2 获取用户命令行参数
3 命令行参数进行分割
4 执行命令
5 判断命令是否为内建命令

前言:

本文介绍是自主Shell编写,对于shell,即外壳表明程序,我们目前接触到的命令行表明器,有bash,另有SSH,对于本日模拟实现的Shell编写,我们模拟的是bash,以及需要的预备知识前文已经介绍了,进程的多方面的知识,在自主Shell编写里面比力紧张的是进程程序替换,进程终止,进程等候,进程状态什么的,都是自主Shell编写里面的辅助知识罢了。
那么,话不多说,我们直接进入到Shell编写部门。

1 命令行表明器部门


我们在Centos版本下进行演示,首先,我们平常看到的命令行表明器,出现的都是这个模样,最开始的_lazy是当前的用户名,@后面的VM-12-14-centos代表的是当前主机名称,后面的~代表的我们所处的当前目录,那么我们这里,就应该要复刻一个一样的出来。
那么第一个问题来了,我们从那里获取对应的用户名主机名以及目前的目录呢?
此时,前文引进的环境变量,就应该进场了:



输入了env之后,我们可以在环境变量表里面看到许多对应的环境变量,此中HOSTNAME,PWD,USER分别代表的就是主机名称,当前路径,当前用户名。
那么我们怎样通过获取?我们已知的是有3种方式,一种是environ,一种是命令行参数表,一种是getenv。
我们这里利用getenv,相对于二级指针environ,getenv是我们最常见的选择,那么我们可以:
  1.    11   char* argv[] = {
  2.    12   getenv("HOSTNAME"),
  3.    13   getenv("USER"),
  4.    14   getenv("PWD")
  5.    15   };
复制代码
将获取到的环境变量放在数组argv里面,随即进行打印:
我们直接利用printf打印数组的三个元素,看起来似乎没有问题,由于命令行参数是在后面输入,所以我们不能利用\n作为竣事,并且,这里介绍一个函数,snprintf,我们不妨利用该函数打印,把全部的环境变量放在一个字符串里面,似乎更好控制一点,这里假如有同砚的man手册配置没有齐全的话,可以利用指令:
   sudo yum install man-pages
  


snprintf就是将全部的输出,放到一个字符串里面,此时,我们直接打印该字符即可,所以第一部门的临期间码为:
  1. 34 void OutputBash()  
  2. 35 {  
  3. 36   char line[SIZE];  
  4. 37     
  5. 38   char* username = GetUser();                                                              
  6. 39   char* hostname = Gethost();  
  7. 40   char* cwd = Getcwd();
  8. 41
  9. 42   snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,cwd);
  10. 43   printf("%s",line);
  11. 44   fflush(stdout);
  12. 45
  13. 46   // char* argv[] = {
  14. 47   // getenv("HOSTNAME"),
  15. 48   // getenv("USER"),
  16. 49   // getenv("PWD")
  17. 50   // };
  18. 51   // char* line;
  19. 52   // //printf("[%s@%s %s]>",argv[0],argv[1],argv[2]);
  20. 53   // fflush(stdout);
  21. 54 }
复制代码
  1.   8 #define SIZE 512
  2.   9
  3. 10
  4. 11 char* GetUser()
  5. 12 {
  6. 13   char* user = getenv("USER");
  7. 14   if(user == NULL) return NULL;
  8. 15   return user;
  9. 16
  10. 17 }
  11. 18
  12. 19 char* Gethost()                                                   
  13. 20 {                                                                  
  14. 21   char* host = getenv("HOSTNAME");                                 
  15. 22   if(host == NULL) return NULL;                                    
  16. 23   return host;                                                     
  17. 24 }                                                                  
  18. 25                                                                    
  19. 26 char* Getcwd()                                                     
  20. 27 {                                                                  
  21. 28   char* cwd = getenv("PWD");                                       
  22. 29   if(cwd == NULL) return NULL;                                    
  23. 30   return cwd;  
  24. 31   
  25. 32 }  
复制代码
但是为什么要说这是暂时的呢?由于我们的pwd并不完善:

目前,打印的出来并不是最完善的,较为完善的应该是只打印当前目录。
那么怎样保证修饰一下呢?
我们可以将该字符串进行分割,也就是利用指针,将该指针的指向指到最后一个/指向的地方即可。但是这里不保举利用函数,假如利用的是函数,我们就要利用二级指针,实属贫苦,所以可以利用宏即可:
   #define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
  那么判断的条件就是,只要p碰到了根目录就停下,但是有个缺陷就是:

/照旧存在,那么我们可以这样利用:
    snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd + 1); 
  此时,较为完善的命令行表明器部门就打印出来了:


2 获取用户命令行参数

第一个问题我们解决了,我们如今该获取用户的命令行参数了。
在获取用户命令行参数这里,我们要注意的点是,我们应该利用什么函数来获取?
可不可以利用scanf来获取呢?假如利用scanf,那么ls -l -n -a,能获取到多少呢?
我们知道scanf是通过空格或者换行符来获取的,此时ls -l -n -a,就只能获取到ls,所以我们应该换个函数,这里保举fgets,着实gets也是可以的,但是由于后面有文件的IO利用,所以我们利用fgets作为一个缓冲:
  1. 57 int GetUserCommand(char* usercommand,size_t n)  
  2. 58 {  
  3. 59   char* s = fgets(usercommand,n,stdin);  
  4. 60   if(s == NULL) return -1;  
  5. 61   
  6. 62   return strlen(s);  
  7. 63 }  
复制代码
但是该代码存在一定的缺陷。
在第4部门会有提示。

3 命令行参数进行分割

获取到了对应的命令,那么执行的时候,不能带空格去执行吧?所以我们要利用函数,将命令行参数进行分割,这里利用的函数是C语言的库函数,strtok,相信许多同砚已经忘记了,不急:

第一个参数是分割的字符串,第二个参数是分割符,那么第一次分割之后,将第一个参数置为NULL,就会继续分割,我们要做的,就是将字符串分割之后,放到数组里面,有益于后面的进程替换工作。
这里定义一个全局变量,用于存在分割后的字符串变量:
  1. #define SEP " "   
  2. char* gArgv[SIZE];
复制代码
这里有一个非常微小的地方,假如我们利用单引号的空格,虽然也是空格,但是和strtok就不匹配了,由于这并不是cosnt char* ,这只是一个字符而已。
  1. 70 void SplitCommand(char* usercommand)
  2. 71 {
  3. 72   gArgv[0] = strtok(usercommand,SEP);
  4. 73   int index = 1;
  5. 74   while((gArgv[index++] = strtok(NULL,SEP)));//分割之后函数返回NULL 恰好作为结尾
  6. 75
  7. 76 }
复制代码
此时有个很不错的代码细节,由于函数分割完返回的就是NULL,刚好可以作为数组的竣事标志。

4 执行命令

到如今,我们可以不管三七二十一,直接执行命令了,至少我们如今先不用管命令是不是内建命令,我们就执行几个简单的即可。
那么要执行命令,我们肯定涉及到进程程序替换。由于分割好的命令我们已经放在了全局变量里面,所以我们可以直接创建函数了:
  1. 85 void ExcuteCommand()
  2. 86 {
  3. 87   pid_t id = fork();
  4. 88
  5. 89   if(id < 0) Die();
  6. 90   else if(id == 0)
  7. 91   {
  8. 92     //child
  9. 93     execvp(gArgv[0],gArgv);
  10. 94     exit(1);
  11. 95   }
  12. 96   else
  13. 97   {
  14. 98     //father
  15. 99     int status = 0;
  16. 100     pid_t rid = waitpid(id,&status,0);
  17. 101     if(rid > 0)
  18. 102     {
  19. 103       lastcode = WEXITSTATUS(status);
  20. 104       if(lastcode != 0) printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
  21. 105     }
  22. 106   }
  23. 107
  24. 108
  25. 109 }
复制代码
这些代码都是进程替换的时候介绍过的了,无非是加修饰,让代码更加美观,此时,咱们就可以跑了,但是有同砚仍会发现,不管怎么运行,都是不可以的,由于我们命令行输入的时候,都会主动的输入一个回车,这个回车,导致了我们跑不了,所以我们需要将回车干掉:

宏定义ZERO即可。
此时,我们就可以正常的执行了。

5 判断命令是否为内建命令

那么如今问题来了,假如我们是执行的ehco,cd这种内建命令,即只能父进程来执行的,我们就不能创建子进程了,判断是否为内建命令,条件成立就内建执行即可,并且跳过下一步:
那么判断内建命令的方式也是非常简单粗暴的,strcmp即可:
  1. 110 void Cd()
  2. 111 {
  3. 112     const char *path = gArgv[1];
  4. 113     if(path == NULL) path = Gethost();
  5. 114     // path 一定存在
  6. 115     chdir(path);
  7. 116
  8. 117     // 刷新环境变量
  9. 118     char temp[SIZE*2];
  10. 119     getcwd(temp, sizeof(temp));
  11. 120     snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
  12. 121     putenv(cwd); // OK
  13. 122 }
  14. 123
  15. 124 int IsInorder()
  16. 125 {
  17. 126     int yes = 0;
  18. 127     const char *enter_cmd = gArgv[0];
  19. 128     if(strcmp(enter_cmd, "cd") == 0)
  20. 129     {
  21. 130         yes = 1;
  22. 131         Cd();
  23. 132     }
  24. 133     else if(strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)  
  25. 134     {  
  26. 135         yes = 1;  
  27. 136         printf("%d\n", lastcode);  
  28. 137         lastcode = 0;                                                                              
  29. 138     }  
  30. 139     return yes;  
  31. 140 }
复制代码
这里拿cd举例子,判断cd是内建命令之后,在cd函数实现,由于我们要该目录,所以利用函数chdir,改变当前工作目录,改变了之后,改变环境变量中的PATH即可。此时自主shell编写就差不多了。

感谢阅读!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦应逍遥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表