梦应逍遥 发表于 2024-10-9 04:33:43

初识Linux · 自主Shell编写

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

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

https://i-blog.csdnimg.cn/direct/d2337e110fbc46248dcec52e78956979.png
我们在Centos版本下进行演示,首先,我们平常看到的命令行表明器,出现的都是这个模样,最开始的_lazy是当前的用户名,@后面的VM-12-14-centos代表的是当前主机名称,后面的~代表的我们所处的当前目录,那么我们这里,就应该要复刻一个一样的出来。
那么第一个问题来了,我们从那里获取对应的用户名主机名以及目前的目录呢?
此时,前文引进的环境变量,就应该进场了:
https://i-blog.csdnimg.cn/direct/4971801076974d1883c43c0e607d8869.png
https://i-blog.csdnimg.cn/direct/dca9413612f54889aa863da5d3059639.png
https://i-blog.csdnimg.cn/direct/5528ddbb8741497687cd167602eee088.png
输入了env之后,我们可以在环境变量表里面看到许多对应的环境变量,此中HOSTNAME,PWD,USER分别代表的就是主机名称,当前路径,当前用户名。
那么我们怎样通过获取?我们已知的是有3种方式,一种是environ,一种是命令行参数表,一种是getenv。
我们这里利用getenv,相对于二级指针environ,getenv是我们最常见的选择,那么我们可以:
   11   char* argv[] = {
   12   getenv("HOSTNAME"),
   13   getenv("USER"),
   14   getenv("PWD")
   15   };
将获取到的环境变量放在数组argv里面,随即进行打印:
我们直接利用printf打印数组的三个元素,看起来似乎没有问题,由于命令行参数是在后面输入,所以我们不能利用\n作为竣事,并且,这里介绍一个函数,snprintf,我们不妨利用该函数打印,把全部的环境变量放在一个字符串里面,似乎更好控制一点,这里假如有同砚的man手册配置没有齐全的话,可以利用指令:
   sudo yum install man-pages
https://i-blog.csdnimg.cn/direct/a244fc6efffd4712a3a399ba0d626171.png
https://i-blog.csdnimg.cn/direct/e335159050fa43f198e3caadbd207e4f.png
snprintf就是将全部的输出,放到一个字符串里面,此时,我们直接打印该字符即可,所以第一部门的临期间码为:
34 void OutputBash()
35 {
36   char line;
37   
38   char* username = GetUser();                                                            
39   char* hostname = Gethost();
40   char* cwd = Getcwd();
41
42   snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,cwd);
43   printf("%s",line);
44   fflush(stdout);
45
46   // char* argv[] = {
47   // getenv("HOSTNAME"),
48   // getenv("USER"),
49   // getenv("PWD")
50   // };
51   // char* line;
52   // //printf("[%s@%s %s]>",argv,argv,argv);
53   // fflush(stdout);
54 }
8 #define SIZE 512
9
10
11 char* GetUser()
12 {
13   char* user = getenv("USER");
14   if(user == NULL) return NULL;
15   return user;
16
17 }
18
19 char* Gethost()                                                   
20 {                                                                  
21   char* host = getenv("HOSTNAME");                                 
22   if(host == NULL) return NULL;                                    
23   return host;                                                   
24 }                                                                  
25                                                                  
26 char* Getcwd()                                                   
27 {                                                                  
28   char* cwd = getenv("PWD");                                       
29   if(cwd == NULL) return NULL;                                    
30   return cwd;
31   
32 }
但是为什么要说这是暂时的呢?由于我们的pwd并不完善:
https://i-blog.csdnimg.cn/direct/6f5b78f7a9324b0c8b195ee0b1240505.png
目前,打印的出来并不是最完善的,较为完善的应该是只打印当前目录。
那么怎样保证修饰一下呢?
我们可以将该字符串进行分割,也就是利用指针,将该指针的指向指到最后一个/指向的地方即可。但是这里不保举利用函数,假如利用的是函数,我们就要利用二级指针,实属贫苦,所以可以利用宏即可:
   #define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)
那么判断的条件就是,只要p碰到了根目录就停下,但是有个缺陷就是:
https://i-blog.csdnimg.cn/direct/b033bf71d3434789935c3a2eda4238f3.png
/照旧存在,那么我们可以这样利用:
    snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd + 1); 
此时,较为完善的命令行表明器部门就打印出来了:
https://i-blog.csdnimg.cn/direct/14b66e0eb8c541e18d0b04e66d9d897d.png
2 获取用户命令行参数

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

获取到了对应的命令,那么执行的时候,不能带空格去执行吧?所以我们要利用函数,将命令行参数进行分割,这里利用的函数是C语言的库函数,strtok,相信许多同砚已经忘记了,不急:
https://i-blog.csdnimg.cn/direct/f2ea1418255f409780a9731957254eac.png
第一个参数是分割的字符串,第二个参数是分割符,那么第一次分割之后,将第一个参数置为NULL,就会继续分割,我们要做的,就是将字符串分割之后,放到数组里面,有益于后面的进程替换工作。
这里定义一个全局变量,用于存在分割后的字符串变量:
#define SEP " "   
char* gArgv;
这里有一个非常微小的地方,假如我们利用单引号的空格,虽然也是空格,但是和strtok就不匹配了,由于这并不是cosnt char* ,这只是一个字符而已。
70 void SplitCommand(char* usercommand)
71 {
72   gArgv = strtok(usercommand,SEP);
73   int index = 1;
74   while((gArgv = strtok(NULL,SEP)));//分割之后函数返回NULL 恰好作为结尾
75
76 }
此时有个很不错的代码细节,由于函数分割完返回的就是NULL,刚好可以作为数组的竣事标志。
4 执行命令

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

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 初识Linux · 自主Shell编写