前面已经能获取到输入的字符了,接着就是解析这些字符,判断,符合要求的,执行对应的函数。而对应的函数,就是需要实现的命令。本文从具体的命令实现逐步倒推,最后对接上一文章。
一、命令函数
Linux 中,基本上每条命令都有参数及帮助信息,仿照这些功能,给出实现函数指针及结构体:
/*
* Monitor Command Table
*/
typedef int (*cmd_func)(int argc, char * const argv[]);
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
cmd_func cmd; /* Implementation function */
char *usage; /* Usage message (short) */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
/* someone should implement the table */
extern cmd_tbl_t cmd_table[];
为保持命令的独立性,设置cmd_func
函数指针。命令结构体为全局变量,下面给出其定义及对应命令的实现:
/* just a foolish command table */
cmd_tbl_t cmd_table[] =
{
{"help", CONFIG_SYS_MAXARGS, do_help, "print help info."},
{"print", 1, do_print, "print the env."},
{"exit", 1, do_exit, "exit..."},
{NULL, 0, NULL, NULL},
};
int do_help(int argc, char * const argv[])
{
_do_help(cmd_table, sizeof(cmd_table)/sizeof(cmd_tbl_t), argc, argv);
return 0;
}
int do_print(int argc, char * const argv[])
{
myprintf("in %s\n", __FUNCTION__);
return 0;
}
int do_exit(int argc, char * const argv[])
{
exit(0);
return 0;
}
以上就是具体命令的实现过程,可以将每条命令都理解为一个单独的可带参数的程序。
二、查找命令
查找命令名称前,需要先对输入的字符串进行解析,实现函数如下:
int parse_line (char *line, char *argv[])
{
int nargs = 0;
#ifdef DEBUG_PARSER
cmd_printf ("parse_line: \"%s\"\n", line);
#endif
while (nargs < CONFIG_SYS_MAXARGS)
{
/* skip any white space */
while ((*line == ' ') || (*line == '\t'))
{
++line;
}
if (*line == '\0')
{ /* end of line, no more args */
argv[nargs] = NULL;
#ifdef DEBUG_PARSER
cmd_printf ("parse_line: nargs=%d\n", nargs);
#endif
return (nargs);
}
argv[nargs++] = line; /* begin of argument string */
/* find end of string */
while (*line && (*line != ' ') && (*line != '\t'))
{
++line;
}
if (*line == '\0')
{ /* end of line, no more args */
argv[nargs] = NULL;
#ifdef DEBUG_PARSER
cmd_printf ("parse_line: nargs=%d\n", nargs);
#endif
return (nargs);
}
*line++ = '\0'; /* terminate current arg */
}
cmd_printf ("** Too many args (max. %d) **\n", CONFIG_SYS_MAXARGS);
#ifdef DEBUG_PARSER
cmd_printf ("parse_line: nargs=%d\n", nargs);
#endif
return (nargs);
}
输出参数为argv
数组,此即为命令参数列表,其中argv[0]
即命令名称,返回值为命令参数个数,即argc
。这就是我们熟悉的C语言 main 函数的参数。
查找命令实际是搜索结构体数组cmd_table
中的命令名称。如果找到返回名称,反之为空。
cmd_tbl_t* find_cmd_tbl3(const char* cmd, cmd_tbl_t *table)
{
const char *p;
char *q;
cmd_tbl_t *c, *found;
int nmatches, longest;
longest = 0;
nmatches = 0;
found = 0;
for (c = table; (p = c->name) != NULL; c++)
{
for (q = cmd; *q == *p++; q++)
if (*q == 0) /* exact match? */
return (c);
if (!*q)
{
return NULL;
}
}
return NULL;
}
cmd_tbl_t *find_cmd (const char* cmd)
{
//int len = sizeof(cmd_table) / sizeof(cmd_tbl_t);
//return find_cmd_tbl(cmd, cmd_table, len);
//return find_cmd_tbl2(cmd, cmd_table);
return find_cmd_tbl3(cmd, cmd_table);
}
注意,如果命令数量过多,不宜使用从头遍历的方法查找。本文工程仅为示例,暂不研究效率。
三、汇总运行
运行命令函数实现如下,
int run_command (const char *cmd)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CB_SIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
//char finaltoken[CB_SIZE];
char *str = cmdbuf;
char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */
int argc, inquotes;
int repeatable = 1;
int rc = 0;
#ifdef DEBUG_PARSER
cmd_printf ("[RUN_COMMAND] cmd[%p]=\"", cmd);
cmd_puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */
cmd_puts ("\"\n");
#endif
//clear_ctrlc(); /* forget any previous Control C */
if (!cmd || !*cmd)
{
return -1; /* empty command */
}
if (strlen(cmd) >= CB_SIZE)
{
cmd_puts ("## Command too long!\n");
return -1;
}
strcpy (cmdbuf, cmd);
/* Process separators and check for invalid
* repeatable commands
*/
#ifdef DEBUG_PARSER
cmd_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
cmd_printf ("token: \"%s\"\n", token);
#endif
/* Extract arguments */
if ((argc = parse_line (token, argv)) == 0)
{
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL)
{
cmd_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;
}
/* OK - call function to do the command */
if ((cmdtp->cmd) (argc, argv) != 0)
{
rc = -1;
}
}
//return rc ? rc : repeatable;
return rc;
}
主要过程有解析字符串parse_line,查找命令结构数组find_cmd,最后运行函数指针。
四、主函数
入口函数及倒计时函数如下:
int abortboot(int delay)
{
int abort = 0;
int i = 0;
myprintf("Hit any key to stop autoboot: %2d ", delay);
while ((delay > 0) && (!abort))
{
--delay;
for (i = 0; !abort && i < 100; ++i)
{
if (mytstc())
{
abort = 1;
delay = 0;
mygetc();
break;
}
mysleep(10);
}
myprintf("\b\b\b%2d ", delay);
}
myputc('\n');
return abort;
}
int readline_test(void)
{
static char lastcommand[CB_SIZE] = {0};
int len;
if (!abortboot(5))
{
myprintf("Aotu run.\n");
return 0;
}
myprintf("You abort.\n");
while (1)
{
len = readline(PROMPT, lastcommand);
if (len > 0)
{
//printf("len: %d\n", len);
if (len >= CB_SIZE)
{
myprintf("command line too large.\n");
break;
}
//strcpy(lastcommand, console_buffer);
//printf("[echo]: %s\n", lastcommand);
}
else if (len == 0)
{
//printf("nothing input.\n");
// do nothing
}
if (len == -1)
{
myputs("<INTERRUPT>\n");
}
else
{
run_command(lastcommand);
}
}
return 0;
}
至此,一个“命令终端”实现完毕。可对其底层的字符处理进行修改,以适应不同环境。除此外,其它业务逻辑完全无变化。
李迟 2020.9.30