自我名言:只有努力,才能追逐梦想,只有努力,才不会欺骗自己。
喜欢的点赞,收藏,关注一下把!
如果发现内容有不对的地方欢迎在评论区批评指正,这是对我最大的鼓励!!!
前面学过了进程创建/终止/等待/替换。现在根据所学的内容实现一个简易版的shell。
//头文件
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 #include<string.h>
1.提示符
这是我们在命令行上首先出现的内容。
注意在命令行输入指令,是在同一行的。
int main()
{
//这里也可以使用环境变量来获取这些内容
printf("用户名@主机名 当前路径#");
//这里printf后面不能带\n,但是要把内容打印到显示器,因此必须刷新缓冲区。
fflush(stdout);
return 0;
}
2.命令行参数
前面说过ls -a -l是命令行参数,是一个整体的字符串,分割层一个个字符串传到main函数中。
因此,我们需要把输入的字符串也要分割成一个个字符串。
2.1输入指令
7 #define NUM 1024
8 #define OPT_NUM 64
9 //全局变量
10 char lineCommand[NUM];//存放输入的指令字符串
11 char* myargv[OPT_NUM];//指针数组,存放分割的一个个字符串
12
这里使用fgets函数,把输入流放在指定数组。
9 #define NUM 1024
10 #define OPT_NUM 64
11
12 char lineCommand[NUM];
13 char* myargv[OPT_NUM];//指针数组,存放分割的一个个字符串
14
15 int main()
16 {
17 //这里也可以使用环境变量来获取这些内容
18 printf("用户名@主机名 当前路径# ");
19 fflush(stdout);
20 //"ls -a -l"-----> "ls" "-a" "-l"
21 //这里减1,是为了极端情况下放\0
22 char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
23 assert(s);
24 //测试一下
25 printf("test:%s\n",lineCommand);
26
27 return 0;
28 }
发现这里空了一格。这是因为我们输入指令的时候,最后一次肯定敲的是\n,因此这里我们消除一下\n。
9 #define NUM 1024
10 #define OPT_NUM 64
11
12 char lineCommand[NUM];
13 char* myargv[OPT_NUM];//指针数组,存放分割的一个个字符串
14
15 int main()
16 {
17 //这里也可以使用环境变量来获取这些内容
18 printf("用户名@主机名 当前路径# ");
19 fflush(stdout);
20 //"ls -a -l"-----> "ls" "-a" "-l"
21 //这里减1,是为了极端情况下放\0
W> 22 char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
23 assert(s);
24 //清除最后一个\n abc\n
25 lineCommand[strlen(lineCommand)-1]=0;
26 //测试一下
27 printf("test:%s\n",lineCommand);
28
29 return 0;
30 }
2.2分割指令
在C语言的时候,学过一个分割字符串的函数,strtok
28 //这里以空格为分隔符
29 myargv[0]=strtok(lineCommand," ");
30 //分割的是同一个字符串,下一次第一个参数就置为NULL就可以了
31 // myargv[1]=strtok(NULL,"");
32 //这里需要实现循环,注意strtok到字符串结束,会返回NULL,myargv[end]=NULL
33 int i=1;
34 while(myargv[i++]=strtok(NULL," "));
36 //条件编译,测试分割是否成功
37 #ifdef DEBUG
38 for(int i=0;myargv[i];++i)
39 {
40 printf("myargv[%d]:%s\n",i,myargv[i]);
41 }
42 #endif
1 myshell:myshell.c
2 gcc -o $@ $^ -std=c99 -DDEBUG //定义宏 不需要就前面加个#注释掉
3
4 .PHONY:clean
5 clean:
6 rm -f myshell
3.创建子进程执行指令
前面我们学了6个替换函数。这里我们选择execvp最合适。
44 //创建子进程
45 pid_t id = fork();
46 assert(id != -1);
47 if(id == 0)
48 {
49 //子进程
50 execvp(myargv[0],myargv);
51 }
52 //父进程
53 //这里先不关心退出码
54 waitpid(id,NULL,0);
但是这里只能实现一次,因此,需要把整体循环起来。
1#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<assert.h>
5 #include<sys/types.h>
6 #include<sys/wait.h>
7 #include<string.h>
8
9 #define NUM 1024
10 #define OPT_NUM 64
11
12 char lineCommand[NUM];
13 char* myargv[OPT_NUM];//指针数组,存放分割的一个个字符串
14
15 int main()
16 {
17 while(1)
18 {
19 //这里也可以使用环境变量来获取这些内容
20 printf("用户名@主机名 当前路径# ");
21 fflush(stdout);
22 //"ls -a -l"-----> "ls" "-a" "-l"
23 //这里减1,是为了极端情况下放\0
24 char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
25 assert(s);
26 //清除最后一个\n abc\n
27 lineCommand[strlen(lineCommand)-1]=0;
28 //测试一下
29 //printf("test:%s\n",lineCommand);
30 //这里以空格为分隔符
31 myargv[0]=strtok(lineCommand," ");
32 //分割的是同一个字符串,下一次第一个参数就置为NULL就可以了
33 // myargv[1]=strtok(NULL,"");
34 //这里需要实现循环,注意strtok到字符串结束,会返回NULL,myargv[end]=NULL
35 int i=1;
36 while(myargv[i++]=strtok(NULL," "));
37
38 //测试分割是否成功
39#ifdef DEBUG
40 for(int i=0;myargv[i];++i)
41 {
42 printf("myargv[%d]:%s\n",i,myargv[i]);
43 }
44 #endif
45
46 //创建子进程
47 pid_t id = fork();
48 assert(id != -1);
49 if(id == 0)
50 {
51 //子进程
52 execvp(myargv[0],myargv);
53 }
54 //父进程
55 //这里先不关心退出码
56 waitpid(id,NULL,0);
57 }
58 return 0;
59 }
4.三个细节
4.1ls可执行程序问题
自己实现的shell,可执行程序没有颜色,
36 //这里对ls,特殊处理
37 if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
38 {
39 myargv[i++]=(char*)"--color=auto";
40 }
4.2切换路径问题
我们发现,当我切换到上层路径时,发现当前路径没有变。这是为什么呢?
什么是当前路径?
在前面我们学过一个查看进程的命令ls /proc
exe----> 是当前进程执行的是磁盘路径下哪一个程序!
cmd----> 是当前进程的工作目录
什么是当前路径,默认是你在那个路径下把程序跑起来。本质就是当前进程的工作目录。
当前路径知道了,那为什么自己写的shell,cd的时候,路径没有变化呢?
子进程也有自己的工作目录,默认和父进程一样。fork之后,子进程执行cd命令,更改的是子进程的目录!子进程执行完毕之后,被回收了。继续使用的是父进程。然后再创建子进程。默认和父进程工作目录一样,这时执行pwd命令,所以当前目录没有变。
如果就想更改当前工作目录,系统提供chdir函数。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7 //想让工作目录是谁,就把谁的路径传过来
8 chdir("/home/wdl");
9 while(1)
10 {
11 printf("我是一个进程,我的id是:%d\n",getpid());
12 sleep(1);
13 }
14
15 return 0;
16 }
修改一下自己的代码。
//如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
//像这种不需要让我们的子进程来执行,而是让shell自己执行的命令 --- 内建/内置命令
42 if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
43 {
44 if(myargv[1] != NULL)
45 {
46 chdir(myargv[1]);
47 continue;
48 }
49 }
4.3进程退出码
echo $? //记录最近一个程序的进程退出码
具体实现
//两个全局变量记录子进程退出信号,退出码
12 int lastCode=0;
13 int lastsig=0;
//特殊处理
57 if(myargv[0] != NULL && strcmp(myargv[0],"echo") == 0)
58 {
59 if(strcmp(myargv[1],"$?") == 0)
60 {
61 printf("%d,%d\n",lastCode,lastsig);
62 }
63 else
64 {
65 printf("%s\n",myargv[1]);
66 }
67 continue;
68 }
78 //创建子进程
79 pid_t id = fork();
80 assert(id != -1);
81 if(id == 0)
82 {
83 //子进程
84 execvp(myargv[0],myargv);
85 exit(1);
86 }
87 //父进程
88 int status=0;
89 pid_t ret= waitpid(id,NULL,0);
90 assert(ret>0);
//记录退出信号,退出码
91 lastCode=(status>>8)&0xFF;
92 lastsig=status&0x7F;
93 return 0;
5.myshell完整代码
#include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<assert.h>
5 #include<sys/types.h>
6 #include<sys/wait.h>
7 #include<string.h>
8
9 #define NUM 1024
10 #define OPT_NUM 64
11
12 int lastCode=0;
13 int lastsig=0;
14
15 char lineCommand[NUM];
16 char* myargv[OPT_NUM];//指针数组,存放分割的一个个字符串
17
18 int main()
19 {
20 while(1)
21 {
22 //这里也可以使用环境变量来获取这些内容
23 printf("用户名@主机名 当前路径# ");
24 fflush(stdout);
25 //"ls -a -l"-----> "ls" "-a" "-l"
26 //这里减1,是为了极端情况下放\0
W> 27 char* s=fgets(lineCommand,sizeof(lineCommand)-1,stdin);
28 assert(s);
29 //清除最后一个\n abc\n
30 lineCommand[strlen(lineCommand)-1]=0;
31 //测试一下
32 //printf("test:%s\n",lineCommand);
33 //这里以空格为分隔符
34 myargv[0]=strtok(lineCommand," ");
35 //分割的是同一个字符串,下一次第一个参数就置为NULL就可以了
36 // myargv[1]=strtok(NULL,"");
37 //这里需要实现循环,注意strtok到字符串结束,会返回NULL,myargv[end]=NULL
38 int i=1;
39 //这里对ls,特殊处理
40 if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
41 {
42 myargv[i++]=(char*)"--color=auto";
43 }
44
W> 45 while(myargv[i++]=strtok(NULL," "));
46 //如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
47 //像这种不需要让我们的子进程来执行,而是让shell自己执行的命令 --- 内建/内置命令
48 if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
49 {
50 if(myargv[1] != NULL)
51 {
52 chdir(myargv[1]);
53 continue;
54 }
55 }
56
57 if(myargv[0] != NULL && strcmp(myargv[0],"echo") == 0)
58 {
59 if(strcmp(myargv[1],"$?") == 0)
60 {
61 printf("%d,%d\n",lastCode,lastsig);
62 }
63 else
64 {
65 printf("%s\n",myargv[1]);
66 }
67 continue;
68 }
69
70 //测试分割是否成功
71 #ifdef DEBUG
72 for(int i=0;myargv[i];++i)
73 {
74 printf("myargv[%d]:%s\n",i,myargv[i]);
75 }
76 #endif
77
78 //创建子进程
79 pid_t id = fork();
80 assert(id != -1);
81 if(id == 0)
82 {
83 //子进程
84 execvp(myargv[0],myargv);
85 exit(1);
86 }
87 //父进程
88 int status=0;
W> 89 pid_t ret= waitpid(id,NULL,0);
90 assert(ret>0);
91 lastCode=(status>>8)&0xFF;
92 lastsig=status&0x7F;
93 return 0;
94 }
95 }