上一次简单的谈了怎样从命令行读取输入并对输入进行初步的处理。下一步就是所谓执行命令了。而命令大概又分成两个部分,一是内建命令,二是执行外部程序。这篇主要讲如喝执行内建命令。
我实现的内建命令有,cd,info,pwd,exit,search。值得一提的是,我把执行外部程序的命令ex也放到了内建命令中,只是为了方便管理。具体代码如下。
#define CWD_LENTH 64 /* Function Declarations for builtin shell commands: */ int ssh_cd(char **args); int ssh_info(char **args); int ssh_exit(char **args); int ssh_pwd(char **args); int ssh_search(char **args); /* List of builtin commands, followed by their corresponding functions. */ char *builtin_str[] = { "cd", "info", "exit", "pwd", "search", "ex" }; /* An array of function pointers. */ int (*builtin_func[]) (char **) = { &ssh_cd, &ssh_info, &ssh_exit, &ssh_pwd, &ssh_search, &ssh_launch }; int ssh_num_builtins() { return sizeof(builtin_str) / sizeof(char *); } /* Builtin function implementations. */ int ssh_cd(char **args) { if (args[1] == NULL) { fprintf(stderr, "ssh: expected argument to \"cd\"\n"); } else { if (chdir(args[1]) != 0) { perror("ssh"); } } return 1; } int ssh_info(char **args) { printf("XJCO2211 Simplified Shell by sc16h3s.\n"); return 1; } int ssh_exit(char **args) { return 0; } int ssh_pwd(char **args) { char cwd[CWD_LENTH]; getcwd(cwd,CWD_LENTH); printf("Current working directory is %s.\n",cwd); return 1; } int ssh_search(char **args) { if (args[1] == NULL){ printf("Please input values after 'search'.\n"); return 1; } char cwd[CWD_LENTH]; char **filenames = (char **)malloc(sizeof(char *) * 1024); char **filegets = (char **)malloc(sizeof(char *) * 1024); int number = 0; DIR *dirptr = NULL; struct dirent *entry; //get cwd. getcwd(cwd,CWD_LENTH); //open dir. if((dirptr = opendir(cwd)) == NULL){ perror("ssh"); exit(EXIT_FAILURE); } while(entry = readdir(dirptr)){ //printf("%d\n",entry->d_reclen); filenames[number] = entry->d_name; number++; } int i,j=0; for (i=0; i<number; i++){ int count = 0; int num = 1; //find the '.' or the end of filename. And store their position. while(filenames[i][count] != 0){ if (filenames[i][count] == 46){ break; } count++; } //compare names to aim. if correct, store them. while(args[1][num] == filenames[i][count]){ if (args[1][num] == 0){ filegets[i]=filenames[i]; break; } num++; count++; } } //print results. for (j=0; j<number; j++){ if(filegets[j] != NULL){ printf("%s\n", filegets[j]); } } return 1; }这里有很多部分,我一一解释一下。第一段就是函数定义。把要用的函数先声明。第二段是,就是在命令行输入的字符指令。第三段是函数指针,这个格式真的是令人头疼。。。真的,刚开始学指针这部分真的是把我折磨的头皮发麻,每次以为自己懂了结果看到一段新的代码又觉得自己不懂了,,,我已经不敢乱说了,所以这段大家自行百度理解,反正在这里这么写是对的,先这么着吧。第四段就是为了算一下你有几个内建指令,方便在后面用,这样你随便在里面加,后面也不用改,自动就算出来了。理解也很简单,在builtin_str里,每个内存块都占char*个大小,所以除一下就知道有多少个了。最后一段,就是具体实现各个内建指令函数了。
前四个指令的实现真的很简单了,看应该都能看懂,看不懂去百度三十秒搞定。接下来说一个我在教程外自己加的指令search,它可以以“search *.c”的格式搜索当前路径下所有的.c文件。同理别的类型的文件也可以搜索,没有后缀的也可以,写成“search *”就行。主要是利用#include <dirent.h>实现。这个库不在C的标准库中(主要是用标准库我tmd写不出来),但是也是一个非常常用的库。来,接下来我们一点一点看:
if (args[1] == NULL){ printf("Please input values after 'search'.\n"); return 1; }
这段好理解,就是如果search后面什么都没跟,那就重新进入循环,然后告诉用户重新输入。这个return 1要跟后面综合部分的函数一起看,等我写完我会把源码全部放出来。现在就知道在内建函数中,如果return 1,就是重新进入循环,如果return 0,那就是跳出循环,整个shell都会结束!
char cwd[CWD_LENTH]; char **filenames = (char **)malloc(sizeof(char *) * 1024); char **filegets = (char **)malloc(sizeof(char *) * 1024); int number = 0; DIR *dirptr = NULL; struct dirent *entry;
cwd是用来储存当前路径的。filenames用来放路径下所有文件的文件名。我假设文件夹下不会有超过1024个文件,多了的话,我太菜了,以后再优化吧。filegets用来放搜索出的符合条件的文件名。number用来计数有多少个文件。 最后两个是读取文件所需的函数,后面再说。
//get cwd. getcwd(cwd,CWD_LENTH);
//open dir. if((dirptr = opendir(cwd)) == NULL){ perror("ssh"); exit(EXIT_FAILURE); }
while(entry = readdir(dirptr)){ //printf("%d\n",entry->d_reclen); filenames[number] = entry->d_name; number++; }
第一段,读取当前路径并存在cwd中。第二段,用opendir函数打开当前路径,用dirptr代表(这句话说的不对,但为了新手能够理解,我姑且如此说)。第三段将路径下所有文件名读取出来。entry = readdir(dirptr)会顺序从路径下读取每个文件的信息,每运行一次就会跳到下一个文件。entry->d_name用来读取文件的文件名,entry里面信息很多,这里不一一列举,需要的网上查。
for (i=0; i<number; i++){ int count = 0; int num = 1;
//find the '.' or the end of filename. And store their position. while(filenames[i][count] != 0){ if (filenames[i][count] == 46){ break; } count++; }
//compare names to aim. if correct, store them. while(args[1][num] == filenames[i][count]){ if (args[1][num] == 0){ filegets[i]=filenames[i]; break; } num++; count++; } }
//print results. for (j=0; j<number; j++){ if(filegets[j] != NULL){ printf("%s\n", filegets[j]); } }
return 1; } 这一段就是具体筛选目标文件名的地方!先说一下我的逻辑,把每一个文件名的后缀拿出来,如“.c”或“.txt”,和我们想选的如“.txt”来一位一位的对比,从第一位开始,一样就跳到下一位继续比,比到如果出现NULL,就证明全部相同了!此时就将对应的文件名存到filegets中。注意!为了方便,我没用stack的结构在filegets中存储数据,我直接将filenames的文件名存到对应位置的filegets中。举例,如果filenames中是这样,{“abc.c”,“abc.txt”,“ab.txt”,“main.c”...}我们要选的是.c文件的话,filegets中就是,{“abc.c”,NULL,NULL,“main.c”...}。我们在输出的时候,只要filegets中不是NULL的输出出来就好了。所以我的代码,大循环用来遍历每一个文件名,count用来记录文件名跳到哪一位,num记录目标文件名跳到哪一位。第二段,找到文件名的后缀。第三段,因为输入的是“search *.XXX”,所以从args[1][num]之后记录的就是我们要的,一一比对就好。在二三段要注意的一点是,filenames[x][y]或filegets[x][y]是数!char*型里面的每一位都是数!保存范围用0~255,输出时用ascii码转化成字符。所以用strcmp函数来比较相同与否是行不通的,直接找ascii码用对应数字来判断就好。最后输出就完了。
如何执行外部程序,实现pipe和IO重定向放到下一篇一起讲。