Zephyr shell通配符

Creative Commons
本作品采用知识共享署名

本文说明在使用zephyr shell遇到的通配符问题和处理方式。

问题

Zephyr原生支持通过UART对ESP8266和ESP32进行AT控制,由于UART的通信速度低,因此在Zephyr下添加了SPI AT,添加后想通过Zephyr的shell输入AT命令对其进行验证调试,开始执行了一些简单的命令都正常:

1
2
3
AT
AT+RST
AT+CWLAP

但当执行获得一些状态的AT命令时,就不会执行而直接退出

1
AT+CIPMUX?

分析

1. 功能代码

shell命令函数实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int wifi_cmd_iface_write_cmd(const struct shell *shell, size_t argc, char **argv)
{
#ifdef ENABLE_SPI_IFACE_TEST
if(argc < 2){
return 0;
}
sprintf(at_cmd, "%s\r\n", argv[1]);
shell_print(shell, "at cmd[%d]: %s",strlen(at_cmd), at_cmd);
iface.write(&iface, at_cmd, strlen(at_cmd));
#endif
return 0;
}

SHELL_CMD(iwcmd, NULL, "iface write cmd", wifi_cmd_iface_write_cmd),

也就是说,当我执行wifi iwcmd AT+CIPMUX?,预期argc应该等于2,argv[1]应该指向”AT+CIPUMX”, 但实际上得到的argc为1,导致直接退出。

2. 问题原因

由于只有带?的命令出问题,很自然的就想到和通配符相关。下面精简出Shell中通配符的处理流程:
shell.c shell_thread->shell_process->state_collect->execute, 最后在execute中处理shell命令,处理shell命令的时候进行通配符处理,以下只列出和通配符相关的处理流程

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
static int execute(const struct shell *shell)
{
...
//准备通配符buffer
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
shell_wildcard_prepare(shell);
}

//循环处理子命令
while ((argc != 1) && (cmd_lvl < CONFIG_SHELL_ARGC_MAX){
//将cmd line解析到argvp中
quote = shell_make_argv(&argc, argvp, cmd_buf, 2);
cmd_buf = (char *)argvp[1];

if (IS_ENABLED(CONFIG_SHELL_WILDCARD) && (cmd_lvl > 0)) {
enum shell_wildcard_status status;
//解析通配符
status = shell_wildcard_process(shell, entry,
argvp[0]);

//没有匹配到就直接退出循环
if (status == SHELL_WILDCARD_CMD_NO_MATCH_FOUND) {
break;
}

//有匹配项,子命令level增加cmd_lvl
if (status != SHELL_WILDCARD_NOT_FOUND) {
++cmd_lvl;
wildcard_found = true;
continue;
}
}
}

//结束通配符解析
if (IS_ENABLED(CONFIG_SHELL_WILDCARD) && wildcard_found) {
shell_wildcard_finalize(shell);
...
}

//执行最终命令
argv[cmd_lvl] = NULL;
return exec_cmd(shell, cmd_lvl - cmd_with_handler_lvl,
&argv[cmd_with_handler_lvl], &help_entry);
}

从上面可以看到cmd_lvl是在循环处理子命令的while中进行自加,一旦shell_wildcard_process返回SHELL_WILDCARD_CMD_NO_MATCH_FOUND就会break提前结束子命令的解析。使得循环解析子命令提前结束,例如下面命令:

1
wifi iwcmd AT+CIPMUX

解析完后cmd_lvl为3

1
wifi iwcmd AT+CIPMUX?

由于x?没有通配的子命令或者参数会提前break, cmd_lvl就为2.
以上两种情况cmd_with_handler_lvl都为1(第一个命令iwcmd对应有handle wifi_cmd_iface_write_cmd,具体流程不分析),因此对于第二种情况传入的argc就是cmd_lvl - cmd_with_handler_lvl = 1, 导致执行命令时拿不到”AT+CIPMUX?”。

3.通配符处理流程

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
enum shell_wildcard_status shell_wildcard_process(const struct shell *shell,
const struct shell_static_entry *cmd,
const char *pattern)
{
enum shell_wildcard_status ret_val = SHELL_WILDCARD_NOT_FOUND;

if (cmd == NULL) {
return ret_val;
}
//判断不存在通配符直接返回SHELL_WILDCARD_NOT_FOUND
if (!shell_wildcard_character_exist(pattern)) {
return ret_val;
}

//通配符匹配
ret_val = commands_expand(shell, cmd, pattern);

return ret_val;
}

//zephyr shell的通配符是?和*
bool shell_wildcard_character_exist(const char *str)
{
uint16_t str_len = shell_strlen(str);

for (size_t i = 0; i < str_len; i++) {
if ((str[i] == '?') || (str[i] == '*')) {
return true;
}
}

return false;
}

static enum shell_wildcard_status commands_expand(const struct shell *shell,
const struct shell_static_entry *cmd,
const char *pattern)
{
enum shell_wildcard_status ret_val = SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
struct shell_static_entry const *entry = NULL;
struct shell_static_entry dloc;
size_t cmd_idx = 0;
size_t cnt = 0;

//逐个获取命令
while ((entry = shell_cmd_get(cmd, cmd_idx++, &dloc)) != NULL) {
//将命令和通配符做fnmatch
if (fnmatch(pattern, entry->syntax, 0) == 0) {
//匹配上的加入到匹配命令buffer中
ret_val = command_add(shell->ctx->temp_buff,
&shell->ctx->cmd_tmp_buff_len,
entry->syntax, pattern);
...
cnt++;
}
};

if (cnt > 0) {
shell_pattern_remove(shell->ctx->temp_buff,
&shell->ctx->cmd_tmp_buff_len, pattern);
}

//如果前面流程一个都没匹配上则返回SHELL_WILDCARD_CMD_NO_MATCH_FOUND,也就是我们遇到的情况
return ret_val;
}

处理

处理方式有三种:

  1. 配置CONFIG_SHELL_WILDCARD=n关掉通配符
  2. shell_wildcard_character_exist中?通配符移掉
  3. 在shell实现中为?添加转义

因为我没有使用通配符的需求,添加转义改动也不小。因此采用了方式1,最简单又没有破坏性的修改。

参考

https://docs.zephyrproject.org/latest/reference/shell/index.html#wildcards-feature