本文主要是介绍简单的CLI(command line interface),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、简介
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 50uint8_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() #endifvoid 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]);elsereturn 1;if ( -1 != cmd_index)CmdTbl[cmd_index].CmdFun(argc, argv);debug_print();return 0;#if 0cmd_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_PARSERprintf ("[RUN_COMMAND] cmd[%p]=\"", cmd);puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */puts ("\"\n"); #endifclear_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_PARSERprintf ("[PROCESS_SEPARATORS] %s\n", cmd); #endifwhile (*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';}elsestr = sep; /* no more commands for next pass */ #ifdef DEBUG_PARSERprintf ("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_PARSERprintf ("[%s]\n", finaltoken); #endifif (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那么多,一定要学会好好利用。
这篇关于简单的CLI(command line interface)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!