nr_shell 是一套开源 shell 框架,基于框架可创建终端交互功能。
为了记录终端输入指令,以及进行解析处理,nr_shell 提供了一套 cmd 结构体,具体如下:
typedef struct static_cmd_function_struct
{
char cmd[NR_SHELL_CMD_NAME_MAX_LENGTH];
void (*fp)(char argc, char *argv);
char *description;
} static_cmd_st;
typedef struct shell_history_queue_struct
{
unsigned short int fp;
unsigned short int rp;
unsigned short int len;
unsigned short int index;
unsigned short int store_front;
unsigned short int store_rear;
unsigned short int store_num;
char queue[NR_SHELL_MAX_CMD_HISTORY_NUM + 1];
char buf[NR_SHELL_CMD_HISTORY_BUF_LENGTH + 1];
} shell_his_queue_t;
typedef struct nr_shell
{
char user_name[NR_SHELL_USER_NAME_MAX_LENGTH];
const static_cmd_st *static_cmd;
shell_his_queue_st cmd_his;
} shell_st;
nr_shell 启动后,shell_st 结构体将作为 nr_shell 的实例对象维护 nr_shell 的运行环境。
根据结构体可知 shell_st 结构将记录当前用户的名称,当前运行的指令,以及运行过的历史指令。
当输入指令格式后,将通过 shell 函数进行解析处理。
对于指令的具体解析,nr_shell 提供了 ansi_st 进行处理。关于 ansi_st 结构体的作用如下:
ansi_st nr_ansi;
typedef struct nr_ansi_struct
{
short p;
unsigned int counter;
char current_line[NR_ANSI_LINE_SIZE];
char combine_buf[NR_ANSI_CTRL_MAX_LEN];
char cmd_num;
char combine_state;
} ansi_st;
void ansi_init(ansi_st *ansi)
{
ansi->counter = 0;
ansi->p = -1;
ansi->current_line[ansi->counter] = '\0';
ansi->cmd_num = 0;
ansi->combine_state = ANSI_NO_CTRL_CHAR;
}
接下来是完整的字符串解析过程:
对输入的字符串逐字解析,首先判断其是否为特殊符号,比如:'\b','\n','\033' 等,若不是则进行通用处理,否则参考对应的处理办法处理。
#define shell(c) \
{ \
if (ansi_get_char(c, &nr_ansi) == NR_SHELL_END_CHAR) \
{ \
shell_parser(&nr_shell, nr_ansi.current_line); \
ansi_clear_current_line(&nr_ansi); \
} \
}
char ansi_get_char(char x, ansi_st *ansi)
{
int cmd_id = -1;
if (ansi->combine_state == ANSI_NO_CTRL_CHAR)
{
cmd_id = ansi_search_char(x, nr_ansi_in_special_symbol);
if (cmd_id >= 0)
{
if (nr_ansi_in_special_symbol_fun[cmd_id] != NULL)
{
nr_ansi_in_special_symbol_fun[cmd_id](ansi);
}
}
else if (x == '\033')
{
ansi->combine_state = ANSI_WAIT_CTRL_CHAR_END;
ansi->combine_buf[ansi->cmd_num] = x;
ansi->cmd_num++;
}
else
{
nr_ansi_common_char_slover(ansi, x);
}
}
else if (ansi->combine_state == ANSI_WAIT_CTRL_CHAR_END)
{
ansi->combine_buf[ansi->cmd_num] = x;
if (('a' <= x && 'z' >= x) || ('A' <= x && 'Z' >= x) || x == '~')
{
cmd_id = ansi_search_char(x, nr_ansi_in_cmd);
nr_ansi_in_cmd_fun[cmd_id](ansi);
ansi->cmd_num = 0;
anis->combine_state = ANSI_NO_CTRL_CHAR;
}
else if (ansi->cmd_num > 18)
{
ansi->cmd_num = 0;
ansi->combine_state = ANSI_NO_CTRL_CHAR;
}
else
{
ansi->cmd_num++;
}
}
else
{
ansi->combine_state = ANSI_NO_CTRL_CHAR;
}
return x;
}
int ansi_search_char(char x, const char *buf)
{
int i = 0;
for (i = 0; (buf[i] != x) && (buf[i] != '\0'); i++)
;
if (buf[i] != '\0')
{
return i;
}
else
{
return -1;
}
}
void nr_ansi_common_char_slover(ansi_st *ansi, char x)
{
unsigned int i;
if (ansi->counter < NR_ANSI_LINE_SIZE - 2)
{
if (ansi->p < ansi->counter)
{
for (i = ansi->counter; i > ansi->p; i--)
{
ansi->current_line[i] = ansi->current_line[i - 1];
}
}
ansi->p++;
ansi->counter++;
ansi->current_line[ansi->p] = x;
ansi->current_line[ansi->counter] = '\0'
if (ansi->p + 1 < ansi->counter)
{
shell_printf("\033[1@");
}
#ifndef NR_MICRO_SHELL_SIMULATOR
ansi_show_char(x);
#endif
}
else
{
ansi->counter = NR_ANSI_LINE_SIZE - 3;
if (ansi->p >= ansi->counter)
{
ansi->p = ansi->counter - 1;
}
ansi->current_line[ansi->counter] = '\0';
}
}
void shell_parser(shell_st *shell, char *str)
{
char argc = 0;
char argv[NR_SHELL_CMD_LINE_MAX_LENGTH + NR_SHELL_CMD_PARAS_MAX_NUM];
char *token = str;
shell_fun_t fp;
char index = NR_SHELL_CMD_PARAS_MAX_NUM;
if (shell_his_queue_search_cmd(&shell->cmd_his, str) == 0 && str[0] != '\0')
{
shell_his_queue_add_cmd(&shell->cmd_his, str);
}
if (strlen(str) > NR_SHELL_CMD_LINE_MAX_LENGTH)
{
shell_printf("this command is too long."NR_SHELL_NEXT_LINE);
shell_printf("%s", shell->user_name);
return;
}
token = nr_shell_strtok(token, " ");
fp = shell_search_cmd(shell, str);
if (fp == NULL)
{
if (isalpha(str[0]))
{
shell_printf("no command named: %s"NR_SHELL_NEXT_LINE, token);
}
}
else
{
argv[argc] = index;
strcpy(argv + index, str);
index += strlen(str) + 1;
argc++;
token = nr_shell_strtok(NULL, " ");
while (token != NULL)
{
argv[argc] = index;
strcpy(argv + index, token);
index += strlen(token) + 1;
argc++;
token = nr_shell_strtok(NULL, " ");
}
}
if (fp != NULL)
{
fp(argc, argv);
}
}
关于 nr_shell 对指令的支持,主要通过框架中的全局变量 nr_shell 完成。
新指令目前不支持接口添加,只能直接在数组表中直接添加。
shell_st nr_shell = {
{
.user_name = NR_SHELL_USER_NAME,
.static_cmd = nr_cmd_start_add;
};
#define nr_cmd_start_add (&static_cmd[0])
const static_cmd_st static_cmd[] =
{
{"ls", shell_ls_cmd},
{"test", shell_test_cmd},
{"\0", NULL}
};
以上,就是对 nr_shell 框架的简单分析,只针对 nr_shell 的运作流程,以及新指令的添加。注:特殊指令,比如:方向键等未做分析。