UNIX-LINUX编程实践教程->第八章->实例代码注解->写一个简单的shell
一 分析
要实现一个shell,需包含3个步骤
1)读入指令
2)指令解析
3)执行指令
1 从键盘读入指令
从键盘读入指令的几个要点:
1)调用getc函数等待并获取用户键盘输入。
2)每一行命令的结束符为'\n',getsline函数就是通过这个结束符来判断用户是否完成指令的输入。
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char* cmdLine = (char*)malloc(sizeof(char)*100); char* prompt = "print your cmd >"; int i; while(1) { i = NextCmd(prompt,cmdLine); if(i != 0) { return i; } else { printf("you print a cmd: %s \n",cmdLine); } } free(cmdLine); return 0; } int NextCmd(char* prompt,char* cmdLine) { int i; printf("%s",prompt); i = GetsLine(cmdLine); if(i != 0) { return i; } else { return 0; } } int GetsLine(char* result) { int word; while(1) { word = getc(stdin); if(word != '\n') { *result = word; result ++; } else { *result = '\0'; return 0; } } }
输出:
print your cmd >asd you print a cmd: asd print your cmd >a you print a cmd: a print your cmd >
在上面的代码中我们从键盘上获得用户的指令输入,然后再直接打印出来。在GetsLine()函数执行getc(),等待用户输入并开始记录,直到用户输入回车后返回,返回前添加'\0'字符表示整条指令结束。在NextCmd()中输出提示符“print your cmd >”,执行GetsLine获得用户输入的整条指令并返回。最后在主函数中打印用户的输入。
2 指令解析
指令解析的几个要点:
1)支持的指令格式【指令】 【参数1】 【参数2】。。。【参数n】
2)指令与参数间,参数与参数间均以空格隔开,一条完整的指令以'\0'结尾。
3)因为使用函数execvp来执行指令(在后面会讲到),所以需要将解析的指令存入指针数组char* argList[]中,结尾处为NULL。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main() { char* argList[20]; char* cmdLine = "abc -a -b -c"; int i; for(i = 0;i<20;i++) { argList[i] = (char*)malloc(sizeof(char)*100); memset(argList[i],'\0',sizeof(char)*100); } CmdToArg(cmdLine,argList); for(i = 0;i<20;i++) { printf("get:%s\n",argList[i]); } for(i = 0;i<20;i++) { free(argList[i]); } return 0; } int CmdToArg(char* cmdLine,char* argList[20]) { char aChar; char* pChar; int i = 0; pChar = argList[0]; while(1) { aChar = *cmdLine; cmdLine++; if(aChar == ' ') { *pChar = '\0'; i++; pChar = argList[i]; } else if(aChar == '\0') { *pChar = '\0'; i++; argList[i] = 0; return 0; } else { *pChar = aChar; pChar++; } } }
上述代码中,使用CmdToArg()函数,将cmdLine中的字符以空格符为间断符号解析,并存入一个argList中,且在argList的末尾处添加‘0’字符,表明一行指令的结束。
3 执行指令
我们使用execvp()函数来启动另一个程序,这里有几点需要注意:
1)新的程序会覆盖原程序,导致新程序结束时原本的shell也结束了,所以需要使用fork()函数来新建一个进程来打开新的程序。
2)fork()函数的返回值可以判断当前进程是父进程还是子进程。
3)在父进程中使用wait()函数来等待新进程结束
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main() { char* arglist[3]; int i; arglist[0] = "ls"; arglist[1] = "-l"; arglist[2] = 0; i = fork(); if(i==0) { printf("I'm the new process.ready for cmd ls\n"); execvp("ls",arglist); } else { wait(NULL); printf("I'm the old process\n"); } }
在上面的代码中,我们使用fork()函数创建了一个新的进程,并在新进程中使用execvp()函数来启动新的程序,并在父进程中使用wait()函数来等待子进程结束。输出如下:
lqx@lqx-virtual-machine:~/bin/UnixPrograme/8$ ./a.out I'm the new process.ready for cmd ls total 44 -rwxrwxr-x 1 lqx lqx 7268 2013-07-02 10:33 a.out -rw-rw-r-- 1 lqx lqx 497 2013-04-25 15:12 execute.c -rw-rw-r-- 1 lqx lqx 482 2013-04-23 15:54 psh2.c -rw-rw-r-- 1 lqx lqx 584 2013-04-25 15:10 smsh1.c -rw-rw-r-- 1 lqx lqx 202 2013-04-25 15:14 smsh.h -rw-rw-r-- 1 lqx lqx 1715 2013-04-25 15:13 splitline.c -rw-rw-r-- 1 lqx lqx 438 2013-07-02 10:33 test.c -rwxrwxr-x 1 lqx lqx 7310 2013-05-09 14:46 testline -rw-rw-r-- 1 lqx lqx 436 2013-05-10 14:46 testline.c I'm the old process
4 整合
在前面,我们分别实现了:
1)从用户终端获得用户的指令输入
2)将一行指令解析为指定格式
3)创建子进程来执行用户的指令
现在就将以上3各部分整合到一起,实现一个基本的shell。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> int main() { char* cmdLine = (char*)malloc(sizeof(char)*100); char* prompt = "print your cmd >"; int i; char* argList[20]; for(i = 0;i<20;i++) { argList[i] = (char*)malloc(sizeof(char)*100); memset(argList[i],'\0',sizeof(char)*100); } while(1) { i = NextCmd(prompt,cmdLine); if(i != 0) { return i; } else { CmdToArg(cmdLine,argList); i = fork(); if(i==0) { execvp(argList[0],argList); exit(0); } else { wait(NULL); } } } free(cmdLine); for(i = 0;i<20;i++) { free(argList[i]); } return 0; } int NextCmd(char* prompt,char* cmdLine) { int i; printf("%s",prompt); i = GetsLine(cmdLine); if(i != 0) { return i; } else { return 0; } } int GetsLine(char* result) { int word; while(1) { word = getc(stdin); if(word != '\n') { *result = word; result ++; } else { *result = '\0'; return 0; } } } int CmdToArg(char* cmdLine,char* argList[20]) { char aChar; char* pChar; int i = 0; pChar = argList[0]; while(1) { aChar = *cmdLine; cmdLine++; if(aChar == ' ') { *pChar = '\0'; i++; pChar = argList[i]; } else if(aChar == '\0') { *pChar = '\0'; i++; argList[i] = 0; return 0; } else { *pChar = aChar; pChar++; } } }
测试一下:
print your cmd >ls -l total 48 -rw-rw-r-- 1 lqx lqx 1863 2013-07-02 10:52 \ -rwxrwxr-x 1 lqx lqx 7491 2013-07-02 11:10 a.out -rw-rw-r-- 1 lqx lqx 497 2013-04-25 15:12 execute.c -rw-rw-r-- 1 lqx lqx 482 2013-04-23 15:54 psh2.c -rw-rw-r-- 1 lqx lqx 584 2013-04-25 15:10 smsh1.c -rw-rw-r-- 1 lqx lqx 202 2013-04-25 15:14 smsh.h -rw-rw-r-- 1 lqx lqx 1715 2013-04-25 15:13 splitline.c -rw-rw-r-- 1 lqx lqx 1842 2013-07-02 11:10 test.c -rwxrwxr-x 1 lqx lqx 7310 2013-05-09 14:46 testline -rw-rw-r-- 1 lqx lqx 436 2013-05-10 14:46 testline.c
以上,我们实现了一个基本的shell,虽然还有很多不足之处,但是对shell的基本原理和功能都有了一些了解。