Skip to content

Commit f4857cc

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 91006c3 commit f4857cc

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

0 commit comments

Comments
 (0)