Skip to content

Commit f39563d

Browse files
committed
shell: Add wildcard support
Extended shell to support wildcard characters: * and ? and expand commands accordingly. Increased default stack size. Signed-off-by: Jakub Rzeszutko <[email protected]> Signed-off-by: Krzysztof Chruscinski <[email protected]>
1 parent bae2c41 commit f39563d

File tree

5 files changed

+358
-1
lines changed

5 files changed

+358
-1
lines changed

subsys/shell/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ zephyr_sources_ifdef(
3333
CONFIG_LOG
3434
shell_log_backend.c
3535
)
36+
37+
zephyr_sources_ifdef(
38+
CONFIG_SHELL_WILDCARD
39+
shell_wildcard.c
40+
)

subsys/shell/Kconfig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ config SHELL_LOG_LEVEL
4747

4848
config SHELL_STACK_SIZE
4949
int "Shell thread stack size"
50-
default 1024 if MULTITHREADING
50+
default 2048 if MULTITHREADING
5151
default 0 if !MULTITHREADING
5252
help
5353
Stack size for thread created for each instance.
@@ -89,6 +89,12 @@ config SHELL_ARGC_MAX
8989
If command is composed of more than defined, argument SHELL_ARGC_MAX
9090
and following are passed as one argument in the string.
9191

92+
config SHELL_WILDCARD
93+
bool "Enable wildcard support in shell"
94+
select FNMATCH
95+
help
96+
Enables using * in shell.
97+
9298
config SHELL_ECHO_STATUS
9399
bool "Enable echo on shell"
94100
default y

subsys/shell/shell.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <shell/shell.h>
1111
#include "shell_utils.h"
1212
#include "shell_ops.h"
13+
#include "shell_wildcard.h"
1314
#include "shell_vt100.h"
1415
#include <assert.h>
1516
#include <atomic.h>
@@ -381,6 +382,15 @@ static const struct shell_static_entry *get_last_command(
381382
*match_arg = SHELL_CMD_ROOT_LVL;
382383

383384
while (*match_arg < argc) {
385+
386+
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
387+
/* ignore wildcard argument */
388+
if (shell_wildcard_character_exist(argv[*match_arg])) {
389+
(*match_arg)++;
390+
continue;
391+
}
392+
}
393+
384394
entry = find_cmd(prev_cmd, *match_arg, argv[*match_arg],
385395
d_entry);
386396
if (entry) {
@@ -922,6 +932,7 @@ static void shell_execute(const struct shell *shell)
922932
const struct shell_cmd_entry *p_cmd = NULL;
923933
size_t cmd_lvl = SHELL_CMD_ROOT_LVL;
924934
size_t cmd_with_handler_lvl = 0;
935+
bool wildcard_found = false;
925936
size_t cmd_idx;
926937
size_t argc;
927938
char quote;
@@ -938,6 +949,10 @@ static void shell_execute(const struct shell *shell)
938949
history_put(shell, shell->ctx->cmd_buff,
939950
shell->ctx->cmd_buff_len);
940951

952+
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
953+
shell_wildcard_prepare(shell);
954+
}
955+
941956
/* create argument list */
942957
quote = shell_make_argv(&argc, &argv[0], shell->ctx->cmd_buff,
943958
CONFIG_SHELL_ARGC_MAX);
@@ -985,6 +1000,28 @@ static void shell_execute(const struct shell *shell)
9851000
break;
9861001
}
9871002

1003+
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
1004+
enum shell_wildcard_status status;
1005+
1006+
status = shell_wildcard_process(shell, p_cmd,
1007+
argv[cmd_lvl]);
1008+
/* Wildcard character found but there is no matching
1009+
* command.
1010+
*/
1011+
if (status == SHELL_WILDCARD_CMD_NO_MATCH_FOUND) {
1012+
break;
1013+
}
1014+
1015+
/* Wildcard character was not found function can process
1016+
* argument.
1017+
*/
1018+
if (status != SHELL_WILDCARD_NOT_FOUND) {
1019+
++cmd_lvl;
1020+
wildcard_found = true;
1021+
continue;
1022+
}
1023+
}
1024+
9881025
cmd_get(p_cmd, cmd_lvl, cmd_idx++, &p_static_entry, &d_entry);
9891026

9901027
if ((cmd_idx == 0) || (p_static_entry == NULL)) {
@@ -994,6 +1031,28 @@ static void shell_execute(const struct shell *shell)
9941031
if (strcmp(argv[cmd_lvl], p_static_entry->syntax) == 0) {
9951032
/* checking if command has a handler */
9961033
if (p_static_entry->handler != NULL) {
1034+
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
1035+
if (wildcard_found) {
1036+
shell_op_cursor_end_move(shell);
1037+
shell_op_cond_next_line(shell);
1038+
1039+
/* An error occurred, fnmatch
1040+
* argument cannot be followed
1041+
* by argument with a handler to
1042+
* avoid multiple function
1043+
* calls.
1044+
*/
1045+
shell_fprintf(shell,
1046+
SHELL_ERROR,
1047+
"Error: requested"
1048+
" multiple function"
1049+
" executions\r\n");
1050+
help_flag_clear(shell);
1051+
1052+
return;
1053+
}
1054+
}
1055+
9971056
shell->ctx->active_cmd = *p_static_entry;
9981057
cmd_with_handler_lvl = cmd_lvl;
9991058
}
@@ -1004,6 +1063,17 @@ static void shell_execute(const struct shell *shell)
10041063
}
10051064
}
10061065

