上一次简单的谈了怎样从命令行读取输入并对输入进行初步的处理。下一步就是所谓执行命令了。而命令大概又分成两个部分,一是内建命令,二是执行外部程序。这篇主要讲如喝执行内建命令。
我实现的内建命令有,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重定向放到下一篇一起讲。