【Linux】——简易版SHELL实现

一、前言
在前面的几篇文章中,我们已经讲解了进程的几乎全部内容了,关于一些细节部分博主会在后面的文章中再为大家继续介绍。今天,我们就利用之前所学,来实现一个简易版的SHELL,希望大家能够从中有所收获,如有不足,欢迎指出!!!

二、SHELL实现
1.获取命令行,实现交互
实现我们SHELL的第一步就是能够从键盘上获取用户的输入,即命令行。

设置打印格式
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"

int quit=0;
char commandline[LINE_SIZE];
char pwd[LINE_SIZE];

//获取用户名
const char *getusername()
{
return getenv("USER");
}

//获取主机名
const char *gethostname()
{
return getenv("HOSTNAME");
}

//获取当前进程所处路径
void getpwd()
{
getcwd(pwd,sizeof(pwd));
}

//设置打印格式
printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);

获取键盘输入
在获取键盘输入的函数有很多,我们这里仅仅以fget()函数为例,除此之外我们还需要定义一个字符数组来存储fget读到的命令行参数,这里有个要注意的细节就是我们要将读到字符串的'\n'换成'\0',因为fget默认是以换行作为分隔符,但是因此读到的字符串会包括换行符,但这个并不是我们的命令输入,所以要替换成'\0'

#define LINE_SIZE 1024

char commandline[LINE_SIZE];

void interact(char* cline,int size)
{
getpwd();
printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);
char *s=fgets(cline,size,stdin);
assert(s);
(void)s;

cline[strlen(cline)-1]='\0';
}
到这里为止,对于命令行参数的获取就完成了,我们可以加上一行打印来验证格式和读取是否正确,由于我们的SHELL进程是一直读取用户的输入的,因此我们设置为循环的形式

int quit=0;

void interact(char* cline,int size)
{
getpwd();
printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);
char *s=fgets(cline,size,stdin);
assert(s);
(void)s;

cline[strlen(cline)-1]='\0';
if(s) printf("echo : %s\n",cline);

}

int main()
{
while(!quit)
{
//1.交互问题,获取命令行
interact(commandline, sizeof(commandline));
}
return 0;
}

最后效果如下:

 

2.分隔子串,解析命令行
当我们获取完命令行后,第二步就是将获取的命令行进行解析,即对其进行分割成指令和若干个选项的形式

这里代码要注意的是利用strtok()函数来根据DELIM分割符来分割字符串的时候,如果是连续分割,那么除了第一次分割要传字符串外,后面的要传NULL

int splitstring(char cline[], char* _argv[])
{
int i=0;
_argv[i++] =strtok(cline,DELIM);
while(_argv[i++] = strtok(NULL, DELIM));
return i-1;
}

int main()
{
while(!quit)
{
//1.交互问题,获取命令行
interact(commandline, sizeof(commandline));
//2.分隔子串,解析命令行
int argc = splitstring(commandline, argv);
if(argc == 0) continue;
}
return 0;
}

3.指令的判断
当我们解析完命令行后,就能够得知用户输入的是什么指令,从而对指令进行判断其为内键命令还是普通命令。对于前者,无需创建子进程,而后者则需要创建子进程。

在本文我们对常见的内键命令,如cd,echo,export这三个来进行判断,读者也可自行实现其余的内键命令

这里有一个要注意的细节点,就是当我们往进程导入环境变量的时候,strcpy只是导入到我们自己的环境变量表,还需要通过putenv()才能写到进程的环境变量表中,被子进程所继承。

