版本库URL:https://github.com/swagnhen/Advanced-Software-Engineering-Exercise.git
实验要求
1)为menu子系统设计接口,并写用户范例代码来实现原来的功能;
2)使用make和make clean来编译程序和清理自动生成的文件;
3)使menu子系统支持带参数的复杂命令,并在用户范例代码中自定义一个带参数的复杂命令;
4)可以使用getopt函数获取命令行参数。
实验内容
callback增强可重用链表实现包含于linkedlist.h与linkedlist.c中
shell中各命令功能的实现包含在cmdopt.h与cmdopt.c中
main函数包含于shelllet.c中
编译时请使用
make
1.子系统设计接口
//用于用户向命令行添加新的命令功能
int ShellletConfig(LinkedList *head, char * cmd, int (*handler)());
//用于启动命令行程序
int ExecuteShelllet(LinkedList *head);
具体实现
int ShellletConfig(LinkedList *l, char *cmd, int (*handler)())
{
CmdNode *p = NULL;
if (l == NULL)
{
return -1;
}
p = (CmdNode *)malloc(sizeof(CmdNode));
p->cmd = cmd;
p->handler = handler;
addNode(l, (Node *)p);
return 0;
}
int ExecuteShelllet(LinkedList *l)
{
int argc = 0;
char *argv[32];
printf("**********Shelllet Running**********\n");
while (1)
{
printf(">>>");
//cmd2arg函数用于将输入的命令转化为argc与argv形式
cmd2arg(&argc, argv);
CmdNode *p = findCmd(l, argv[0]);
if (p != NULL && p->handler != NULL)
p->handler(argc, argv);
argc = 0;
}
return 0;
}
2.Makefile编译文件
#
# Makefile for Shelllet Program
#
CC_PTHREAD_FLAGS = -lpthread
CC_FLAGS = -c
CC_OUTPUT_FLAGS = -o
CC = gcc
RM = rm
RM_FLAGS = -f
TARGET = test
OBJS = linkedlist.o cmdopt.o shelllet.o
all: $(OBJS)
$(CC) $(CC_OUTPUT_FLAGS) $(TARGET) $(OBJS)
.c.o:
$(CC) $(CC_FLAGS) $<
clean:
$(RM) $(RM_FLAGS) $(OBJS) $(TARGET) *.bak
3.复杂指令解析
void cmd2arg(int *argc, char **argv)
{
char cmd[1024];
char *pcmd = NULL;
pcmd = fgets(cmd, 1024, stdin);
if (pcmd == NULL)
return;
pcmd = strtok(pcmd, " ");
while (pcmd != NULL && *argc < 32)
{
argv[*argc] = pcmd;
(*argc)++;
pcmd = strtok(NULL, " ");
}
if (*argc == 1)
{
int len = strlen(argv[0]);
*(argv[0] + len - 1) = '\0';
}
}
用户实现的复杂指令函数
int test(int argc, char *argv[]){
printf(">>>Test success\n");
return 0;
}
4.获取命令行参
经cmd2arg处理后argv[1]开始均为命令行参数
void cmd2arg(int *argc, char **argv)
{
char cmd[1024];
char *pcmd = NULL;
pcmd = fgets(cmd, 1024, stdin);
if (pcmd == NULL)
return;
pcmd = strtok(pcmd, " ");
while (pcmd != NULL && *argc < 32)
{
argv[*argc] = pcmd;
(*argc)++;
pcmd = strtok(NULL, " ");
}
if (*argc == 1)
{
int len = strlen(argv[0]);
*(argv[0] + len - 1) = '\0';
}
}
运行效果
实验总结
我对老师的写法做了一些修改,还是去掉了全局链表,这样的话每次用户(程序员)在使用这个程序的时候都得自己先建立一个链表,初始化之后再将链表传入ExecuteShelllet函数中执行命令行程序。
这样应该算是面向对象的实现思路吧,毕竟C中没有类,不能定义一个Menu类再向其添加start方法。
其实这个程序有bug,不论输什么命令第一次都会失效。
我调试之后发现是在第一次解析命令时字符串比对函数strcmp出现错误,没有返回正确结果的同时还改变了形参cmd的值。
就很神秘,这个函数的原型不是加了const么,怎么还能改变形参值的,还只出现一次。
感觉可能是头文件include的问题,之前在没加string.h的时候函数strtok也出现了一样的问题。
试了很多没有解决,就搁置了。
还有就是像加减乘除这样有指令执行中含有scanf调用的命令2,运行结束后程序会执行一个空指令(即“”)。
这个肯定是fget和scanf混用的问题,估计是add运行结束之后有个换行符\n还在输入流里,下一轮开始的时候被fget读到了。
虽然在每个scanf后面单独加个fget应该能解决,但是感觉这种方式非常蠢,也没想到更好的方式,就算了。
这程序再拓展几次估计就要不能维护了。
【Swegnhan + 《软件工程(C编码实践篇)》MOOC课程作业http://mooc.study.163.com/course/USTC-1000002006】