Skip to content

Commit d24aa4f

Browse files
committed
feat(examples): add shell-like experience to advanced console example
Add Unix-like shell functionality to demonstrate console capabilities: - Implement tee, cat, echo, and other shell commands - Add esp_shell component for shell command execution - Enable task-based command execution for pipelines - Demonstrate pipe usage and I/O redirection Shows practical usage of console task API and VFS pipe features.
1 parent d1f35a8 commit d24aa4f

File tree

5 files changed

+353
-3
lines changed

5 files changed

+353
-3
lines changed

examples/system/console/advanced/components/cmd_system/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
idf_component_register(SRCS "cmd_system.c" "cmd_system_common.c"
1+
idf_component_register(SRCS "cmd_system.c" "cmd_system_common.c" "cmd_system_shell_common.c"
22
INCLUDE_DIRS .
33
REQUIRES console spi_flash esp_driver_uart esp_driver_gpio)
44

examples/system/console/advanced/components/cmd_system/cmd_system.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ void register_system_common(void);
2222
void register_system_deep_sleep(void);
2323
void register_system_light_sleep(void);
2424

25+
// Register common tools used in shell: cat, echo, grep, tail, tee
26+
void register_system_shell_common(void);
27+
28+
void register_system_shell_tee(void);
29+
void register_system_shell_cat(void);
30+
void register_system_shell_grep(void);
31+
void register_system_shell_echo(void);
32+
2533
#ifdef __cplusplus
2634
}
2735
#endif
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Unlicense OR CC0-1.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include <string.h>
9+
#include <errno.h>
10+
#include <ctype.h>
11+
#include <inttypes.h>
12+
#include "esp_log.h"
13+
#include "esp_console.h"
14+
#include "esp_chip_info.h"
15+
#include "esp_flash.h"
16+
#include "argtable3/argtable3.h"
17+
#include "freertos/FreeRTOS.h"
18+
#include "freertos/task.h"
19+
#include "cmd_system.h"
20+
#include "sdkconfig.h"
21+
22+
static struct {
23+
struct arg_str *path;
24+
struct arg_lit *append;
25+
struct arg_end *end;
26+
} tee_args;
27+
28+
29+
static int cmd_tee(int argc, char **argv)
30+
{
31+
int nerrors = arg_parse(argc, argv, (void **)&tee_args);
32+
if (nerrors != 0) {
33+
arg_print_errors(stderr, tee_args.end, argv[0]);
34+
return 1;
35+
}
36+
37+
if (tee_args.path->count != 1) {
38+
fprintf(stderr, "Please provide a single file path\n");
39+
return 1;
40+
}
41+
42+
const char *path = tee_args.path->sval[0];
43+
const char *mode = (tee_args.append->count > 0) ? "a" : "w";
44+
45+
FILE *file = fopen(path, mode);
46+
if (!file) {
47+
fprintf(stderr, "tee: %s: %s\n", path, strerror(errno));
48+
return 1;
49+
}
50+
51+
char buf[256];
52+
while (fgets(buf, sizeof(buf), stdin)) {
53+
// Write to stdout
54+
fputs(buf, stdout);
55+
fflush(stdout);
56+
57+
// Write to file
58+
fputs(buf, file);
59+
}
60+
61+
fclose(file);
62+
return 0;
63+
}
64+
65+
void register_system_shell_tee(void) {
66+
tee_args.path = arg_str1(NULL, NULL, "<path>", "File to write to");
67+
tee_args.append = arg_lit0("a", "append", "Append to file instead of overwriting");
68+
tee_args.end = arg_end(2);
69+
const esp_console_cmd_t tee_cmd = {
70+
.command = "tee",
71+
.help = "Read from stdin and write to file and stdout",
72+
.hint = NULL,
73+
.func = &cmd_tee,
74+
.argtable = &tee_args,
75+
};
76+
ESP_ERROR_CHECK(esp_console_cmd_register(&tee_cmd));
77+
}
78+
79+
80+
static struct {
81+
struct arg_str *path;
82+
struct arg_end *end;
83+
} cat_args;
84+
85+
int cmd_cat(int argc, char **argv)
86+
{
87+
int nerrors = arg_parse(argc, argv, (void **)&cat_args);
88+
if (nerrors != 0) {
89+
arg_print_errors(stderr, cat_args.end, argv[0]);
90+
return 1;
91+
}
92+
93+
if (cat_args.path->count != 1) {
94+
fprintf(stderr, "Please provide a single file path\n");
95+
return 1;
96+
}
97+
const char *path = cat_args.path->sval[0];
98+
99+
FILE *file = fopen(path, "r");
100+
if (!file) {
101+
fprintf(stderr, "File %s not found\n", path);
102+
return 2;
103+
}
104+
105+
while (true) {
106+
char buf[32];
107+
size_t bytes = fread(buf, 1, sizeof(buf), file);
108+
if (bytes < 1)
109+
break;
110+
fwrite(buf, 1, bytes, stdout);
111+
}
112+
113+
if (file) {
114+
fclose(file);
115+
}
116+
117+
return 0;
118+
}
119+
120+
void register_system_shell_cat(void) {
121+
cat_args.path = arg_str1(NULL, NULL, "<path>", "File to cat");
122+
cat_args.end = arg_end(2);
123+
const esp_console_cmd_t cat_cmd = {.command = "cat", .help = "cat file", .hint = NULL, .func = &cmd_cat, .argtable = &cat_args};
124+
ESP_ERROR_CHECK(esp_console_cmd_register(&cat_cmd));
125+
}
126+
127+
static struct {
128+
struct arg_str *pattern;
129+
struct arg_file *files;
130+
struct arg_lit *ignore_case;
131+
struct arg_lit *line_number;
132+
struct arg_lit *invert_match;
133+
struct arg_end *end;
134+
} grep_args;
135+
136+
static int strstr_case_insensitive(const char *haystack, const char *needle)
137+
{
138+
if (!*needle) {
139+
return 1; // empty needle matches
140+
}
141+
142+
for (const char *h = haystack; *h; h++) {
143+
const char *n = needle;
144+
const char *h_temp = h;
145+
146+
while (*h_temp && *n && (tolower((unsigned char)*h_temp) == tolower((unsigned char)*n))) {
147+
h_temp++;
148+
n++;
149+
}
150+
151+
if (!*n) {
152+
return 1; // found match
153+
}
154+
}
155+
return 0; // no match
156+
}
157+
158+
static int grep_line(const char *line, const char *pattern, bool ignore_case, bool invert_match)
159+
{
160+
int match;
161+
if (ignore_case) {
162+
match = strstr_case_insensitive(line, pattern);
163+
} else {
164+
match = (strstr(line, pattern) != NULL);
165+
}
166+
167+
if (invert_match) {
168+
return !match;
169+
}
170+
return match;
171+
}
172+
173+
static int grep_file(FILE *f, const char *pattern, bool ignore_case, bool line_number, bool invert_match, const char *filename_prefix)
174+
{
175+
char line[256];
176+
int line_num = 0;
177+
int match_count = 0;
178+
179+
while (fgets(line, sizeof(line), f)) {
180+
line_num++;
181+
// Remove trailing newline
182+
size_t len = strlen(line);
183+
if (len > 0 && line[len - 1] == '\n') {
184+
line[len - 1] = '\0';
185+
}
186+
187+
if (grep_line(line, pattern, ignore_case, invert_match)) {
188+
if (filename_prefix != NULL) {
189+
printf("%s:", filename_prefix);
190+
}
191+
if (line_number) {
192+
printf("%d:", line_num);
193+
}
194+
printf("%s\n", line);
195+
fflush(stdout);
196+
match_count++;
197+
}
198+
}
199+
if (ferror(f)) {
200+
// Don't report pipe errors (EPIPE) - that's normal ending when input is finished
201+
if (errno != EPIPE) {
202+
fprintf(stderr, "Error %d reading file\n", ferror(f));
203+
return 1;
204+
}
205+
}
206+
207+
return (match_count > 0) ? 0 : 1;
208+
}
209+
210+
static int cmd_grep(int argc, char **argv)
211+
{
212+
int nerrors = arg_parse(argc, argv, (void **)&grep_args);
213+
if (nerrors != 0) {
214+
arg_print_errors(stderr, grep_args.end, argv[0]);
215+
return 1;
216+
}
217+
218+
const char *pattern = grep_args.pattern->sval[0];
219+
bool ignore_case = grep_args.ignore_case->count > 0;
220+
bool line_number = grep_args.line_number->count > 0;
221+
bool invert_match = grep_args.invert_match->count > 0;
222+
223+
// If no files specified, read from stdin
224+
if (grep_args.files->count == 0) {
225+
return grep_file(stdin, pattern, ignore_case, line_number, invert_match, NULL);
226+
}
227+
228+
// Process files
229+
int ret = 0;
230+
bool show_filename = (grep_args.files->count > 1);
231+
232+
for (int i = 0; i < grep_args.files->count; i++) {
233+
const char *filename = grep_args.files->filename[i];
234+
FILE *f = fopen(filename, "r");
235+
if (f == NULL) {
236+
fprintf(stderr, "grep: %s: No such file or directory\n", filename);
237+
ret = 1;
238+
continue;
239+
}
240+
241+
int file_ret = grep_file(f, pattern, ignore_case, line_number, invert_match, show_filename ? filename : NULL);
242+
fclose(f);
243+
244+
if (file_ret != 0) {
245+
ret = file_ret;
246+
}
247+
}
248+
249+
return ret;
250+
}
251+
252+
void register_system_shell_grep(void) {
253+
grep_args.pattern = arg_str1(NULL, NULL, "PATTERN", "Search pattern");
254+
grep_args.files = arg_filen(NULL, NULL, "FILE", 0, 10, "Files to search (stdin if omitted)");
255+
grep_args.ignore_case = arg_lit0("i", "ignore-case", "Case-insensitive search");
256+
grep_args.line_number = arg_lit0("n", "line-number", "Print line numbers");
257+
grep_args.invert_match = arg_lit0("v", "invert-match", "Select non-matching lines");
258+
grep_args.end = arg_end(2);
259+
260+
const esp_console_cmd_t cmd = {
261+
.command = "grep",
262+
.help = "Search for PATTERN in files or stdin",
263+
.hint = NULL,
264+
.func = &cmd_grep,
265+
.argtable = &grep_args,
266+
};
267+
esp_console_cmd_register(&cmd);
268+
}
269+
270+
static int cmd_echo(int argc, char **argv)
271+
{
272+
for (int i = 1; i < argc; i++) {
273+
if (i == argc - 1)
274+
printf("%s\n", argv[i]);
275+
else
276+
printf("%s ", argv[i]);
277+
}
278+
return 0;
279+
}
280+
281+
void register_system_shell_echo(void) {
282+
const esp_console_cmd_t cmd = {
283+
.command = "echo",
284+
.help = "Echo arguments to stdout",
285+
.hint = NULL,
286+
.func = &cmd_echo,
287+
};
288+
esp_console_cmd_register(&cmd);
289+
}
290+
291+
static int cmd_true(int argc, char **argv) {
292+
return 0;
293+
}
294+
295+
void register_system_shell_true(void) {
296+
const esp_console_cmd_t cmd = {
297+
.command = "true",
298+
.help = "Do nothing, successfully",
299+
.hint = NULL,
300+
.func = &cmd_true,
301+
};
302+
esp_console_cmd_register(&cmd);
303+
}
304+
305+
static int cmd_false(int argc, char **argv) {
306+
return 1;
307+
}
308+
309+
void register_system_shell_false(void) {
310+
const esp_console_cmd_t cmd = {
311+
.command = "false",
312+
.help = "Do nothing, unsuccessfully",
313+
.hint = NULL,
314+
.func = &cmd_false,
315+
};
316+
esp_console_cmd_register(&cmd);
317+
}
318+
319+
void register_system_shell_common(void) {
320+
321+
register_system_shell_tee();
322+
register_system_shell_cat();
323+
register_system_shell_grep();
324+
register_system_shell_echo();
325+
register_system_shell_true();
326+
register_system_shell_false();
327+
328+
}

