Skip to content

Commit f19ccfe

Browse files
Merge pull request #3 from Awesome-Embedded-Learning-Studio/feat/phase1
feat: 16 new tools
2 parents 0e35df3 + 4ab1f40 commit f19ccfe

64 files changed

Lines changed: 4140 additions & 24 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ include(cmake/Config.cmake)
2020

2121
# ── Applet sources (conditional on config) ───────────────────
2222
set(CFBOX_APPLET_SOURCES)
23+
24+
# Multi-file applets
25+
if(CFBOX_ENABLE_SH)
26+
file(GLOB_RECURSE SH_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/applets/sh/*.cpp)
27+
list(APPEND CFBOX_APPLET_SOURCES ${SH_SOURCES})
28+
endif()
29+
30+
# Single-file applets
2331
foreach(applet IN LISTS CFBOX_APPLETS)
2432
string(TOUPPER "${applet}" APPLET_UPPER)
25-
if(CFBOX_ENABLE_${APPLET_UPPER})
33+
if(NOT applet STREQUAL "sh" AND CFBOX_ENABLE_${APPLET_UPPER})
2634
list(APPEND CFBOX_APPLET_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/applets/${applet}.cpp)
2735
endif()
2836
endforeach()

Roadmap.md

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Context
44

5-
CFBox 是一个 C++23 BusyBox 替代品,当前版本有 17 个 applet(echo, printf, cat, head, tail, wc, sort, uniq, grep, sed, mkdir, rm, cp, mv, ls, find, init)。项目使用注册表分发模式(`APPLET_REGISTRY`)、`std::expected` 错误处理、自定义参数解析器,CI 覆盖原生构建、交叉编译和 QEMU 测试。
5+
CFBox 是一个 C++23 BusyBox 替代品,当前版本有 33 个 applet。项目使用注册表分发模式(`APPLET_REGISTRY`)、`std::expected` 错误处理、自定义参数解析器,CI 覆盖原生构建、交叉编译和 QEMU 测试。
66

77
**目标**:全面对齐 BusyBox,覆盖嵌入式、容器、救援和通用场景。Shell 是最关键的组件,必须最先实现。
88

@@ -15,7 +15,7 @@ CFBox 是一个 C++23 BusyBox 替代品,当前版本有 17 个 applet(echo,
1515
| 阶段 | 主题 | 新增 Applet | 核心基础设施 | 累计 |
1616
|------|------|------------|-------------|------|
1717
| 0 | 构建系统现代化 ✅ | 0 | CMake 配置、help 系统、UTF-8、彩色输出 | 17 |
18-
| 1 | POSIX Shell + Coreutils I | ~17 | Shell 引擎、进程管理、信号处理 | ~34 |
18+
| 1 | POSIX Shell + Coreutils I | ~17 | Shell 引擎、进程管理、信号处理 | ~34 |
1919
| 2 | Coreutils II + findutils | ~41 | 流处理管线、校验和框架 | ~75 |
2020
| 3 | 编辑器 + 归档 + 压缩 | ~15 | 终端抽象、压缩框架 | ~90 |
2121
| 4 | 进程/Init + util-linux | ~38 | /proc 解析器、TUI 框架 | ~128 |
@@ -48,32 +48,33 @@ CFBox 是一个 C++23 BusyBox 替代品,当前版本有 17 个 applet(echo,
4848

4949
---
5050

51-
## Phase 1:POSIX Shell + Coreutils 第一批(最高优先级)
51+
## Phase 1:POSIX Shell + Coreutils 第一批
5252

5353
**目标**:实现交互式 POSIX Shell——这是在真实 Linux 上使用的基础。同时实现简单的 coreutils 建立势头。
5454

55-
### Shell 架构(`src/applets/sh/`
55+
### Coreutils 第一批 ✅
5656

57-
Shell 是最复杂的单一组件,按模块拆分
57+
16 个简单 coreutils 已完成,覆盖脚本基础所需
5858

59-
| 模块 | 文件 | 功能 |
59+
`basename` ✅, `dirname` ✅, `true` ✅, `false` ✅, `yes` ✅, `sleep` ✅, `pwd` ✅, `tty` ✅, `uname` ✅, `whoami` ✅, `hostname` ✅, `id` ✅, `logname` ✅, `nproc` ✅, `test` ✅, `link`
60+
61+
### POSIX Shell ✅
62+
63+
Shell 已实现为第一个多文件 applet(`src/applets/sh/`,8 个模块,~2400 行):
64+
65+
| 模块 | 文件 | 状态 |
6066
|------|------|------|
61-
| 词法分析 | `sh_lexer.cpp` | 分词、引号处理、here-document |
62-
| 语法解析 | `sh_parser.cpp` | AST 构建:管道、列表、复合命令、函数定义 |
63-
| 执行器 | `sh_executor.cpp` | AST 遍历执行,fork/exec,管道/重定向设置 |
64-
| 内建命令 | `sh_builtins.cpp` | cd, export, exit, test, read, trap, umask, eval, source 等 |
65-
| 行编辑 | `sh_lineedit.cpp` | 原生实现(不依赖 readline):行编辑、历史记录、Tab 补全 |
66-
| 作业控制 | `sh_jobs.cpp` | 进程组、会话管理、jobs/fg/bg、Ctrl+Z |
67-
| 变量系统 | `sh_vars.cpp` | 环境变量、Shell 变量、特殊参数、位置参数 |
68-
| 词展开 | `sh_expand.cpp` | 波浪号、参数展开、命令替换、算术展开、文件名展开 |
69-
70-
### 需要的基础设施
71-
- **进程管理** `include/cfbox/process.hpp`:fork/exec/pipe/dup2/waitpid RAII 封装
72-
- **信号处理** `include/cfbox/signal.hpp`:RAII 信号处理器
73-
74-
### Coreutils 第一批(简单 applet,50-200 行/个)
75-
76-
`basename`, `dirname`, `true`, `false`, `yes`, `sleep`, `pwd`, `tty`, `uname`, `whoami`, `hostname`, `id`, `logname`, `nproc`, `test`, `link`
67+
| 共享类型 | `sh.hpp` | ✅ AST 节点、Token、ShellState |
68+
| 词法分析 | `sh_lexer.cpp` | ✅ 分词、引号、$展开、运算符 |
69+
| 语法解析 | `sh_parser.cpp` | ✅ 递归下降:管道、列表、if/while/for、子shell、花括号组 |
70+
| 执行器 | `sh_executor.cpp` | ✅ fork/exec/pipe、重定向 < > >>、内置命令 |
71+
| 内建命令 | `sh_builtins.cpp` | ✅ echo/cd/pwd/export/exit/set/unset/shift/read/eval/source |
72+
| 词展开 | `sh_expand.cpp` | ✅ $VAR、${VAR}、$?、$$、$#/$@/$*、命令替换、波浪号、glob |
73+
| 变量系统 | `sh_vars.cpp` | ✅ 变量存储、环境同步、位置参数、特殊参数 |
74+
75+
**已实现功能**:管道 `|`、重定向 `< > >>``&&` `||``if/elif/else/fi``while/until/do/done``for/in/do/done`、子shell `()`、花括号组 `{}`、变量赋值、变量展开、命令替换 `$(...)`、tilde 展开、glob 展开、15 个内置命令。
76+
77+
**待后续迭代**:here-document `<<`、函数定义、算术展开 `$(( ))`、行编辑(readline 风格)、作业控制(jobs/fg/bg)、`case/esac`
7778

7879
### 验证
7980
- `./cfbox sh -c "echo hello | wc -l"` 管道工作

cmake/Config.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ include_guard()
66
set(CFBOX_APPLETS
77
echo printf cat head tail wc sort uniq
88
mkdir rm cp mv ls grep find sed init
9+
true false yes pwd
10+
basename dirname uname nproc link
11+
hostname logname whoami tty
12+
sleep id test
13+
sh
914
)
1015

1116
foreach(applet IN LISTS CFBOX_APPLETS)

include/cfbox/applet_config.hpp.in

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,20 @@
2121
#cmakedefine01 CFBOX_ENABLE_FIND
2222
#cmakedefine01 CFBOX_ENABLE_SED
2323
#cmakedefine01 CFBOX_ENABLE_INIT
24+
#cmakedefine01 CFBOX_ENABLE_TRUE
25+
#cmakedefine01 CFBOX_ENABLE_FALSE
26+
#cmakedefine01 CFBOX_ENABLE_YES
27+
#cmakedefine01 CFBOX_ENABLE_PWD
28+
#cmakedefine01 CFBOX_ENABLE_BASENAME
29+
#cmakedefine01 CFBOX_ENABLE_DIRNAME
30+
#cmakedefine01 CFBOX_ENABLE_UNAME
31+
#cmakedefine01 CFBOX_ENABLE_NPROC
32+
#cmakedefine01 CFBOX_ENABLE_LINK
33+
#cmakedefine01 CFBOX_ENABLE_HOSTNAME
34+
#cmakedefine01 CFBOX_ENABLE_LOGNAME
35+
#cmakedefine01 CFBOX_ENABLE_WHOAMI
36+
#cmakedefine01 CFBOX_ENABLE_TTY
37+
#cmakedefine01 CFBOX_ENABLE_SLEEP
38+
#cmakedefine01 CFBOX_ENABLE_ID
39+
#cmakedefine01 CFBOX_ENABLE_TEST
40+
#cmakedefine01 CFBOX_ENABLE_SH

include/cfbox/applets.hpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,57 @@ extern auto sed_main(int argc, char* argv[]) -> int;
5555
#if CFBOX_ENABLE_INIT
5656
extern auto init_main(int argc, char* argv[]) -> int;
5757
#endif
58+
#if CFBOX_ENABLE_TRUE
59+
extern auto true_main(int argc, char* argv[]) -> int;
60+
#endif
61+
#if CFBOX_ENABLE_FALSE
62+
extern auto false_main(int argc, char* argv[]) -> int;
63+
#endif
64+
#if CFBOX_ENABLE_YES
65+
extern auto yes_main(int argc, char* argv[]) -> int;
66+
#endif
67+
#if CFBOX_ENABLE_PWD
68+
extern auto pwd_main(int argc, char* argv[]) -> int;
69+
#endif
70+
#if CFBOX_ENABLE_BASENAME
71+
extern auto basename_main(int argc, char* argv[]) -> int;
72+
#endif
73+
#if CFBOX_ENABLE_DIRNAME
74+
extern auto dirname_main(int argc, char* argv[]) -> int;
75+
#endif
76+
#if CFBOX_ENABLE_UNAME
77+
extern auto uname_main(int argc, char* argv[]) -> int;
78+
#endif
79+
#if CFBOX_ENABLE_NPROC
80+
extern auto nproc_main(int argc, char* argv[]) -> int;
81+
#endif
82+
#if CFBOX_ENABLE_LINK
83+
extern auto link_main(int argc, char* argv[]) -> int;
84+
#endif
85+
#if CFBOX_ENABLE_HOSTNAME
86+
extern auto hostname_main(int argc, char* argv[]) -> int;
87+
#endif
88+
#if CFBOX_ENABLE_LOGNAME
89+
extern auto logname_main(int argc, char* argv[]) -> int;
90+
#endif
91+
#if CFBOX_ENABLE_WHOAMI
92+
extern auto whoami_main(int argc, char* argv[]) -> int;
93+
#endif
94+
#if CFBOX_ENABLE_TTY
95+
extern auto tty_main(int argc, char* argv[]) -> int;
96+
#endif
97+
#if CFBOX_ENABLE_SLEEP
98+
extern auto sleep_main(int argc, char* argv[]) -> int;
99+
#endif
100+
#if CFBOX_ENABLE_ID
101+
extern auto id_main(int argc, char* argv[]) -> int;
102+
#endif
103+
#if CFBOX_ENABLE_TEST
104+
extern auto test_main(int argc, char* argv[]) -> int;
105+
#endif
106+
#if CFBOX_ENABLE_SH
107+
extern auto sh_main(int argc, char* argv[]) -> int;
108+
#endif
58109

59110
// registry — one line per applet, conditionally compiled
60111
constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
@@ -109,4 +160,56 @@ constexpr auto APPLET_REGISTRY = std::to_array<cfbox::applet::AppEntry>({
109160
#if CFBOX_ENABLE_INIT
110161
{"init", init_main, "system init for boot testing (PID 1)"},
111162
#endif
163+
#if CFBOX_ENABLE_TRUE
164+
{"true", true_main, "do nothing, exit with status 0"},
165+
#endif
166+
#if CFBOX_ENABLE_FALSE
167+
{"false", false_main, "do nothing, exit with status 1"},
168+
#endif
169+
#if CFBOX_ENABLE_YES
170+
{"yes", yes_main, "output a string repeatedly until killed"},
171+
#endif
172+
#if CFBOX_ENABLE_PWD
173+
{"pwd", pwd_main, "print working directory"},
174+
#endif
175+
#if CFBOX_ENABLE_BASENAME
176+
{"basename", basename_main, "strip directory and suffix from file names"},
177+
#endif
178+
#if CFBOX_ENABLE_DIRNAME
179+
{"dirname", dirname_main, "strip last component from file name"},
180+
#endif
181+
#if CFBOX_ENABLE_UNAME
182+
{"uname", uname_main, "print system information"},
183+
#endif
184+
#if CFBOX_ENABLE_NPROC
185+
{"nproc", nproc_main, "print number of available processors"},
186+
#endif
187+
#if CFBOX_ENABLE_LINK
188+
{"link", link_main, "create a hard link"},
189+
#endif
190+
#if CFBOX_ENABLE_HOSTNAME
191+
{"hostname", hostname_main, "show or set the system host name"},
192+
#endif
193+
#if CFBOX_ENABLE_LOGNAME
194+
{"logname", logname_main, "print the user's login name"},
195+
#endif
196+
#if CFBOX_ENABLE_WHOAMI
197+
{"whoami", whoami_main, "print effective user ID"},
198+
#endif
199+
#if CFBOX_ENABLE_TTY
200+
{"tty", tty_main, "print the file name of the terminal connected to stdin"},
201+
#endif
202+
#if CFBOX_ENABLE_SLEEP
203+
{"sleep", sleep_main, "delay for a specified amount of time"},
204+
#endif
205+
#if CFBOX_ENABLE_ID
206+
{"id", id_main, "print real and effective user and group IDs"},
207+
#endif
208+
#if CFBOX_ENABLE_TEST
209+
{"test", test_main, "evaluate conditional expression"},
210+
{"[", test_main, "evaluate conditional expression"},
211+
#endif
212+
#if CFBOX_ENABLE_SH
213+
{"sh", sh_main, "POSIX shell command interpreter"},
214+
#endif
112215
});

include/cfbox/fs_util.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ inline auto rename(std::string_view old_path, std::string_view new_path) -> base
112112
return {};
113113
}
114114

115+
inline auto create_hard_link(std::string_view target, std::string_view link_path) -> base::Result<void> {
116+
std::error_code ec;
117+
std::filesystem::create_hard_link(
118+
std::filesystem::path{target},
119+
std::filesystem::path{link_path},
120+
ec);
121+
if (ec) {
122+
return std::unexpected(base::Error{static_cast<int>(ec.value()), ec.message()});
123+
}
124+
return {};
125+
}
126+
115127
inline auto current_path() -> base::Result<std::string> {
116128
std::error_code ec;
117129
auto p = std::filesystem::current_path(ec);

src/applets/basename.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <cstdio>
2+
#include <string>
3+
#include <string_view>
4+
5+
#include <cfbox/args.hpp>
6+
#include <cfbox/help.hpp>
7+
8+
namespace {
9+
constexpr cfbox::help::HelpEntry HELP = {
10+
.name = "basename",
11+
.version = CFBOX_VERSION_STRING,
12+
.one_line = "strip directory and suffix from file names",
13+
.usage = "basename PATH [SUFFIX]",
14+
.options = "",
15+
.extra = "",
16+
};
17+
18+
auto do_basename(std::string_view path, std::string_view suffix) -> std::string {
19+
// Remove trailing slashes (but keep root "/")
20+
while (path.size() > 1 && path.back() == '/') {
21+
path.remove_suffix(1);
22+
}
23+
24+
std::string result;
25+
if (path.empty()) {
26+
result = ".";
27+
} else if (path == "/") {
28+
result = "/";
29+
} else {
30+
// Find last component after last '/'
31+
auto pos = path.rfind('/');
32+
if (pos == std::string_view::npos) {
33+
result = std::string{path};
34+
} else {
35+
result = std::string{path.substr(pos + 1)};
36+
}
37+
}
38+
39+
// Strip suffix if given and result ends with it (but not if suffix == result)
40+
if (!suffix.empty() && result.size() > suffix.size()) {
41+
if (result.compare(result.size() - suffix.size(), suffix.size(), suffix) == 0) {
42+
result.erase(result.size() - suffix.size());
43+
}
44+
}
45+
46+
return result;
47+
}
48+
49+
} // namespace
50+
51+
auto basename_main(int argc, char* argv[]) -> int {
52+
auto parsed = cfbox::args::parse(argc, argv, {});
53+
54+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
55+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
56+
57+
const auto& pos = parsed.positional();
58+
if (pos.empty()) {
59+
std::fprintf(stderr, "cfbox basename: missing operand\n");
60+
return 1;
61+
}
62+
63+
std::string_view suffix;
64+
if (pos.size() >= 2) {
65+
suffix = pos[1];
66+
}
67+
68+
std::puts(do_basename(pos[0], suffix).c_str());
69+
return 0;
70+
}

src/applets/dirname.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <cstdio>
2+
#include <string>
3+
#include <string_view>
4+
5+
#include <cfbox/args.hpp>
6+
#include <cfbox/help.hpp>
7+
8+
namespace {
9+
constexpr cfbox::help::HelpEntry HELP = {
10+
.name = "dirname",
11+
.version = CFBOX_VERSION_STRING,
12+
.one_line = "strip last component from file name",
13+
.usage = "dirname PATH",
14+
.options = "",
15+
.extra = "",
16+
};
17+
18+
auto do_dirname(std::string_view path) -> std::string {
19+
if (path.empty()) return ".";
20+
21+
// Remove trailing slashes
22+
while (path.size() > 1 && path.back() == '/') {
23+
path.remove_suffix(1);
24+
}
25+
26+
if (path.empty()) return "/";
27+
28+
auto pos = path.rfind('/');
29+
if (pos == std::string_view::npos) return ".";
30+
31+
// Remove trailing slashes from the parent part
32+
auto parent = path.substr(0, pos);
33+
while (parent.size() > 1 && parent.back() == '/') {
34+
parent.remove_suffix(1);
35+
}
36+
37+
if (parent.empty()) return "/";
38+
return std::string{parent};
39+
}
40+
41+
} // namespace
42+
43+
auto dirname_main(int argc, char* argv[]) -> int {
44+
auto parsed = cfbox::args::parse(argc, argv, {});
45+
46+
if (parsed.has_long("help")) { cfbox::help::print_help(HELP); return 0; }
47+
if (parsed.has_long("version")) { cfbox::help::print_version(HELP); return 0; }
48+
49+
const auto& pos = parsed.positional();
50+
if (pos.empty()) {
51+
std::fprintf(stderr, "cfbox dirname: missing operand\n");
52+
return 1;
53+
}
54+
55+
std::puts(do_dirname(pos[0]).c_str());
56+
return 0;
57+
}

0 commit comments

Comments
 (0)