在日常开发中,我们经常需要将大文件拆分成小块传输或存储,或者把分散的文件碎片重新合并。今天用 OpenCode 花 5 分钟,全程自然语言驱动,写一个命令行文件拆分/合并工具。
确保已安装 Go(≥1.18),然后按照 opencode.ai 指引安装 OpenCode。安装完成后,在终端输入 opencode 进入交互界面即可开始。
我对 OpenCode 说:
> 帮我初始化一个 Go 项目,模块名叫 filesplit,目录放在当前路径下的 filesplit 文件夹中。写一个 main.go,内容是为文件拆分/合并工具提供命令行入口框架,支持 -split 和 -merge 两个子命令。
OpenCode 的响应:
执行 mkdir filesplit && cd filesplit && go mod init filesplit 创建项目,随后生成 main.go:
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("用法: filesplit -split|-merge <参数>")
os.Exit(1)
}
switch os.Args[1] {
case "-split":
fmt.Println("拆分功能待实现")
case "-merge":
fmt.Println("合并功能待实现")
default:
fmt.Println("未知命令:", os.Args[1])
os.Exit(1)
}
}
要点:这里我用了明确的子命令设计思路,OpenCode 按 flag 模式生成脚手架,后续我们只需填充对应的函数实现。
我对 OpenCode 说:
> 在 main.go 中实现 splitFile 函数:接收源文件路径和分片大小(MB),将文件按指定大小分块,每个分块保存为 "原文件名.partN" 格式,同时输出分片数量。
OpenCode 的响应:
OpenCode 分析后自动补全了拆分逻辑,并更新了 switch 分支调用该函数:
func splitFile(filePath string, chunkSizeMB int) {
data, err := os.ReadFile(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "读取文件失败: %v\n", err)
os.Exit(1)
}
chunkSize := chunkSizeMB * 1024 * 1024
totalChunks := (len(data) + chunkSize - 1) / chunkSize
for i := 0; i < totalChunks; i++ {
start := i * chunkSize
end := start + chunkSize
if end > len(data) {
end = len(data)
}
partName := fmt.Sprintf("%s.part%d", filePath, i+1)
if err := os.WriteFile(partName, data[start:end], 0644); err != nil {
fmt.Fprintf(os.Stderr, "写入分片失败: %v\n", err)
os.Exit(1)
}
}
fmt.Printf("已拆分为 %d 个分片\n", totalChunks)
}
要点:OpenCode 自动处理了最后一个分片的边界情况(不满一个 chunkSize),并且使用了 os.ReadFile / os.WriteFile 简化 I/O 操作。
我对 OpenCode 说:
> 实现 mergeFile 函数:输入原始文件名,查找当前目录下所有 "原文件名.partN" 分片并按编号排序后合并写入原文件。完成后提示合并成功。
OpenCode 的响应:
OpenCode 注意到需要按 part 编号排序,自动引入了 sort 和 strings 包,生成了如下代码:
func mergeFile(originalName string) {
var parts []string
entries, err := os.ReadDir(".")
if err != nil {
fmt.Fprintf(os.Stderr, "读取目录失败: %v\n", err)
os.Exit(1)
}
prefix := originalName + ".part"
for _, e := range entries {
if !e.IsDir() && strings.HasPrefix(e.Name(), prefix) {
parts = append(parts, e.Name())
}
}
if len(parts) == 0 {
fmt.Fprintf(os.Stderr, "未找到分片文件\n")
os.Exit(1)
}
sort.Strings(parts)
out, err := os.Create(originalName)
if err != nil {
fmt.Fprintf(os.Stderr, "创建文件失败: %v\n", err)
os.Exit(1)
}
defer out.Close()
for _, part := range parts {
data, err := os.ReadFile(part)
if err != nil {
fmt.Fprintf(os.Stderr, "读取分片失败: %v\n", err)
os.Exit(1)
}
out.Write(data)
}
fmt.Printf("已从 %d 个分片合并还原: %s\n", len(parts), originalName)
}
要点:合并时用 sort.Strings 对文件名排序,依赖 "part1/part2/…/part10" 的字典序恰好也是数字序(因为编号是定长的),保证拼接顺序正确。
我对 OpenCode 说:
> 完善 switch 分支,让 split 子命令从 os.Args 中读取文件路径和大小参数(默认 1MB),merge 子命令读取原文件名,并加上参数数量校验。
OpenCode 的响应:
OpenCode 整合了参数解析逻辑,更新 main 函数如下:
func main() {
if len(os.Args) < 2 {
fmt.Println("用法:")
fmt.Println(" 拆分: filesplit -split <文件路径> [分片大小MB]")
fmt.Println(" 合并: filesplit -merge <原文件名>")
os.Exit(1)
}
switch os.Args[1] {
case "-split":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "请指定要拆分的文件路径\n")
os.Exit(1)
}
chunkSize := 1
if len(os.Args) >= 4 {
fmt.Sscanf(os.Args[3], "%d", &chunkSize)
}
splitFile(os.Args[2], chunkSize)
case "-merge":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "请指定原文件名\n")
os.Exit(1)
}
mergeFile(os.Args[2])
default:
fmt.Println("未知命令:", os.Args[1])
os.Exit(1)
}
}
要点:分片大小是可选的,默认 1MB。fmt.Sscanf 是 Go 标准库中最轻量的字符串→整数转换方式。
最终 main.go 共约 100 行:
package main
import (
"fmt"
"os"
"sort"
"strings"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("用法:")
fmt.Println(" 拆分: filesplit -split <文件路径> [分片大小MB]")
fmt.Println(" 合并: filesplit -merge <原文件名>")
os.Exit(1)
}
switch os.Args[1] {
case "-split":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "请指定要拆分的文件路径\n")
os.Exit(1)
}
chunkSize := 1
if len(os.Args) >= 4 {
fmt.Sscanf(os.Args[3], "%d", &chunkSize)
}
splitFile(os.Args[2], chunkSize)
case "-merge":
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "请指定原文件名\n")
os.Exit(1)
}
mergeFile(os.Args[2])
default:
fmt.Println("未知命令:", os.Args[1])
os.Exit(1)
}
}
func splitFile(filePath string, chunkSizeMB int) {
data, err := os.ReadFile(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "读取文件失败: %v\n", err)
os.Exit(1)
}
chunkSize := chunkSizeMB * 1024 * 1024
totalChunks := (len(data) + chunkSize - 1) / chunkSize
for i := 0; i < totalChunks; i++ {
start := i * chunkSize
end := start + chunkSize
if end > len(data) {
end = len(data)
}
partName := fmt.Sprintf("%s.part%d", filePath, i+1)
if err := os.WriteFile(partName, data[start:end], 0644); err != nil {
fmt.Fprintf(os.Stderr, "写入分片失败: %v\n", err)
os.Exit(1)
}
}
fmt.Printf("已拆分为 %d 个分片\n", totalChunks)
}
func mergeFile(originalName string) {
var parts []string
entries, err := os.ReadDir(".")
if err != nil {
fmt.Fprintf(os.Stderr, "读取目录失败: %v\n", err)
os.Exit(1)
}
prefix := originalName + ".part"
for _, e := range entries {
if !e.IsDir() && strings.HasPrefix(e.Name(), prefix) {
parts = append(parts, e.Name())
}
}
if len(parts) == 0 {
fmt.Fprintf(os.Stderr, "未找到分片文件\n")
os.Exit(1)
}
sort.Strings(parts)
out, err := os.Create(originalName)
if err != nil {
fmt.Fprintf(os.Stderr, "创建文件失败: %v\n", err)
os.Exit(1)
}
defer out.Close()
for _, part := range parts {
data, err := os.ReadFile(part)
if err != nil {
fmt.Fprintf(os.Stderr, "读取分片失败: %v\n", err)
os.Exit(1)
}
out.Write(data)
}
fmt.Printf("已从 %d 个分片合并还原: %s\n", len(parts), originalName)
}
# 编译 cd filesplit && go build -o filesplit.exe # 拆分(默认每片 1MB) ./filesplit -split bigfile.zip # 输出: 已拆分为 4 个分片 # 拆分(指定 2MB 每片) ./filesplit -split bigfile.zip 2 # 合并还原 ./filesplit -merge bigfile.zip # 输出: 已从 4 个分片合并还原: bigfile.zip
整个开发过程用时约 5 分钟,从项目初始化到可运行工具,全程用自然语言描述需求,OpenCode 负责代码生成、边界处理、参数校验等实现细节。
对比传统开发方式:手动创建文件结构、查标准库文档、处理分片边界条件、编写错误处理……这些繁琐工作都被 OpenCode 代劳了。你只需要专注于描述你要什么,实现细节交给 AI。
这也是 OpenCode 实践课系列的核心理念:AI 不是替代开发者,而是让开发者从"怎么写"中解放出来,把精力集中在"做什么"上。