Caveman 这个项目,最开始其实不是什么正经想法,而是我对自己越来越重的数字生活忍耐到了极限后的一个出口。市面上那些笔记软件、书签工具、待办清单,一个比一个功能多,一个比一个界面漂亮,可我用得越多越发现:我的数据被困在别人的服务器里,格式被困在别人的生态里,连备份都要看别人脸色。折腾了一个多月之后,我随手写了个脚本集合,代号就叫 Caveman——穴居人的意思。这里没有花哨的交互,没有云端同步,没有任何你想象中现代工具该有的东西,它就是把所有信息像原始人往岩壁上刻记号一样,老老实实存成纯文本和普通文件。今天这篇就是把 Caveman 的设计思路、完整实现和实际跑了一个季度之后的经验教训一次性讲透。如果你也被各种工具绑架得喘不过气,或者你只是想找一个能让自己用一辈子的轻量信息管理方案,这篇内容应该能给你一些很不一样的参考。
1. 为什么会有 Caveman:从工具倦怠到“越原始越好”
先聊聊项目诞生的背景。不是技术驱动,纯粹是疼出来的需求。
过去三四年,我换过不少信息管理工具。笔记用过大而全的,书签用过浏览器厂商自带的,待办用过带甘特图的,甚至还有一段时间把日历、文档、表格、日记全塞进同一个平台里。每次迁移数据都是一场灾难。导出格式千奇百怪就不说了,有些工具甚至连完整导出都要收费,我那些沉淀了好几年的记录,最后只能靠复制粘贴一页一页地搬。最让我受不了的是,一旦某个产品调整方向,我所有的使用习惯都得跟着改,这种失控感非常糟糕。
我认真盘算了一下自己到底需要什么。刨掉那些营销文案里反复灌输的需求,我的真实使用场景就四类:快速记录一闪而过的想法,把零散的网页、图片、片段归档起来,写长一点的文章和日记,以及在一个固定的地方管理待办和项目状态。这四件事都不复杂。复杂的是外围那些东西,账号体系、数据格式绑定、离线访问限制、同步冲突处理,还有永远做不完的功能更新。想明白这一层之后,我决定自己做一套工具,定了几条硬性原则:
- 数据格式必须是我能理解、能手工编辑的纯文本,绝不用私有二进制格式。
- 所有数据必须存放在本地,任何一台电脑上用编辑器打开就能读。
- 功能只保留上面四类,其他一律不要。
- 能用系统自带命令处理的逻辑,绝不引入额外依赖。
这套原则和现代软件的发展趋势完全是反着来的。现在大家都喜欢把东西做得越来越复杂,往云端塞,往大数据塞,我却想回到最原始的状态。Caveman 这个名字就是在这个阶段定下来的,它就代表一种态度:像穴居人一样,用最简单、最原始、最可靠的方式记录自己的数字生活。原始不等于落后,原始意味着可控。
2. 目录结构设计:把文件系统当成数据库来用
Caveman 的核心设计决策,是放弃所有现代存储方案,直接使用操作系统自带的最小存储单元:文件。
2.1 为什么不用数据库,也不用 JSON 文件
你可能觉得这是个很反直觉的选择。现在随便一个工具都恨不得用上全套数据库技术,字段、索引、事务、加密一个不少。我最初也动过用 SQLite 的念头,毕竟一个文件就能存全部数据,查询还特别方便。但后来我意识到,数据库解决的是“大量结构化数据的高效检索”问题,而我这几千条笔记,完全可以用文件系统中的文件夹和命名规则解决。
用文件当存储有几个被低估的优势。第一,迁移成本为零。想换个地方存放,直接复制粘贴就行,任何系统都能识别。第二,兼容性拉到满。今天流行的工具三五年后可能消失,但文件这个抽象在计算机世界里已经存在了几十年,未来也几乎不可能消失。第三,人与机器可以协同处理。如果是数据库,我得写 SQL 才能查东西;如果是文件,我除了用脚本查询,还可以直接打开文件夹手工浏览,紧急情况下连工具都不需要,用系统自带的搜索就能完成全文检索。
2.2 Caveman 的物理目录结构
以一个普通用户的视角,Caveman 在你的主目录下创建这样一个文件夹:
caveman/ ├── inbox/ # 所有内容先进这里,统一入口 ├── notes/ # 经过整理后的长期笔记 ├── pages/ # 长文、文章、日记等 ├── tasks/ # 待办与任务状态 ├── assets/ # 图片、附件等资源文件 ├── archive/ # 超过一年未变动的冷数据 ├── meta/ # 工具自身的配置与索引 └── tmp/ # 临时文件,定期清空这套结构对应四种使用场景,每个场景的入口都是清清楚楚的。
inbox 这个目录是整个系统的收件中心。任何东西,不管是一条灵感、一个网页摘录,还是一张随手拍的照片,统一扔进来。它存在的意义是降低记录门槛。人在灵感来的时候是不愿意做分类决策的,一旦你要想“这条该归到哪个文件夹”,动作就容易被中断。Caveman 的策略是先把东西接住,整理的事情留给以后。
notes 目录放那些已经整理过的知识型内容。它允许一层子目录,但我不建议弄太深,两层以上就容易出现分类死角。pages 目录比较特殊,一开始没想明白,后来发现写文章和写日记有一个共同特点:篇幅长,一个文件就是一个完整的作品,不适合和其他信息混在一起。tasks 目录则是任务清单,每一个待办事项是一个独立的 Markdown 文件,靠文件名中所带的状态标记来区分是否完成。
2.3 命名规则就是用文件系统做索引
没有数据库,检索靠什么?靠文件名。
我在项目里定了一套强制命名规范,所有文件都遵循YYYY-MM-DD - 标题.md这个格式。好处非常明显:排序天然是按时间来的,不用额外维护任何时间字段;文件名本身就有语义,不需要打开文件就知道内容主题;用户在任何工具里,包括手机自带文件管理器,都能一眼定位文件位置。
这套规则非常笨,但非常可靠。就像穴居人给自己的岩画加了个坐标,找的时候不用去翻整面墙。
3. 核心脚本实现:Caveman 的四个关键模块
光有目录结构只能算是一个“规范化放文件”的文件夹,还配不上工具这个称呼。我给 Caveman 写了四组核心脚本,对应快速记录、全文检索、任务管理和归档压缩这四个最常用的操作。为了保持依赖轻盈,主体全部用 Bash 写成,只有统计相关的少量逻辑用 Python 处理。
提示:以下脚本均基于 GNU/Linux 或 macOS 的常见环境。Windows 用户建议先装一个 Git Bash 或 WSL 再使用,当然也可以直接把脚本逻辑移植到 PowerShell。
3.1 快速记录模块:caveman add
灵感这种东西,晚记三十秒就可能彻底蒸发。所以 Caveman 的第一个脚本优先级最高,目标只有一个:从输入到文件落地,耗时越短越好。
# 用法: caveman add "今天想到一个极简工具思路" caveman_add() { local note="$*" local now=$(date +"%Y-%m-%d %H:%M") local day=$(date +"%Y-%m-%d") local title=$(echo "$note" | cut -c1-50) local file="$CAVEMAN_HOME/inbox/${day} ${title}.md" printf '# %s\n\n记录时间:%s\n\n%s\n' "$title" "$now" "$note" > "$file" echo "已存入 inbox: $file" }这段脚本其实就是三件事:取当前时间,截取前 50 个字当文件名,把正文写进新文件。它不做任何花哨处理,连去重都不做,因为去重这个动作放在理念上是反直觉的。如果你在整理的时候发现同样的内容出现了两条,说明它值得多想一步:究竟是重复的记录,还是两个相关但不同的念头。这种判断应该交给人的大脑,而不是脚本逻辑。
3.2 全文检索模块:caveman find
文件多了之后,找东西就变成第一需求。我封装了一个基于 grep 的检索命令,好处是它把范围限制在 Caveman 目录内,不会像系统级搜索那样把所有无关文件都翻出来:
# 用法: caveman find "关键词" caveman_find() { case "$1" in *" "*) grep -ri "$*" --include="*.md" "$CAVEMAN_HOME/"{inbox,notes,pages} ;; *) ls "$CAVEMAN_HOME"/{inbox,notes,pages} | grep -i "$1" ;; esac }这里做了一个故意设计的取舍:单关键词搜索时优先看文件名,多关键词搜索时才走全文 grep。原因是文件名匹配结果干净、速度快,全文搜索适合做兜底。第二条命令会把匹配行号带出来,方便你直接确认这个文件到底在讲什么。
真正用起来的时候,这套检索逻辑大多数情况够用。如果未来文件量级到了几万条,我可以无缝切换到 ripgrep 或者直接加一层 tiny 索引。但这是以后的事,现阶段绝不为想象出来的性能瓶颈提前买单。
3.3 任务管理模块:caveman task
任务管理在 Caveman 里的做法是最具争议性的,因为我没有提供任何“项目看板”或者“优先级矩阵”,一个待办就是一个文件。
# 用法: 新建任务 caveman task add "写季度总结" # 已完成 caveman task done "季度总结" caveman_task() { local action="$1"; shift case "$action" in add) local name="$*" local file="$CAVEMAN_HOME/tasks/$(date +"%Y-%m-%d") - ${name}.md" printf '# 任务:%s\n\n- [ ] 未完成\n- 创建:%s\n' "$name" "$(date)" > "$file" ;; done) local match=$(grep -il "未完成" "$CAVEMAN_HOME/tasks/"*"${1}"*.md | head -1) sed -i '' 's/- \[ \] 未完成/- [x] 已完成/' "$match" 2>/dev/null || \ sed -i 's/- \[ \] 未完成/- [x] 已完成/' "$match" echo "完成: $match" ;; list) echo "== 未完成任务 ==" grep -L "已完成" "$CAVEMAN_HOME"/tasks/*.md 2>/dev/null echo "== 最近完成 ==" grep -l "已完成" "$CAVEMAN_HOME"/tasks/*.md 2>/dev/null | tail -5 ;; esac }你可能想问,为什么不用现成的 Todo 应用。我的回答是,Todo 应用里的任务是一条数据库记录,生命周期完全受软件控制;而 Caveman 里的任务就是一个文件,它天然具备和其他笔记完全一致的属性——可以加链接、可以补备注、可以直接编辑。把任务写成文件的另一个好处是延迟满足感变得可视化,你随时可以去翻翻旧任务文件,看看自己半年前列的那些事,这种回望是任何打卡 App 都给不了的。
3.4 归档与清理模块:caveman archive
Caveman 最后一个核心脚本处理的是冷数据。系统运行久了,总会有大量几个月甚至几年都没碰过的老文件。它们占空间不多,但会让日常检索噪音变大。
# 用法: caveman archive 将超过 90 天未修改的页面和笔记移入归档 caveman_archive() { local cutoff=$(date -v-90d +"%Y-%m-%d" 2>/dev/null || date -d '-90 days' +"%Y-%m-%d") local day="$cutoff" find "$CAVEMAN_HOME"/pages "$CAVEMAN_HOME"/notes \ -name "*.md" -type f -not -path "*/archive/*" | \ while read -r f; do local mtime=$(stat -f "%m" "$f" 2>/dev/null || stat -c "%Y" "$f") local mday=$(date -r "$mtime" +"%Y-%m-%d" 2>/dev/null || date -d "@$mtime" +"%Y-%m-%d") if [[ "$mday" < "$day" ]]; then mkdir -p "$CAVEMAN_HOME/archive/$(dirname "${f#$CAVEMAN_HOME/}")" mv "$f" "$CAVEMAN_HOME/archive/${f#$CAVEMAN_HOME/}" fi done echo "归档完成,当前笔记总数: $(find "$CAVEMAN_HOME" -name '*.md' | wc -l)" # 顺手清理零字节废文件 find "$CAVEMAN_HOME"/tmp -type f -empty -delete }自动归档这个设计我纠结了很久。自动化出的最大风险是误判:一个你三个月没碰的文件不一定就是过期文件。所以我的妥协是 archive 命令只移动文件,不删除文件,归档目录里面的数据随时可以手动拉回到主目录。真正需要物理删除的内容,我建议还是人工检查之后自己删,这个谨慎不丢人。
4. 日常使用流:从一个想法到完整归档的全链路
脚本写完之后,我花了几天时间把 Caveman 真正用起来。这段时间最关键的收获不是功能完成度,而是搞清楚了一个道理:任何工具,必须每天能用、用的时候不犹豫才叫合格。下面用三个具体场景展示整个链条是怎么跑的。
场景一,灵感记录。上班路上我突然想到一个产品文案的切入点,掏出手机,打开终端模拟器(我用的是 Termux),输入:
caveman add "新文案切入点:用穴居人比喻极简工具的反向价值"这条内容落进 inbox,文件名带着时间戳,几秒钟完成。这个动作并不比打开一个现代笔记 App 慢,但它没有弹窗,没有广告,没有云同步的转圈等待,甚至连网络都不用连。
场景二,知识整理。晚上我准备把这些天积累的灵感系统地过一遍。执行caveman list看到 inbox 里所有文件,逐条判断后移动到对应的 notes、pages 或者直接删除。这个环节很多工具会直接叫“归档”,但我更愿意叫“整理”,因为它的动作不是扔进垃圾桶,而是真正确认了每条信息的价值。
场景三,专题检索。两个星期后,我在写一篇关于“极简工具”的文章,需要找当时那条文案灵感。执行caveman find "穴居人",系统立刻定位到那天的记录文件。由于 Caveman 的全文检索是基于 grep 原生的,速度取决于文本总体大小。我目前几千条纯文本笔记的规模下,基本是毫秒级返回。
这整个链路走下来,最让我舒心的是每一个环节都在本地完成。我不用操心网络故障导致记录丢失,不用担心某个 App 改版导致入口消失,更不用背着“同步冲突”这个包袱。本质上,我把自己的数字记忆用最物理的方式握在了自己手里。
5. 一致性策略:git 版本管理 + 定期外置备份
纯文本方案唯一的弱点在于,没有什么东西会阻止你自己手滑删错文件。所以 Caveman 必须配一套可靠的一致性和备份方案,我把这套方案拆成了三层。
第一层是版本历史,用 git 记录整个 Caveman 目录的每一次变化。我设置了一个自动提交的 cron 任务,每小时把变更提交一次,附带简单的提交信息:
cd $CAVEMAN_HOME && git add -A && git commit -m "caveman auto backup $(date +'%Y-%m-%d %H:%M')"这个操作的商业价值在于,我可以在任意时间点回到十个月前的状态。有一次我不小心把某篇日记的段落改错了,而且改动之后保存关闭了,正常编辑器根本找不回旧内容。还好有 git,git log --all --oneline定位到修改前的版本,一条命令就恢复了原状。
第二层是跨设备备份,依托同步网盘或自建 NAS。我自己的做法是把 Caveman 目录同时放在 OneDrive 的本地文件夹里,这样手机端也能通过同步看到,日常编辑不受影响,也不依赖某一家厂商的云服务。如果你偏好完全离线,可以用一个外置移动硬盘挂载脚本,每周运行一次 rsync 做增量镜像,效果一样。
第三层是极端容灾,定期把整个 Caveman 目录打包加密之后上传到对象存储,比如 S3 或者各类 OSS 服务。这里要特别提醒一句:如果内容涉及隐私,压缩打包之后一定要用 GPG 之类的工具做一次加密,别图省事裸传上去。
一套方案三层保障,每一层解决不同层级的问题。git 管细节回滚,同步网盘管跨设备便利,加密离线备份管灾难恢复。这三个层级加起来的数据安全度,远超大多数商业云笔记应用提供的默认保护。
6. 运行三个月后我的反思:哪些设计需要修正
任何项目吹得再响,真用几个月都会暴露出问题。Caveman 经过了完整的季度测试后,我总结出三个最值得说的教训。
第一个教训是元数据宁少勿多。最初版本里我雄心勃勃地给每个 Markdown 文件都加了 front matter,包含时间、地点、标签、状态、来源 URL、心情指数等信息。实际用了两周就发现,这套设计的维护成本极高,每次整理一个文件都要像填表格一样,填到第三天我就开始偷懒,后来那些元数据字段慢慢就都变成空的了。现在我把 front matter 砍到只剩两行:
--- date: 2025-01-15 tags: [极简, 工具] ---标签也只保留最低频、最必要的几个,其他信息全部靠全文检索和文件名解决。这个发现其实印证了一个很简单的道理:一个工具如果让你必须付出额外劳动才能使用,哪怕每次只有十秒钟,长期来看你也会本能地避开它。
第二个教训是强制流程会杀死一切。Caveman 刚上线那会儿,我非常严格地要求自己,任何新内容必须先进 inbox,然后当天必须整理完。这个流程坚持了不到五天就崩了。后来我调整了策略,inbox 里面超过两周还没整理的文件,系统只是每天在终端启动时提示一下清理数量,至于要不要整理、什么时候整理,完全由我自己决定。实践下来反而走顺了。工具不应该当主人,它最多只能当个温和的提醒者。
第三个教训是脚本会变得越来越臃肿,需要定期做减法。Caveman 本来是一个极简单纯工具,但我每遇到一个小需求就往上加函数,到第二个月底脚本行数已经翻了一倍还多。有些功能,比如生成月度统计报表、自动扫描重复文件、把笔记转换成 HTML,其实完全用不到。最终我在一次集中清理中删掉了大概 40% 的代码,又回到了最初的稳定状态。这个动作我决定每半年做一次,删掉冗余逻辑,保持工具的原始速度。
7. 延伸思路:给 Caveman 增加一个移动入口
Caveman 已经基本满足了我本地管理信息的所有需求,但还有一个现实问题:现在手机几乎不离手,很多碎片想法都是在手机上冒出来的。手机端的纯命令行体验虽然能用,但毕竟有门槛。我给未来的自己留了几个扩展方向,也在这里列出来供你参考。
第一个方向是搭配一个极简文本编辑器。手机端我用的是纯编辑器加 SFTP 连接到家里电脑上的 Caveman 目录,写完存盘直接更新。如果不想碰服务器,也可以直接用支持 WebDAV 的编辑器应用,把 Caveman 目录挂载成一个可读写的网盘,手机的记录动作瞬间变成复制粘贴,零学习成本。
第二个方向是做一个单文件前端。因为所有内容都是结构化的 Markdown,我可以写一个几十 KB 的本地 HTML 页面,读取文件夹内容生成索引和全文预览,跑在浏览器里,不需要任何后端服务。这种单页应用的妙处是数据仍然全部保持在本地目录里,界面改起来也不心疼。
第三个方向是引入命名规范的扩展。比如在文件名前面增加@前缀表示收藏内容,用#前缀表示极重要的待处理文件,这样查询的时候等于多了一套无形的标签体系。我不会给这个扩展做成规范化功能,它更像各人记录习惯的个性化选择。
事实上,Caveman 这个项目我已经把它开源到了我的个人仓库里。它真的很小,全部脚本加起来不到两百行,但你如果看完这篇内容愿意动手做一遍,我可以负责任地说:这套东西的价值不在代码量,而在于它让数字记录这件事重新变得透明、可控。我的建议是,不要迷信任何花哨工具,先把自己的数据和格式主动权握回手里,其他的都有的聊。