Zephyr shell通配符

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

问题 链接到标题

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

AT
AT+RST
AT+CWLAP

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

AT+CIPMUX?

分析 链接到标题

1. 功能代码 链接到标题

shell命令函数实现如下

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命令的时候进行通配符处理,以下只列出和通配符相关的处理流程

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提前结束子命令的解析。使得循环解析子命令提前结束,例如下面命令:

wifi iwcmd AT+CIPMUX

解析完后cmd_lvl为3 而

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.通配符处理流程 链接到标题

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