这2天做了一个类似Linux shell的UI界面,目前已初步完成cd, ls, help, pwd, quit等命令,在Linux下实现,效果图见下图:
ls命令:
开始及help命令:
对于完成此UI界面来说,最主要是根据输入的命令找到相关的命令处理函数。通过参考部分bash的实现,找到了如下方法:
通过一个回调函数指针,实现调用相关命令处理函数的功能,代码如下:
//***************************************************************************** // // Command line function callback type. // //***************************************************************************** typedef int (*pfnCmdLine)(int argc, char *argv[]); //***************************************************************************** // //! Structure for an entry in the command list table. // //***************************************************************************** typedef struct { // //! A pointer to a string containing the name of the command. // const char *pcCmd; // //! A function pointer to the implementation of the command. // pfnCmdLine pfnCmd; // //! A pointer to a string of brief help text for the command. // const char *pcHelp; } tCmdLineEntry;
而终端输入的命令最终会在CmdLineProcess()这个核心函数里面进行处理,代码如下:
int CmdLineProcess(char *pcCmdLine) { static char *argv[CMDLINE_MAX_ARGS + 1]; char *pcChar; int argc; int bFindArg = 1; tCmdLineEntry *pCmdEntry; // // Initialize the argument counter, and point to the beginning of the // command line string. // argc = 0; pcChar = pcCmdLine; //printf("CmdLineProcess: %s\n", pcCmdLine); // // Advance through the command line until a zero character is found. // while (*pcChar) { // // If there is a space, then replace it with a zero, and set the flag // to search for the next argument. // if(*pcChar == ' ') { *pcChar = 0; bFindArg = 1; } // // Otherwise it is not a space, so it must be a character that is part // of an argument. // else { // // If bFindArg is set, then that means we are looking for the start // of the next argument. // if(bFindArg) { // // As long as the maximum number of arguments has not been // reached, then save the pointer to the start of this new arg // in the argv array, and increment the count of args, argc. // if(argc < CMDLINE_MAX_ARGS) { //printf("\nargc=%d, argv=%s ", argc, argv); //printf(" pcChar=%c ", *pcChar); argv[argc] = pcChar; //printf("\nargc=%d, argv=%s ", argc, argv); argc++; bFindArg = 0; } // // The maximum number of arguments has been reached so return // the error. // else { return(CMDLINE_TOO_MANY_ARGS); } } } // // Advance to the next character in the command line. // pcChar++; } //printf("argc=%d, argv=%s ", argc, argv); // // If one or more arguments was found, then process the command. // if(argc) { // // Start at the beginning of the command table, to look for a matching // command. // pCmdEntry = &g_sCmdTable[0]; //printf("pCmdEntry->pcCmd=%s \n", pCmdEntry->pcCmd); // // Search through the command table until a null command string is // found, which marks the end of the table. // while(pCmdEntry->pcCmd) { //printf("while: pCmdEntry->pcCmd=%s, argv[0]=%s \n", pCmdEntry->pcCmd, argv[0]); //printf("while: pCmdEntry->pfnCmd=0x%x \n", pCmdEntry->pfnCmd); // // If this command entry command string matches argv[0], then call // the function for this command, passing the command line // arguments. // if(!strcmp(argv[0], pCmdEntry->pcCmd)) { //printf("Define: pCmdEntry->pcCmd=%s, argv[0]=%s \n", pCmdEntry->pcCmd, argv[0]); //printf("***pCmdEntry->pfnCmd=0x%x \n", pCmdEntry->pfnCmd); return(pCmdEntry->pfnCmd(argc, argv)); } // // Not found, so advance to the next entry. // pCmdEntry++; } } // // Fall through to here means that no matching command was found, so return // an error. // return(CMDLINE_BAD_CMD); }
参考了部分网上的资料,实现Linux "ls" 命令的源码如下:
/************************************************************************************ ** File: - Z:\work\code\c\longluo\cmd_ls.c ** VENDOR_EDIT ** Copyright (C), tcpipstack. ** ** Description: ** cmd_ls.c - The implement of the Linux Like "ls" command. ** ** Version: 1.0 ** Date created: 10:20:56,14/11/2012 ** Author: long.luo ** ** --------------------------- Revision History: -------------------------------- ** <author> <data> <desc> ** ************************************************************************************/ #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <pwd.h> #include <grp.h> #include <time.h> #include <dirent.h> int do_ls(char *dir,char *filename,int lflag) { int n; struct stat buf; char out[100]; struct passwd *pw; struct group *gr; struct tm *t; //如果不带l参数,直接显示文件/目录名 if (lflag == 0) { printf("%s\t", filename); return 0; } if(lstat(dir,&buf)<0) { fprintf(stderr,"stat error:%s\n",strerror(errno)); return -1; } //获取字符串的属性:普通文件-、目录d、字符设备c、块设备b、 //管道文件p、连接文件l、套接字文件s switch(buf.st_mode & S_IFMT) { case S_IFREG: printf("-"); break; case S_IFDIR: printf("d"); break; case S_IFCHR: printf("c"); break; case S_IFBLK: printf("b"); break; case S_IFIFO: printf("p"); break; case S_IFLNK: printf("l"); break; case S_IFSOCK: printf("s"); break; } //打印文件的读写属性:读r、写w、执行x、无权限- for(n=8;n>=0;n--) { if(buf.st_mode&(1<<n)) { switch(n%3) { case 2: printf("r"); break; case 1: printf("w"); break; case 0: printf("x"); break; default: break; } } else { printf("-"); } } //硬链接数,此链接非彼链接,指(包含)目录的个数, //文件为1,目录起始为2,再加上目录里包含的目录个数(不递归,只一层) printf(" %d",buf.st_nlink); pw = getpwuid(buf.st_uid); //所属用户名 printf(" %s",pw->pw_name); gr = getgrgid(buf.st_gid); //所属组名 printf(" %s",gr->gr_name); printf(" %ld",buf.st_size); //字节计总大小 t = localtime(&buf.st_atime); //最后一次访问时间 printf(" %d-%d-%d %d:%d" ,t->tm_year+1900 ,t->tm_mon+1 ,t->tm_mday ,t->tm_hour ,t->tm_min); printf(" %s ",filename); //判断是否为链接,是返回真 if(S_ISLNK(buf.st_mode)) { printf(" -> "); if(readlink(filename,out,100)==-1) { //printf("readlink error\n"); } printf("%s",out); } printf("\n"); return 0; } // ls的准备工作 int ls_prepare(char *w,int aflag,int lflag) { struct stat buf; //man lstat可以看到此结构 char name[100]; DIR *dir; //类似打开文件的fd描述符 struct dirent *pdr; //man readdir可以看到此结构 //获取文件/目录属性并赋值给buf,该函数和lstat一样, //只是当w为链接时,指代他本身,并不存在文件 if(lstat(w,&buf)<0) { fprintf(stderr,"stat error:%s\n",strerror(errno)); return -1; } if(S_ISDIR(buf.st_mode)) //判断是否为目录,是返回真 { dir = opendir(w); //打开目录 while ((pdr = readdir(dir))!=NULL) //读/遍历目录 { if(aflag==0) //如果不带a参数,越过以.开头的所有文件/目录 { if(pdr->d_name[0]=='.') continue; memset(name,0,100); strcpy(name,w); //拷贝 strcat(name,"/"); //追加 strcat(name,pdr->d_name); do_ls(name,pdr->d_name,lflag); } //有a参数显示所有 else { memset(name,0,100); strcpy(name,w); strcat(name,"/"); strcat(name,pdr->d_name); do_ls(name,pdr->d_name,lflag); } } closedir(dir); } //为文件则直接显示 else { do_ls(w,w,lflag); } return 0; }
项目的main函数代码如下,主要就是一个while(1)循环读取终端的输入,然后送入命令处理函数,值得注意的是因为输入了换行符,因此需要对输入的字符进行处理,否则会有bug产生:
int main(int argc, char *argv[]) { int nStatus, nStrLen; fprintf(stderr, "Hello, CMD UI Program..."); // Enter an infinite loop for reading and processing commands from the user. while (1) { // Print a prompt to the console. Show the CWD. printf("\n%s> ", g_cCwdBuf); // Get a line of text from the user. fgets(g_cCmdBuf, CMD_BUF_SIZE, stdin); // Remove the char '\n'. nStrLen = strlen(g_cCmdBuf); if ('\n' == g_cCmdBuf[nStrLen - 1]) { g_cCmdBuf[nStrLen - 1] = '\0'; } // Pass the line from the user to the command processor. // It will be parsed and valid commands executed. nStatus = CmdLineProcess(g_cCmdBuf); // Handle the case of bad command. if(nStatus == CMDLINE_BAD_CMD) { printf("Bad command!\n"); } // Handle the case of too many arguments. else if(nStatus == CMDLINE_TOO_MANY_ARGS) { printf("Too many arguments for command processor!\n"); } // Otherwise the command was executed. Print the error // code if one was returned. else if(nStatus != 0) { printf("Command returned error code.\n"); } } return 0; }
在编写这个小项目的同时,也顺便把Makefile的编写也复习了下,这个项目的Makefile文件如下:
# # Makefile - Rules for building the program. # # History: # long.luo, 12/11/2012, created it. # long.luo, 13/11/2012, modified it. # # The compiler to be used. ifndef COMPILER COMPILER=gcc endif # Definitions for using GCC. ifeq (${COMPILER}, gcc) # The command for calling the compiler. CC=gcc # The flags passed to the compiler. CFLAGS=-Wall -Os -c # The command for calling the linker. LD=ld # The flags passed to the linker. LDFLAGS=--gc-sections # The PHONY targets. .PHONY: all clean # The default rule, which causes the Application to be built. all: cmd_ui # The rule to create the target directory. ${COMPILER}: mkdir ${COMPILER} # The rule for building the object file from each C source file. %.o : %.c ${CC} ${CFLAGS} -D${COMPILER} -o $@ $< # The objects. OBJS=cmdline.o cmd_ui_shell.o cmd_ls.o # The final executable file. exec=cmd_ui # The rule for building the application. cmd_ui: ${OBJS} $(CC) -o $@ $? # The rule to clean out all the build products. clean: rm -f ${wildcard *.o} ${exec} endif
以上,这个项目可以应用在其他终端上,比如串口上实现简单的UI界面:-)