在學習了Linux的進程式控制制之後,學習了fork函數和exec函數族,通過這些個函數可以簡單的實現一份shell,就是實現一份命令行解釋器,當然是簡單版的,實現功能如下 還不能實現正則表達式,要實現這個我當前的代碼根本不能用,要重頭開始改寫。。。 下麵貼代碼 通過gethostname獲取主機名,通 ...
在學習了Linux的進程式控制制之後,學習了fork函數和exec函數族,通過這些個函數可以簡單的實現一份shell,就是實現一份命令行解釋器,當然是簡單版的,實現功能如下
- 能執行普通的命令如ls ,ps ,top等
- 可以實現目錄的跳轉cd命令
- 能執行命令並加上參數如ls-l
- 能執行打開man手冊
- 能識別管道符
還不能實現正則表達式,要實現這個我當前的代碼根本不能用,要重頭開始改寫。。。
下麵貼代碼
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <sys/wait.h>
6 #include <stdlib.h>
7 #include <pwd.h>
8 #include <sys/utsname.h>
9 #include <libgen.h>
10
11
12 void eatblank(char **buf)
13 {
14 while(**buf==' ')
15 {
16 (*buf)++;
17 }
18 }
19
20
21 void GetHostName(char *hostname,int length)
22 {
23 gethostname(hostname,length);
24 char *p=hostname;
25 while(*p!='\0')
26 {
27 if(*p=='.')
28 {
29 *p='\0';
30 }
31 p++;
32 }
33 }
34
35 void Pipe(char **my_argv,char *buf);
36 void BuildCommand(char **my_argv,char *buf)
37 {
38 eatblank(&buf);
39 my_argv[0]=buf;
40 int index=1;
41 char *p=buf;
42 while(*p!='\0')
43 {
44 if(*p==' ')
45 {
46 *p='\0';
47 p++;
48 eatblank(&p) ;
49 if(*p!='|')
50 {
51 my_argv[index++]=p;
52 }
53 continue;
54 }
55 else if(*p=='|')
56 {
57 p++;
58 //p++;
59 my_argv[index]=NULL;
60 Pipe(my_argv,p);
61 }
62 else
63 {
64 p++;
65 }
66 }
67 my_argv[index]=NULL;
68 }
69
70
71
72 void Pipe(char ** my_argv,char *buf)
73 {
74 int fd[2];
75 pipe(fd);
76 pid_t id2=fork();
77 if(id2==0)
78 {
79 close(1);
80 dup(fd[1]);
81 close(fd[1]);
82 close(fd[0]);
83 execvp(my_argv[0],my_argv);
84 }
85 else
86 {
87 waitpid(id2,NULL,0);
88 close(0);
89 dup(fd[0]);
90 close(fd[0]);
91 close(fd[1]);
92 BuildCommand(my_argv,buf);
93 execvp(my_argv[0],my_argv);
94 }
95 //在此處添加exec族函數
96 }
97
98
99 int main()
100 {
101 while(1)
102 {
103 char *my_argv[64];
104 struct passwd *pwd=getpwuid(getuid());
105 char hostname[256]={'\0'};
106 char cwd[256]={'\0'};
107 getcwd(cwd,256);
108 GetHostName(hostname,256);
109 printf("[%s@%s %s]#",pwd->pw_name,hostname,basename(cwd));
110 fflush(stdout);
111 char buf[1024];
112 buf[0]='\0';
113
114 int count=read(0,buf,sizeof(buf));
115 buf[count-1]='\0';
116 my_argv[0]=buf;
117 pid_t id=fork();
118 if(id==0)
119 {
120 //child
121
122 if(strncmp(buf,"cd",2)==0)
123 {
124 exit(1);
125 }
126 BuildCommand(my_argv,buf);
127 execvp(my_argv[0],my_argv);
128 printf("if the process has some problem ,I should run here\n");
129 exit(0);
130 }
131 else
132 {
133 //father
134 int status=0;
135 wait(&status);
136 if(status==256)
137 {
138 my_argv[0]+=3;
139 chdir(my_argv[0]);
140 }
141 }
142 }
143 return 0;
144 }
通過gethostname獲取主機名,通過getcwd獲得當前工作目錄,通過getpwuid獲得當前登錄的用戶信息
這樣命令提示符就完成了;
- 普通命令的實現
普通命令的實現並不困難,我的目的是讓子進程來執行命令,也就是通常的讓fork產生的子進程執行exec族函數,然後自己死掉,通過父進程回收資源,迴圈並創建新的子進程,這就是shell的大框架,其中用execvp函數來實現命令的執行,函數原型就不多說了,查手冊就能查到,簡單解釋一下參數,第一個參數是命令行的字元串,第二是參數是一個字元串數組,從上到下依次存放,命令,參數(可能有多個,一個參數占一個下標),最後用NULL占據一個下標表示結束。
- cd命令的實現
cd命令的實現有些問題,不是普通命令的實現,就是說簡單的使用execvp是不能實現的,因為就運算元進程改變了目錄之後也會把自己殺死,父進程和子進程之間是不通的(就是說父進程和子進程並不共用環境變數,子進程修改了當前工作目錄的環境變數對父進程也沒有什麼影響),後來使用system來執行系統調用,也失敗,因為更多時候shell會產生一個子進程來執行命令,因為shell本身執行會有風險,可能會因為用戶的錯誤操作把自己掛掉,所以使用子進程來執行命令,而這樣和剛纔的結果是一樣的並不會影響到父進程,最後採用了chdir函數,他可以改變當前進程的環境變數中的工作目錄(就是專門change dir用的),讓父進程來執行,fork出來的子進程會有一份父進程環境變數的拷貝,這就完成了改變目錄,並將結果傳遞了下來
1 else
2 {
3 //father
4 int status=0;
5 wait(&status);
6 if(status==256)
7 {
8 my_argv[0]+=3;
9 chdir(my_argv[0]);
10 }
11 }
- 管道符的實現
管道符的實現很簡單,平常我們執行命令的時候,都是結果自動輸出到電腦屏幕上,這說明一般命令的輸出是write在標準輸出stdout的,而我們輸出命令的參數是通過鍵盤,這說明命令的輸入來源是標準輸入stdin,如果我們關閉了,標準輸出和標準輸入,而是通過一個管道,讓結果寫進管道,然後讓參數從管道中讀取(簡單的說就是讓管道的兩段代替標準輸入和標準輸出),管道符就實現了。
1 void Pipe(char ** my_argv,char *buf)
2 {
3 int fd[2];
4 pipe(fd);
5 pid_t id2=fork();
6 if(id2==0)
7 {
8 close(1);
9 dup(fd[1]);
10 close(fd[1]);
11 close(fd[0]);
12 execvp(my_argv[0],my_argv);
13 }
14 else
15 {
16 waitpid(id2,NULL,0);
17 close(0);
18 dup(fd[0]);
19 close(fd[0]);
20 close(fd[1]);
21 BuildCommand(my_argv,buf);
22 execvp(my_argv[0],my_argv);
23 }