int buildCommand(char *_argv[], int _argc)
66 {
67 if(_argc == 2 && strcmp(_argv[0], "cd") ==0 )
68 {
69 chdir(argv[1]);
70 getpwd();
71 sprintf(getenv("PWD"),"%s",pwd);
72 return 1;
73 }
74 else if(_argc == 2 && strcmp(_argv[0], "export") ==0 )
75 {
76 strcpy(myenv, _argv[1]);
77 putenv(myenv);
78 return 1;
79 }
80 else if(_argc == 2 && strcmp(_argv[0], "echo") ==0 )
81 {
82 if(strcmp(_argv[1], "$?") == 0)
83 {
84 printf("%d\n",lastcode);
85 lastcode=0;
86 }
87 else if(*_argv[1]== '$')
88 {
89 char* val = getenv(_argv[1]+1);
90 if(val) printf("%s\n",val);
91
92
93 }
94 else
95 {
96 printf("%s\n",_argv[1]);
97 }
98 return 1;
99 }

103 int main()
104 {
105 while(!quit)
106 {
107 //1.交互问题,获取命令行
108 interact(commandline, sizeof(commandline));
109 //2.分隔子串,解析命令行
110 int argc = splitstring(commandline, argv);
111 if(argc == 0) continue;
112 //3.指令的判断
113 //内键命令,本质就是一个shell内部的一个函数
114 int n=buildCommand(argv,argc);
115
116 }
117
118
119
120 return 0;
121 }

代码效果如下:

4.普通命令的执行
当我们输入的执行不再是内键命令的时候,对于普通命令我们就需要创建一个子进程来执行相应的指令

110 void NormalExcute(char *_argv[])
111 {
112 pid_t id=fork();
113 if(id < 0)
114 {
115 perror("fork");
116 return;
117 }
118 else if(id == 0)
119 {
120 //让子进程执行命令
121 execvp(_argv[0],_argv);
122 exit(EXIT_CODE);
123 }
124 else{
125 int status = 0;
126 pid_t rid =waitpid(id, &status, 0);
127 if(rid == id)
128 {
129 lastcode = WEXITSTATUS(status);
130 }
131 }
132 }

int main()
135 {
136 while(!quit)
137 {
138 //1.交互问题,获取命令行
139 interact(commandline, sizeof(commandline));
140 //2.分隔子串,解析命令行
141 int argc = splitstring(commandline, argv);
142 if(argc == 0) continue;
143 //3.指令的判断
144 //内键命令,本质就是一个shell内部的一个函数
145 int n=buildCommand(argv,argc);
146 //4.普通命令的执行
147 if(!n) NormalExcute(argv);
148 }
149
150 return 0;
151 }

代码效果如下:

 

三、全部代码
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <assert.h>
5 #include <unistd.h>
6 #include <stdlib.h>
7 #include <sys/types.h>
8 #include <sys/wait.h>
9
10
11 #define LEFT "["
12 #define RIGHT "]"
13 #define LABLE "#"
14 #define DELIM " \t"
15 #define LINE_SIZE 1024
16 #define ARGC_SIZE 32
17 #define EXIT_CODE 44
18
19 int lastcode = 0;
20 int quit=0;
21 char commandline[LINE_SIZE];
22 char pwd[LINE_SIZE];
23 char *argv[ARGC_SIZE];
24
25 //自定义环境变量表
26 char myenv[LINE_SIZE];
27 //自定义本地变量表
28
29 const char *getusername()
30 {
31 return getenv("USER");
32 }
33
34 const char *gethostname()
35 {
36 return getenv("HOSTNAME");
37 }
38
39 void getpwd()
40 {
41 getcwd(pwd,sizeof(pwd));
42 }
43
44
45 void interact(char* cline,int size)
46 {
47 getpwd();
48 printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);
49 char *s=fgets(cline,size,stdin);
50 assert(s);
51 (void)s;
52
53 cline[strlen(cline)-1]='\0';
54 //if(s) printf("echo : %s\n",cline);
55 }
56
57 int splitstring(char cline[], char* _argv[])
58 {
59 int i=0;
60 _argv[i++] =strtok(cline,DELIM);
61 while(_argv[i++] = strtok(NULL, DELIM));
62 return i-1;
63 }
64
65
66 int buildCommand(char *_argv[], int _argc)
67 {
68 if(_argc == 2 && strcmp(_argv[0], "cd") ==0 )
69 {
70 chdir(argv[1]);
71 getpwd();
72 sprintf(getenv("PWD"),"%s",pwd);
73 return 1;
74 }
75 else if(_argc == 2 && strcmp(_argv[0], "export") ==0 )
76 {
77 strcpy(myenv, _argv[1]);
78 putenv(myenv);
79 return 1;
80 }
81 else if(_argc == 2 && strcmp(_argv[0], "echo") ==0 )
82 {
83 if(strcmp(_argv[1], "$?") == 0)
84 {
85 printf("%d\n",lastcode);
86 lastcode=0;
87 }
88 else if(*_argv[1]== '$')
89 {
90 char* val = getenv(_argv[1]+1);
91 if(val) printf("%s\n",val);
92
93
94 }
95 else
96 {
97 printf("%s\n",_argv[1]);
98 }
99 return 1;
100 }
101 //特殊处理一下ls
102 if(strcmp(_argv[0], "ls") ==0)
103 {
104 _argv[_argc++] = "--color";
105 _argv[_argc] = NULL;
106 }
107 return 0;
108 }
109
110 void NormalExcute(char *_argv[])
111 {
112 pid_t id=fork();
113 if(id < 0)
114 {
115 perror("fork");
116 return;
117 }
118 else if(id == 0)
119 {
120 //让子进程执行命令
121 execvp(_argv[0],_argv);
122 exit(EXIT_CODE);
123 }
124 else{
125 int status = 0;
126 pid_t rid =waitpid(id, &status, 0);
127 if(rid == id)
128 {
129 lastcode = WEXITSTATUS(status);
130 }
131 }
132 }
133
134 int main()
135 {
136 while(!quit)
137 {
138 //1.交互问题,获取命令行
139 interact(commandline, sizeof(commandline));
140 //2.分隔子串,解析命令行
141 int argc = splitstring(commandline, argv);
142 if(argc == 0) continue;
143 //3.指令的判断
144 //内键命令,本质就是一个shell内部的一个函数
145 int n=buildCommand(argv,argc);
146 //4.普通命令的执行
147 if(!n) NormalExcute(argv);
148 }
149
150 return 0;
151 }
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_73953114/article/details/145381302

阅读剩余
THE END