目录
1 概述
在嵌入式开发中,经常会利用命令行交互系统测试功能,本文描述使用C语言开发的一个命令行交互系统,支持命令注册,命令分组,执行命令,显示命令提示。
2 设计
2.1 接口
typedef int (*CommandHandler)(int argc, char* argv[]);
int RegisterCommand(const char* cmd, CommandHandler handler,
const char* help);
int RegisterGroupCommand(const char* cmd, const char* group,
CommandHandler handler, const char* help);
void ShowCommands(const char* group);
void ExecCommand(const char* cmdString);
int CommandReturnValue(void);
int WaitForCommand(void);
接口说明:
- CommandHandler 命令Hanlder类型
- RegisterCommand 注册命令
- RegisterGroupCommand 按组注册命令
- ShowCommands 按组显示命令,如果组名称为NULL显示所有命令
- ExecCommand 执行命令
- CommandReturnValue 获取最近命令返回值
- WaitForCommand 等待输入命令
2.2 实现
2.2.1 Command类型
#define COMMAND_MAX_SIZE 300
#define ARG_MAX_SIZE 20
#define CMD_PROMPT_STR "CMD->"
typedef struct Command
{
const char *cmd;
const char *help;
const char *group;
CommandHandler handler;
}Command;
static Command commands[COMMAND_MAX_SIZE] = { 0 };
说明:
- Command结构包括命令,帮助,组名称及命令handler。
- commands 静态全局命令,最大支持注册命令300个。
2.2.2 FindCommand
static int FindCommand(const char * str, Command ** pCmd)
{
if(0 == str || 0 == pCmd)
return 0;
int i;
for(i = 0; i < COMMAND_MAX_SIZE; i++)
{
if( 0 == commands[i].handler)
break;
int lenStr = strlen((const char *)str);
int len = strlen(commands[i].cmd);
if(0 == memcmp(commands[i].cmd, (const char *)str, (len > lenStr) ? len : lenStr))
{
*pCmd = &commands[i];
return 1;
}
}
return 0;
}
2.2.3 ExecuteCommand
static int ExecuteCommand(Command *pCmd, int argc, char* argv[])
{
if(0 == pCmd || 0 == pCmd->handler)
{
printf("\r\nExecuteCmd para error");
return 0;
}
return (pCmd->handler)(argc, argv);
}
2.2.4 ParseCommand
static int command_ret = -9999;
static void ParseCommand(const char *cmdString)
{
Command *pCmd = 0;
char *p = 0,*cmd = 0;
char* argv[ARG_MAX_SIZE] = { 0 };
int argc = 0;
if(!strlen(cmdString))
return;
add_history(cmdString);
if((('/' == cmdString[0]) && ('/' == cmdString[1]))
|| ('@' == cmdString[0])
|| ('#' == cmdString[0]))
{
printf("\r\nAnnotating code!");
printf("\r\n"CMD_PROMPT_STR);
fflush(stdout);
}
else
{
p = strtok((char *)cmdString," ");
if(0 != p)
{
cmd = p;
while(p)
{
p = strtok(0," ");
if(0 == p)
break;
if(argc < ARG_MAX_SIZE)
argv[argc++] = p;
else
break;
}
}
if(!FindCommand(cmd, &pCmd))
printf("\r\nUnknown command \r\n");
else
{
int ret = ExecuteCommand(pCmd, argc, argv);
if(ret < 0)
printf("\nusage: %s\n\n", pCmd->help);
command_ret = ret;
}
}
}
2.2.5 RegisterCommand/RegisterGroupCommand
int RegisterCommand(const char* cmd, CommandHandler handler, const char* help)
{
return RegisterGroupCommand(cmd, 0, handler, help);
}
int RegisterGroupCommand(const char* cmd, const char* group,
CommandHandler handler, const char* help)
{
if(!cmd || !help || !handler)
{
printf("%s: invalid param\n", __func__);
return 1;
}
uint32_t i;
for(i = 0; i < COMMAND_MAX_SIZE; i++)
{
if(!commands[i].handler)
{
commands[i].cmd = cmd;
commands[i].group = group;
commands[i].help = help;
commands[i].handler = handler;
break;
}
}
if(i == COMMAND_MAX_SIZE)
{
printf("%s: is max size=%u\n", __func__, i);
return 2;
}
return 0;
}
2.2.6 注册系统命令
static uint8_t runFlag = 1;
static void ExitCommand(void) { runFlag = 0; }
static int exit_cmd(int argc, char* argv[])
{
ExitCommand();
return 0;
}
static int show_cmd(int argc, char* argv[])
{
const char* group = NULL;
if(argc > 0)
group = argv[0];
ShowCommands(group);
return 0;
}
static int ret_cmd(int argc, char* argv[])
{
if(argc == 0)
printf("ret = %d\n", command_ret);
else
fwrite(&command_ret, sizeof(command_ret), 1, stdout);
return command_ret > 0 ? command_ret : 0;
}
static int print_cmd(int argc, char* argv[])
{
int i;
for(i = 0; i < argc; i++)
{
printf("%s", argv[i]);
if(i != argc - 1)
printf(" ");
else
printf("\n");
}
}
static int test_cmd(int argc, char* argv[])
{
if(argc < 2)
return command_ret > 0 ? command_ret : 0;
int value = strtol(argv[1], NULL, 0);
if(value == command_ret)
printf("Test of %s is passed(ok)!\n", argv[0]);
else
printf("Test of %s is not passed!\n", argv[0]);
return command_ret > 0 ? command_ret : 0;
}
static int pause_cmd(int argc, char* argv[])
{
readkey();
return 0;
}
static void RegisterDefaultCommand(void)
{
static int isRegister = 0;
if(isRegister)
return;
RegisterGroupCommand("ret", "sys", ret_cmd, "get return value of last command");
RegisterGroupCommand("print", "sys", print_cmd, "print text");
RegisterGroupCommand("test", "sys", test_cmd, "test return value of last command");
RegisterGroupCommand("pause", "sys", pause_cmd, "pause util press enter key");
RegisterGroupCommand("exit", "sys", exit_cmd, "exit the program");
RegisterGroupCommand("help", "sys", show_cmd, "show this page");
isRegister = 1;
}
2.2.7 ExecCommand/CommandReturnValue
void ExecCommand(const char* cmdString)
{
printf("\n%s\n", cmdString);
ParseCommand(cmdString);
}
int CommandReturnValue(void)
{
return command_ret;
}
2.2.8 ShowCommands
void ShowCommands(const char* group)
{
uint32_t i;
uint32_t count = 0;
RegisterDefaultCommand();
if(!group || !strlen(group))
{
for(i = 0; i < COMMAND_MAX_SIZE; i++)
{
if(commands[i].handler)
{
printf("%-10s: %s\n", commands[i].cmd, commands[i].help);
count++;
}
}
printf("\nshow %u commands\n", count);
}
else
{
for(i = 0; i < COMMAND_MAX_SIZE; i++)
{
if(commands[i].handler
&& commands[i].group
&& strcmp(commands[i].group, group) == 0)
{
printf("%-10s: %s\n", commands[i].cmd, commands[i].help);
count++;
}
}
}
}
2.2.9 WaitForCommand
int WaitForCommand(void)
{
int value;
uint8_t len = 0;
uint8_t cursor = 0;
RegisterDefaultCommand();
while(runFlag)
{
char *line_read = readline(CMD_PROMPT_STR);
if(!line_read)
continue;
ParseCommand(line_read);
free(line_read);
}
return 0;
}
2.2.10 readline/readkey/add_history
有readline库情况下,直接调用readline库的readline和add_history接口。
没有readline库情况下如下简单实现readline和add_history:
#define CMDLINE_MAX_LEN 1024
char *readline(const char* prompt)
{
static char cmdline[CMDLINE_MAX_LEN + 1];
memset(cmdline, 0, sizeof(cmdline));
printf("%s", prompt);
fgets(cmdline, CMDLINE_MAX_LEN, stdin);
cmdline[strlen(cmdline) - 1] = 0;
return strdup(cmdline);
}
void readkey(void)
{
static char keys[CMDLINE_MAX_LEN + 1];
fgets(keys, CMDLINE_MAX_LEN, stdin);
}
int add_history(const char* line)
{
return 0;
}
3 实例
#include "command.h"
#include <stdio.h>
static int test_args(int argc, char* argv[])
{
int i;
for(i = 0; i < argc; i++)
{
printf("%s", argv[i]);
if(i != argc - 1)
printf(" ");
else
printf("\n");
}
return 0;
}
static int test_return(int argc, char* argv[])
{
if(argc < 1)
return -1;
return 0;
}
int main(int argc, char* argv[])
{
RegisterCommand("test1", test_args, "test1 arg1 arg2 ...");
RegisterCommand("test2", test_return, "test2 arg1");
WaitForCommand();
return 0;
}
运行结果:
CMD->help
test1 : test1 arg1 arg2 ...
test2 : test2 arg1
ret : get return value of last command
print : print text
test : test return value of last command
pause : pause util press enter key
exit : exit the program
help : show this page
show 8 commands
CMD->test1 this is a test
this is a test
CMD->test2 a b
CMD->ret
ret = 0
CMD->test2
usage: test2 arg1
CMD->ret
ret = -1
CMD->exit