examples/system/console/advanced/main/console_example_main.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Unlicense OR CC0-1.0
55
*/
@@ -10,9 +10,12 @@
1010
#include "esp_system.h"
1111
#include "esp_log.h"
1212
#include "esp_console.h"
13+
#include "esp_shell.h"
14+
#include "esp_vfs_pipe.h"
1315
#include "linenoise/linenoise.h"
1416
#include "argtable3/argtable3.h"
1517
#include "esp_vfs_fat.h"
18+
#include "esp_vfs_pipe.h"
1619
#include "nvs.h"
1720
#include "nvs_flash.h"
1821
#include "soc/soc_caps.h"
@@ -82,6 +85,10 @@ void app_main(void)
8285
ESP_LOGI(TAG, "Command history disabled");
8386
#endif
8487

88+
/* Configure VFS to use pipe for console I/O */
89+
esp_vfs_pipe_config_t cfs_config = ESP_VFS_PIPE_CONFIG_DEFAULT();
90+
esp_vfs_pipe_register(&cfs_config);
91+
8592
/* Initialize console output periheral (UART, USB_OTG, USB_JTAG) */
8693
initialize_console_peripheral();
8794

@@ -96,6 +103,7 @@ void app_main(void)
96103
/* Register commands */
97104
esp_console_register_help_command();
98105
register_system_common();
106+
register_system_shell_common();
99107
#if SOC_LIGHT_SLEEP_SUPPORTED
100108
register_system_light_sleep();
101109
#endif
@@ -149,7 +157,7 @@ void app_main(void)
149157

150158
/* Try to run the command */
151159
int ret;
152-
esp_err_t err = esp_console_run(line, &ret);
160+
esp_err_t err = esp_shell_run(line, &ret);
153161
if (err == ESP_ERR_NOT_FOUND) {
154162
printf("Unrecognized command\n");
155163
} else if (err == ESP_ERR_INVALID_ARG) {

0 commit comments

Comments
 (0)