“命令终端”的实现3-命令的执行

前面已经能获取到输入的字符了,接着就是解析这些字符,判断,符合要求的,执行对应的函数。而对应的函数,就是需要实现的命令。本文从具体的命令实现逐步倒推,最后对接上一文章。

一、命令函数

Linux 中,基本上每条命令都有参数及帮助信息,仿照这些功能,给出实现函数指针及结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 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函数指针。命令结构体为全局变量,下面给出其定义及对应命令的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

/* 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;
}

以上就是具体命令的实现过程,可以将每条命令都理解为一个单独的可带参数的程序。

二、查找命令

查找命令名称前,需要先对输入的字符串进行解析,实现函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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中的命令名称。如果找到返回名称,反之为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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);
}

注意,如果命令数量过多,不宜使用从头遍历的方法查找。本文工程仅为示例,暂不研究效率。

三、汇总运行

运行命令函数实现如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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,最后运行函数指针。

四、主函数

入口函数及倒计时函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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