一、简介
CLI(command line interface)是系统为用户提供的命令行接口。相信熟悉linux的同学肯定都必须了解一些linux的命令,在linux的shell上运行以实现一些功能。如最简单的ls命令,显示目录内容。
CLI可以让用户实时的与系统进行交互,获取系统的实时信息,完成用户自定义的功能。所有的系统都必须为用户提供接口,只不过接口的形式多种多样,如web、传统的C/S结构的图像界面,当然CLI是其中的一种方式。
二、为什么要研究CLI
你在做项目(主要指C/C++项目)的时候是否遇到过如下问题:
1、程序运行过程中出现故障,想实时的查看一些运行状态;
2、需要实时的与运行的系统进行一些简单的交互;
3、遇到这些问题时,你又不想使用GDB跟踪;
这时可以考虑为自己的程序增加一个小小的CLI,会极大的方便你的开发。
问题来了,实现像linux 强大CLI系统,那可不是一件简单的事情。如果从零DIY一个自己的CLI感觉有点困难,这时软件复用就派上用场了。只要你可以找一个已有的项目,带CLI的将其移植一下,相信很快就可以为你的程序添加一个属于你自己私有的CLI。自定义一下命令,不需要华丽的用户体验,只要实用即可。
三、自己的CLI
当时我想到的简单CLI就是uboot,熟悉uboot的同学肯定都知道uboot有一套命令集,如果做过uboot移植的同学肯定经常用它。只需要几个命令就可以将linux kernel引导起来,非常实用。那很自然的如果将uboot这套CLI移植到你的程序中,自定义一下适合自己的命令,那就大功告成了。
那就废话少说,看看uboot源码,将CLI部分移植出来。
下面是本人从uboot中移植的代码:
1、很简单只有两个文件(command.h和command.c);
2、将这两个文件编译一下:如gcc command.c -o ct,然后直接执行./ct;
3、下面是测试结果
源码:command.h
#ifndef __COMMAND_H__ #define __COMMAND_H__ /* Monitor Command Prompt */ #define CONFIG_SYS_PROMPT "->" /* Buffer size for input from the Console */ #define CONFIG_SYS_CBSIZE 256 #endif
源码:command.c
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include "command.h" #define MAX_ARGC 10 #define MAX_CMD_NUM 50 uint8_t console_buffer[CONFIG_SYS_CBSIZE + 1]; /* console I/O buffer */ static char erase_seq[] = "\b \b"; /* erase sequence */ static char tab_seq[] = " "; /* used to expand TABs */ typedef void (*cmd_fun_t)(int , char *[]) ; //命令结构体 typedef struct CMD_STRUCT { char name[32]; /* Command Name */ char usage[64];/* Usage message */ cmd_fun_t CmdFun;//void (*CmdFun)(int , char *[]);/* Command execute function */ }CMD_STRUCT_T; int cmd_num_current = 0; //命令列表 CMD_STRUCT_T CmdTbl[MAX_CMD_NUM]; #ifdef debug_cmd #define debug_print() printf("[%s] line:%d\n", __FUNCTION__, __LINE__) #else #define debug_print() #endif void HelpCmdExeFun(int agrc, char *argv[]) { debug_print() ; int i = 0; while (i < cmd_num_current) { printf("%3d. %-32s -- %s\n", i, CmdTbl[i].name, CmdTbl[i].usage); i++; } return; } void read_fun(int agrc, char * argv [ ]) { debug_print() ; int i = 0; while (i < cmd_num_current) { if (read_fun == CmdTbl[i].CmdFun) { printf("%s -- %s\n", CmdTbl[i].name, CmdTbl[i].usage); break; } i++; } return; } void write_fun(int agrc, char * argv [ ]) { debug_print() ; int i = 0; while (i < cmd_num_current) { if (write_fun == CmdTbl[i].CmdFun) { printf("%s -- %s\n", CmdTbl[i].name, CmdTbl[i].usage); break; } i++; } return; } void setenv_fun(int agrc, char * argv [ ]) { debug_print() ; int i = 0; while (i < cmd_num_current) { if (setenv_fun == CmdTbl[i].CmdFun) { printf("%s -- %s\n", CmdTbl[i].name, CmdTbl[i].usage); break; } i++; } return; } int register_cmd(char *name, char *usage, cmd_fun_t fun) { int ret; if (cmd_num_current < MAX_CMD_NUM) { strcpy(CmdTbl[cmd_num_current].name, name); strcpy(CmdTbl[cmd_num_current].usage , usage); CmdTbl[cmd_num_current].CmdFun = fun; cmd_num_current++; } else { printf("%s error\n"); return 1; } return 0; } char parse_buf[256] ; int parse_line(const char * const line, char *argv[]) { debug_print(); int argc = 0; char *ptr = parse_buf; memset(parse_buf, '\0', 256); strncpy(parse_buf, line, strlen(line)); while ((argv[argc]=strtok(ptr, " "))!=NULL) { debug_print(); //printf("argv[%d]:%s\n", argc, argv[argc]); argc++; if (argc > MAX_ARGC) break; ptr = NULL; } debug_print(); return argc; } static char * delete_char (char *buffer, char *p, int *colp, int *np, int plen) { char *s; if (*np == 0) { return (p); } if (*(--p) == '\t') { /* will retype the whole line */ while (*colp > plen) { puts (erase_seq); (*colp)--; } for (s=buffer; s<p; ++s) { if (*s == '\t') { puts (tab_seq+((*colp) & 07)); *colp += 8 - ((*colp) & 07); } else { ++(*colp); putc (*s, stdout ); } } } else { puts (erase_seq); (*colp)--; } (*np)--; return (p); } int32_t readline_into_buffer ( int8_t *const prompt, int8_t * buffer) { int8_t *p = buffer; int8_t * p_buf = p; int32_t n = 0; /* buffer index */ int32_t plen = 0; /* prompt length */ int32_t col; /* output column cnt */ int8_t c; int8_t *ptr = prompt; /* print prompt */ if (prompt) { plen = strlen (prompt); while (*ptr) putc(*ptr++, stdout); //puts (prompt); } col = plen; while (1) { c = getc(stdin); /* * Special character handling */ switch (c) { case '\r': /* Enter */ case '\n': *p = '\0'; //puts ("\r"); return (p - p_buf); case '\0': /* nul */ continue; case 0x03: /* ^C - break */ p_buf[0] = '\0'; /* discard input */ return (-1); case 0x15: /* ^U - erase line */ while (col > plen) { puts (erase_seq); --col; } p = p_buf; n = 0; continue; case 0x17: /* ^W - erase word */ p=delete_char(p_buf, p, &col, &n, plen); while ((n > 0) && (*p != ' ')) { p=delete_char(p_buf, p, &col, &n, plen); } continue; case 0x08: /* ^H - backspace */ case 0x7F: /* DEL - backspace */ p=delete_char(p_buf, p, &col, &n, plen); continue; default: /* * Must be a normal character then */ if (n < CONFIG_SYS_CBSIZE-2) { if (c == '\t') { /* expand TABs */ //puts (tab_seq+(col&07)); col += 8 - (col&07); } else { ++col; /* echo input */ //putc (c, stdout); } *p++ = c; ++n; } else { /* Buffer full */ putc ('\a', stdout); } } } } int32_t readline ( int8_t *const prompt) { /* * If console_buffer isn't 0-length the user will be prompted to modify * it instead of entering it from scratch as desired. */ memset(console_buffer, '\0', CONFIG_SYS_CBSIZE+1); console_buffer[0] = '\0'; return readline_into_buffer(prompt, console_buffer); } int find_cmd(char *cmd) { debug_print(); //printf("cmd:%s\n",cmd); int cmd_index = 0; if (('0' == cmd[0]) && ('\0' == cmd[1])) return cmd_index; cmd_index = atoi(cmd); if ((cmd_index >0 ) && (cmd_index < cmd_num_current)) { return cmd_index; } cmd_index = 0; while (cmd_index < MAX_CMD_NUM) { if (0 == strncmp(CmdTbl[cmd_index].name, cmd, strlen(cmd))) return cmd_index; cmd_index++; } printf("Command [%s ] don't support!\n", cmd); debug_print(); return -1 ; } /**************************************************************************** * returns: * 1 - command executed, repeatable * 0 - command executed but not repeatable, interrupted commands are * always considered not repeatable * -1 - not executed (unrecognized, bootd recursion or too many args) * (If cmd is NULL or "" or longer than CONFIG_SYS_CBSIZE-1 it is * considered unrecognized) * * WARNING: * * We must create a temporary copy of the command since the command we get * may be the result from getenv(), which returns a pointer directly to * the environment data, which may change magicly when the command we run * creates or modifies environment variables (like "bootp" does). */ int run_command (const char * const cmd, int flag) { debug_print(); //puts(cmd); int cmd_index = 0; int argc = 0; char *argv[MAX_ARGC]; argc = parse_line(cmd, argv); if ((argc > 0) &&(argc < MAX_ARGC)) cmd_index = find_cmd(argv[0]); else return 1; if ( -1 != cmd_index) CmdTbl[cmd_index].CmdFun(argc, argv); debug_print(); return 0; #if 0 cmd_tbl_t *cmdtp; char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */ char *token; /* start of token in cmdbuf */ char *sep; /* end of token (separator) in cmdbuf */ char finaltoken[CONFIG_SYS_CBSIZE]; char *str = cmdbuf; char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */ int argc, inquotes; int repeatable = 1; int rc = 0; #ifdef DEBUG_PARSER printf ("[RUN_COMMAND] cmd[%p]=\"", cmd); puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */ puts ("\"\n"); #endif clear_ctrlc(); /* forget any previous Control C */ if (!cmd || !*cmd) { return -1; /* empty command */ } if (strlen(cmd) >= CONFIG_SYS_CBSIZE) { puts ("## Command too long!\n"); return -1; } strcpy (cmdbuf, cmd); /* Process separators and check for invalid * repeatable commands */ #ifdef DEBUG_PARSER printf ("[PROCESS_SEPARATORS] %s\n", cmd); #endif while (*str) { /* * Find separator, or string end * Allow simple escape of ';' by writing "\;" */ for (inquotes = 0, sep = str; *sep; sep++) { if ((*sep=='\'') && (*(sep-1) != '\\')) inquotes=!inquotes; if (!inquotes && (*sep == ';') && /* separator */ ( sep != str) && /* past string start */ (*(sep-1) != '\\')) /* and NOT escaped */ break; } /* * Limit the token to data between separators */ token = str; if (*sep) { str = sep + 1; /* start of command for next pass */ *sep = '\0'; } else str = sep; /* no more commands for next pass */ #ifdef DEBUG_PARSER printf ("token: \"%s\"\n", token); #endif /* find macros in this token and replace them */ process_macros (token, finaltoken); /* Extract arguments */ if ((argc = parse_line (finaltoken, argv)) == 0) { rc = -1; /* no command at all */ continue; } /* Look up command in command table */ if ((cmdtp = find_cmd(argv[0])) == NULL) { printf ("Unknown command '%s' - try 'help'\n", argv[0]); rc = -1; /* give up after bad command */ continue; } /* found - check max args */ if (argc > cmdtp->maxargs) { cmd_usage(cmdtp); rc = -1; continue; } #if defined(CONFIG_CMD_BOOTD) /* avoid "bootd" recursion */ if (cmdtp->cmd == do_bootd) { #ifdef DEBUG_PARSER printf ("[%s]\n", finaltoken); #endif if (flag & CMD_FLAG_BOOTD) { puts ("'bootd' recursion detected\n"); rc = -1; continue; } else { flag |= CMD_FLAG_BOOTD; } } #endif /* OK - call function to do the command */ if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) { rc = -1; } repeatable &= cmdtp->repeatable; /* Did the user stop this? */ if (had_ctrlc ()) return -1; /* if stopped then not repeatable */ } return rc ? rc : repeatable; #endif } int main(int argc, char **argv) { int32_t rc = -1; int32_t len; static int8_t lastcommand[CONFIG_SYS_CBSIZE] = { 0, }; memset(CmdTbl, 0, sizeof(CMD_STRUCT_T)*MAX_CMD_NUM); register_cmd("help", "list all cmd\n\r", HelpCmdExeFun); register_cmd("read", "read memory\n\r", read_fun); register_cmd("write", "write memory\n\r", write_fun); register_cmd("setenv", "set env\n\r", setenv_fun); debug_print(); while (1) { len = readline (CONFIG_SYS_PROMPT); if (len > 0) { memset(lastcommand, '\0', CONFIG_SYS_CBSIZE); strncpy (lastcommand, console_buffer, strlen(console_buffer)); rc = run_command (lastcommand, 0); if (rc <= 0) { /* invalid command or not repeatable, forget it */ lastcommand[0] = 0; } } } return 0; }
四、总结
目的基本达到,实现了四个命令。
这个小程序本身没有什么,但是从有实现简单CLI的想法,到寻找开源代码,最后移植调试。最关键的必须注重软件复用,现在的open source那么多,一定要学会好好利用。