1066+
if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) {
1067+
shell_wildcard_finalize(shell);
1068+
/* cmd_buffer has been overwritten by function finalize function
1069+
* with all expanded commands. Hence shell_make_argv needs to
1070+
* be called again.
1071+
*/
1072+
(void)shell_make_argv(&argc, &argv[0],
1073+
shell->ctx->cmd_buff,
1074+
CONFIG_SHELL_ARGC_MAX);
1075+
}
1076+
10071077
/* Executing the deepest found handler. */
10081078
if (shell->ctx->active_cmd.handler == NULL) {
10091079
if (shell->ctx->active_cmd.help) {

subsys/shell/shell_wildcard.c

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* Copyright (c) 2018 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <string.h>
8+
#include <fnmatch.h>
9+
#include "shell_wildcard.h"
10+
#include "shell_utils.h"
11+
12+
static void subcmd_get(const struct shell_cmd_entry *cmd,
13+
size_t idx, const struct shell_static_entry **entry,
14+
struct shell_static_entry *d_entry)
15+
{
16+
assert(entry != NULL);
17+
assert(st_entry != NULL);
18+
19+
if (cmd == NULL) {
20+
*entry = NULL;
21+
return;
22+
}
23+
24+
if (cmd->is_dynamic) {
25+
cmd->u.dynamic_get(idx, d_entry);
26+
*entry = (d_entry->syntax != NULL) ? d_entry : NULL;
27+
} else {
28+
*entry = (cmd->u.entry[idx].syntax != NULL) ?
29+
&cmd->u.entry[idx] : NULL;
30+
}
31+
}
32+
33+
static enum shell_wildcard_status command_add(char *buff, u16_t *buff_len,
34+
char const *cmd,
35+
char const *pattern)
36+
{
37+
u16_t cmd_len = shell_strlen(cmd);
38+
char *completion_addr;
39+
u16_t shift;
40+
41+
/* +1 for space */
42+
if ((*buff_len + cmd_len + 1) > CONFIG_SHELL_CMD_BUFF_SIZE) {
43+
return SHELL_WILDCARD_CMD_MISSING_SPACE;
44+
}
45+
46+
completion_addr = strstr(buff, pattern);
47+
48+
if (!completion_addr) {
49+
return SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
50+
}
51+
52+
shift = shell_strlen(completion_addr);
53+
54+
/* make place for new command: + 1 for space + 1 for EOS */
55+
memmove(completion_addr + cmd_len + 1, completion_addr, shift + 1);
56+
memcpy(completion_addr, cmd, cmd_len);
57+
/* adding space to not brake next command in the buffer */
58+
completion_addr[cmd_len] = ' ';
59+
60+
*buff_len += cmd_len + 1; /* + 1 for space */
61+
62+
return SHELL_WILDCARD_CMD_ADDED;
63+
}
64+
65+
/**
66+
* @internal @brief Function for searching and adding commands to the temporary
67+
* shell buffer matching to wildcard pattern.
68+
*
69+
* Function will search commands tree for commands matching wildcard pattern
70+
* stored in argv[cmd_lvl]. When match is found wildcard pattern will be
71+
* replaced by matching commands. If there is no space in the buffer to add all
72+
* matching commands function will add as many as possible. Next it will
73+
* continue to search for next wildcard pattern and it will try to add matching
74+
* commands.
75+
*
76+
*
77+
* This function is internal to shell module and shall be not called directly.
78+
*
79+
* @param[in/out] shell Pointer to the CLI instance.
80+
* @param[in] cmd Pointer to command which will be processed
81+
* @param[in] pattern Pointer to wildcard pattern.
82+
*
83+
* @retval WILDCARD_CMD_ADDED All matching commands added to the buffer.
84+
* @retval WILDCARD_CMD_ADDED_MISSING_SPACE Not all matching commands added
85+
* because CONFIG_SHELL_CMD_BUFF_SIZE
86+
* is too small.
87+
* @retval WILDCARD_CMD_NO_MATCH_FOUND No matching command found.
88+
*/
89+
static enum shell_wildcard_status commands_expand(const struct shell *shell,
90+
const struct shell_cmd_entry *cmd,
91+
const char *pattern)
92+
{
93+
enum shell_wildcard_status ret_val = SHELL_WILDCARD_CMD_NO_MATCH_FOUND;
94+
struct shell_static_entry const *p_static_entry = NULL;
95+
struct shell_static_entry static_entry;
96+
size_t cmd_idx = 0;
97+
size_t cnt = 0;
98+
99+
do {
100+
subcmd_get(cmd, cmd_idx++, &p_static_entry, &static_entry);
101+
102+
if (!p_static_entry) {
103+
break;
104+
}
105+
106+
if (fnmatch(pattern, p_static_entry->syntax, 0) == 0) {
107+
ret_val = command_add(shell->ctx->temp_buff,
108+
&shell->ctx->cmd_tmp_buff_len,
109+
p_static_entry->syntax, pattern);
110+
if (ret_val == SHELL_WILDCARD_CMD_MISSING_SPACE) {
111+
shell_fprintf(shell,
112+
SHELL_WARNING,
113+
"Command buffer is too short to"
114+
" expand all commands matching"
115+
" wildcard pattern: %s\r\n",
116+
pattern);
117+
break;
118+
} else if (ret_val != SHELL_WILDCARD_CMD_ADDED) {
119+
break;
120+
}
121+
cnt++;
122+
}
123+
} while (cmd_idx);
124+
125+
if (cnt > 0) {
126+
shell_pattern_remove(shell->ctx->temp_buff,
127+
&shell->ctx->cmd_tmp_buff_len, pattern);
128+
}
129+
130+
return ret_val;
131+
}
132+
133+
bool shell_wildcard_character_exist(const char *str)
134+
{
135+
size_t str_len = shell_strlen(str);
136+
137+
for (size_t i = 0; i < str_len; i++) {
138+
if ((str[i] == '?') || (str[i] == '*')) {
139+
return true;
140+
}
141+
}
142+
143+
return false;
144+
}
145+
146+
void shell_wildcard_prepare(const struct shell *shell)
147+
{
148+
/* Wildcard can be correctly handled under following conditions:
149+
* - wildcard command does not have a handler
150+
* - wildcard command is on the deepest commands level
151+
* - other commands on the same level as wildcard command shall also not
152+
* have a handler
153+
*
154+
* Algorithm:
155+
* 1. Command buffer: ctx->cmd_buff is copied to temporary buffer:
156+
* ctx->temp_buff.
157+
* 2. Algorithm goes through command buffer to find handlers and
158+
* subcommands.
159+
* 3. If algorithm will find a wildcard character it switches to
160+
* Temporary buffer.
161+
* 4. In the Temporary buffer command containing wildcard character is
162+
* replaced by matching command(s).
163+
* 5. Algorithm switch back to Command buffer and analyzes next command.
164+
* 6. When all arguments are analyzed from Command buffer, Temporary
165+
* buffer with all expanded commands is copied to Command buffer.
166+
* 7. Deepest found handler is executed and all lower level commands,
167+
* including expanded commands, are passed as arguments.
168+
*/
169+
170+
memset(shell->ctx->temp_buff, 0, sizeof(shell->ctx->temp_buff));
171+
memcpy(shell->ctx->temp_buff,
172+
shell->ctx->cmd_buff,
173+
shell->ctx->cmd_buff_len);
174+
175+
/* Function shell_spaces_trim must be used instead of shell_make_argv.
176+
* At this point it is important to keep temp_buff as one string.
177+
* It will allow to find wildcard commands easily with strstr function.
178+
*/
179+
shell_spaces_trim(shell->ctx->temp_buff);
180+
181+
/* +1 for EOS*/
182+
shell->ctx->cmd_tmp_buff_len = shell_strlen(shell->ctx->temp_buff) + 1;
183+
}
184+
185+
186+
enum shell_wildcard_status shell_wildcard_process(const struct shell *shell,
187+
const struct shell_cmd_entry *cmd,
188+
const char *pattern)
189+
{
190+
enum shell_wildcard_status ret_val = SHELL_WILDCARD_NOT_FOUND;
191+
192+
if (cmd == NULL) {
193+
return ret_val;
194+
}
195+
196+
if (!shell_wildcard_character_exist(pattern)) {
197+
return ret_val;
198+
}
199+
200+
/* Function will search commands tree for commands matching wildcard
201+
* pattern stored in argv[cmd_lvl]. When match is found wildcard pattern
202+
* will be replaced by matching commands. If there is no space in the
203+
* buffer to add all matching commands function will add as many as
204+
* possible. Next it will continue to search for next wildcard pattern
205+
* and it will try to add matching commands.
206+
*/
207+
ret_val = commands_expand(shell, cmd, pattern);
208+
209+
return ret_val;
210+
}
211+
212+
void shell_wildcard_finalize(const struct shell *shell)
213+
{
214+
memcpy(shell->ctx->cmd_buff,
215+
shell->ctx->temp_buff,
216+
shell->ctx->cmd_tmp_buff_len);
217+
shell->ctx->cmd_buff_len = shell->ctx->cmd_tmp_buff_len;
218+
}

0 commit comments

Comments
 (0)