Skip to content

Commit 2b6e2f8

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 904b23e commit 2b6e2f8

File tree

5 files changed

+408
-2
lines changed

5 files changed

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

0 commit comments

Comments
 (0)