说实话,我第一次看到 “caveman” 这个词,脑子里冒出来的是老动画片里穿兽皮、举石头的穴居人形象。但把它当成一个项目名来认真琢磨,是另外一回事。我越来越觉得,现在的人缺的不是更好用的软件,而是像穴居人一样“只用必要的东西解决问题”的本能。
那段时间我手机里堆了十几个效率 App:待办用一个软件,笔记用一个软件,工时记录再用一个,习惯打卡又换一个。表面上看各司其职,实际上我得记住每个 App 的入口、同步逻辑和订阅状态,还要不停应付更新弹窗。真正被消耗掉的不是时间,而是专注力。于是我开始想,如果我像穴居人一样,只保留最原始、最可靠的一套工具,生活会变成什么样?这个念头最终变成了一个叫 caveman 的命令行项目。这篇文章会把它是什么、为什么这么做、怎么落地、踩过哪些坑,全部摊开来讲。
1. 从 caveman 这个词开始:为什么我要给自己造一个“原始人工具箱”
先解释一下 caveman 这个名字。它直译是“穴居人”,放在项目语境里,代表一种刻意后退的姿态:我不追求图形界面、云端同步、人工智能推荐,只追求“一个命令能干完的事,绝不用十个按钮完成”。我把这个极简命令行工具命名为 caveman,就是在提醒自己——别把简单的事搞复杂。
我的核心痛点非常具体:每天要快速记下一件事,要知道自己还有哪些事没做完,要能隔一段时间复盘,还要保证数据永远属于自己、不依赖任何服务器。市面上的工具没有一个能同时满足这四点。大多数 App 做的是“管理任务”而不是“帮助你完成任务”,它们的核心商业模式是让你持续打开它,而不是让你尽快关掉它。这种体验久了真的很累。
于是我用大概一个周末,用纯 Bash 写了一套工具。它能做四件事:待办清单、快速笔记、时间追踪、每日回顾。所有数据以纯文本形式放在用户目录的.caveman文件夹里,删掉工具本体数据也不会受影响。它没有数据库,没有网络请求,没有后台进程,甚至连配置文件都只有一个环境变量。听起来像开玩笑,但实际用下来,它比我之前所有“强大”的效率工具都更稳定。
1.1 现代工具的复杂度焦虑
我不否认复杂工具的价值,但复杂工具的成本经常被低估。一个任务管理软件如果有一百个功能,你就得花时间学会其中九十多个用不上的功能;它的更新会改变操作位置,它的订阅会到期,它的云同步偶尔会冲突。这些成本会在某个时刻超过它带来的收益,只是很多人没意识到。
我自己的崩溃时刻是某天晚上:我花了二十分钟在工具里把任务打上标签、设置优先级、拖拽排序,结果真正要做的三件事一件都没开始。那一刻我觉得特别荒谬。后来我就想,穴居人安排一天根本不需要标签系统——他只需要知道“今天要打猎”和“猎物还没打到”。这个朴素逻辑其实就是 caveman 项目的出发点。
1.2 caveman 不是什么万能药
必须老实说,caveman 不适合所有人。它没有手机推送,没有团队协作,没有甘特图,没有漂亮的图表报告。如果你需要这些功能,强烈建议继续用专业工具,不要硬折腾。
它适合的是另一类人:工作在终端里进行的技术人员、写作者、对数据掌控有强需求的人,以及所有对“工具越多越焦虑”深有体会的人。核心价值不是功能全面,而是让你重新拿回对工具和数据的主控权。对我来说,它就像一块燧石——谈不上精致,但任何时候敲一敲都能出火。
2. 设计与选型:为什么“最原始”的方案反而最可靠
这个项目里最值得讲的不是功能,而是设计取舍。我在做之前列了几个硬性要求,每个都排除了不少“更现代”的技术方案。
首先,整个工具必须是单文件。不是微服务,不是插件化架构,就是一个几百行的 Bash 脚本。其次,数据必须是纯文本,不能用 SQLite,不能用 JSON 二进制序列化,更不能存在某个公司的服务器上。最后,脚本本身的复杂度必须控制在“任何人两小时内能读懂”的范围内。这些要求决定了后续所有技术选型。
2.1 数据比工具重要:纯文本的长期主义
为什么如此执着于纯文本?因为工具会死,数据不会。任何一个软件都可能停止维护,但一个按行排列的文本文件,三十年后照样能用记事本打开。纯文本还意味着所有 Unix 生态的工具都能直接处理它:grep搜索、cat拼接、wc统计、awk分析、git做版本管理,不需要额外的库或驱动。
举个例子,我想知道某一天的笔记里总共写了多少字,一句wc -l就出来了;我想统计某个项目在时间追踪里出现了几次,一句grep -c "项目名" timer.log就搞定。这种自由度是任何封闭软件都给不了的。更重要的是,当我想换工具时,不需要做“数据迁移”——文本本身就是迁移格式。
2.2 为什么不是 Python 或 Go
有人可能会问,写命令行工具用 Python 不是更简单吗?确实是更简单,但这里有个权衡。Bash 是几乎所有 Unix 系统的默认组件,不需要安装解释器,不需要虚拟环境,也不需要处理依赖版本。我把脚本扔到任何一台 Linux 或 macOS 机器上,挂到 PATH 里就能运行。这一点对“随时随地可复现”的项目精神来说太重要了。
Bash 的缺点是逻辑复杂后容易写出隐蔽的 bug,尤其是各种引号展开和分词陷阱。我控制项目规模的方式是“功能瘦身”:只保留四类子命令,每个子命令只支持四五个参数。如果某个需求让脚本超过三百行,我就重新思考这个需求是不是必要的。这种约束反而逼着我把功能设计得足够聚焦。
2.3 数据目录怎么组织:按“日子”而不是按“项目”
caveman 的数据目录长这样:
~/.caveman/ ├── todo.txt ├── notes/ │ └── 2025-03-01.md │ └── 2025-03-02.md ├── timer.log └── journal/ └── 2025-03.md设计考量很直白:对个人效率管理来说,“今天”是最高频的天然单位,按日期归档可以避免分类焦虑。很多人用笔记软件时最大的问题是建了二十个文件夹却不知道该把一条随手记放哪里。caveman 干脆取消了这个选择,一切按日期走;如果以后想按主题整理,用grep就能从所有文件里捞出来。
todo.txt是唯一没有按日期拆分的文件,因为任务天然具有跨日属性。它保存的是当前所有未完成和已完成任务的文本流,每行一条。timer.log则是纯追加的时间流水,方便做周报时用awk按列汇总。
3. 从零到一:caveman 的完整实现
下面进入实操环节。我不打算贴一个五百行的完整脚本,那样太啰嗦,而且每个人的需求不同。我更想把骨架和几个关键函数的实现思路讲透,让读者能照着搭出适合自己的版本。
3.1 脚本入口与命令分发
整个工具的本质是一个带子命令的脚本。入口用 Bash 的case分发,结构清晰,后续加子命令也容易。开头先定义数据目录和几个关键变量:
#!/usr/bin/env bash set -euo pipefail CAVE_ROOT="${CAVE_ROOT:-$HOME/.caveman}" CAVE_NOTE_DIR="$CAVE_ROOT/notes" CAVE_TODO="$CAVE_ROOT/todo.txt" CAVE_TIMER="$CAVE_ROOT/timer.log" CAVE_JOURNAL="$CAVE_ROOT/journal" mkdir -p "$CAVE_NOTE_DIR" "$CAVE_JOURNAL"set -euo pipefail这三行非常关键,建议所有 Bash 脚本都写上。-e让脚本在出错时立即退出,避免带着错误状态继续跑;-u让未定义变量的引用直接报错,能揪出很多手滑写错的变量名;pipefail让管道中任意一环失败时整体返回失败,而不是只看最后一条命令的退出码。没有这三行,Bash 写起来会非常“飘”,出了错都不知道在哪。
分发部分长这样:
cmd="${1:-help}" case "$cmd" in todo) shift; cmd_todo "$@" ;; note) shift; cmd_note "$@" ;; timer) shift; cmd_timer "$@" ;; log) shift; cmd_log "$@" ;; *) help ;; esac注意我用了shift把子命令本身从参数列表里去掉,这样每个cmd_*函数拿到的$@就是纯粹的子参数。这种写法比在每个函数里再判断$1是子命令序号干净很多。
3.2 待办功能:caveman todo
待办可能是最常用也最核心的模块。我的设计是用行首的[ ]和[x]标记区分未完成和已完成,而不是一做完就删除。这样保留了一条任务从出生到完成的完整历史,方便后续复盘。
cmd_todo() { touch "$CAVE_TODO" action="${1:-list}" shift || true case "$action" in add) echo "[ ] $(date '+%Y-%m-%d %H:%M') $*" >> "$CAVE_TODO" ;; list) grep -n '^\[ \]' "$CAVE_TODO" ;; done) sed -i.bak "${1}s/^\[ \]/[x]/" "$CAVE_TODO" ;; clean) > "$CAVE_TODO" ;; esac }有几个细节想特别解释。sed -i.bak是跨平台兼容的做法:macOS 的sed在-i后必须跟扩展名参数,否则会报错;Linux 上sed -i可以直接用。统一写成sed -i.bak两边都能跑,代价是每次会留下一个.bak备份文件,定期删掉就行。更优雅的做法是检测系统类型动态设置参数,但对个人项目来说没必要那么讲究。
grep -n '^\[ \]'会输出“真实行号 + 任务文本”,比如3:[ ] 2025-03-01 14:32 写周报。这个行号是文件里的真实行号,所以后续执行caveman todo done 3时,sed修改的也是文件的第 3 行。这个细节很关键,否则你看到的“序号”和文件实际行号一旦对不上,就会误改任务。
我刻意没有做优先级、截止日期、标签这些功能。原因很朴素:任务管理的核心动作是“完成”,不是“排序”。如果一天的任务多到必须用优先级排序,说明不是工具的问题,是任务的节奏出了问题。
3.3 快速笔记:caveman note
笔记模块的设计目标是“零成本记录”:不需要选分类,不需要建标题,只需要一句话。实现上,我让每条笔记追加到当天的 markdown 文件里,用时间作为小节标题:
cmd_note() { f="$CAVE_NOTE_DIR/$(date +%Y-%m-%d).md" if [ "${1:-}" = "open" ]; then ${EDITOR:-vim} "$f" else printf '\n## %s\n%s\n' "$(date '+%H:%M')" "$*" >> "$f" fi }使用效果是这样的:在终端里输入caveman note 修复了登录模块的缓存问题,脚本就把这条内容写进notes/2025-03-01.md,并在前面自动加一个时间戳标题。三秒钟完成,不需要切换窗口,不需要思考“该放哪个文件夹”。想展开写就去敲caveman note open,它会直接用$EDITOR打开当天的文件。
这种做法对“灵感捕获”特别有效。我过去用笔记软件时,很多一闪而过的想法会因为“懒得分类”而丢掉;caveman 把这个摩擦成本降到了接近于零。
3.4 时间追踪:caveman timer
我每周都有统计各类工作耗时的需求,所以写了 timer 模块。它的数据格式是 TSV(Tab 分隔值),每行三列:开始时间戳、结束时间戳、任务名称。当一个任务开始时写入一条只有开始时间戳和名称的记录,结束时再回来补上结束时间戳。
核心逻辑是把时间戳存成 Unix 秒数,这样算时长就是简单的减法,再自己换算成小时分钟:
cmd_timer() { action="${1:-status}" case "$action" in start) printf '%s\t%s\n' "$(date +%s)" "$*" >> "$CAVE_TIMER" ;; stop) end=$(date +%s) # 取最后一行未闭合的记录,补上结束时间 awk -v end="$end" ' /^[0-9]+\t/ && !/^[0-9]+\t[0-9]+\t/ { $0 = $0 end; # 简化示意,实际要对列插入 } { print } ' "$CAVE_TIMER" > "$CAVE_TIMER.tmp" mv "$CAVE_TIMER.tmp" "$CAVE_TIMER" ;; esac }上面的 awk 片段是示意性的,真正写的时候需要处理 Tab 列的插入。我在自己的版本里其实是直接用最后一行替换,逻辑更简单。这里更值得记的是设计教训:如果你对 awk 和 sed 不够熟,不要在时间追踪这种需要精确改行的逻辑里硬写;更稳的方案是把每次 start 和 stop 都追加成独立行,后面统计时用配对算法处理。虽然文件会变长几十倍,但对个人项目来说完全不是问题。
3.5 每日回顾:caveman log
log 模块是我最喜欢的部分。它不做复杂加工,只是把一天散落在各处的记录聚合到一个视图里:
cmd_log() { d="${1:-$(date +%Y-%m-%d)}" echo "=== Todo ===" grep "$d" "$CAVE_TODO" || true echo "=== Notes ===" cat "$CAVE_NOTE_DIR/$d.md" 2>/dev/null || echo "(无笔记)" echo "=== Timer ===" grep -E "$d" "$CAVE_TIMER" || echo "(无计时记录)" }每天晚上花三分钟浏览这些内容,就能清晰地看到今天完成了什么、卡在哪里、时间花到了什么地方。这个回顾习惯比任何自动生成的统计数据都有用,因为数据是最原始的第一手记录,没有经过工具算法的“加工”。
4. 实际使用:我的一天是怎么被 caveman 安排的
项目做出来是一回事,真正让我坚持用的是它能融进日常生活。下面是我比较典型的一天的用法。
4.1 早晨 5 分钟规划
早上打开终端,第一件事是执行caveman todo list,看看有哪些任务从昨天留下来了。然后我会挑出今天最重要的一件事,把它作为第一条加入待办:caveman todo add 完成产品方案初稿。这样整个列表顶部就是今日主轴,往下才是其他次要事项。
选择在终端而不是手机 App 里做规划,看起来不太“现代”,但有一个非常实际的好处:终端里没有通知,没有红点,没有推送。你打开它就只能看到任务本身,不会被其他东西带偏。
4.2 写代码时的随手记录
开发过程里最怕的是思路中断。以前遇到一个临时问题,我会切到浏览器去搜索或记下来,结果往往是切出去就回不来了。现在我只敲一行命令:caveman note 发现订单模块的金额计算精度有问题,明天处理。内容落盘后,大脑从“保持记忆的紧张感”里解脱出来,可以立刻回到当下的编码工作里。
4.3 晚上的回顾与归档
晚上我会执行caveman log把当天所有记录过一遍。看到未完成的任务,我会评估一下:如果明天还值得做,就再caveman todo add一遍;如果不值得,直接让它留在历史里就好。这个“二次过滤”很重要,因为不是所有当时觉得重要的事,回头都真的重要。
之后我会把当天的笔记整理一下,长文展开可以在caveman note open里补,零散话术则保持原样。这个过程不是强制任务,而是顺手做的三分钟小事。
4.4 多设备同步:git 就够了
caveman 本身没有同步功能,但纯文本数据给了我最简单的同步方案:Git。在~/.caveman目录里执行git init,然后把目录推到私有仓库,换设备或重装系统后git clone回来即可。因为文件全是文本,git diff能看到每天到底改了哪些内容,任务历史天然可追溯。
我还会在系统里配一个定时任务,每半小时自动执行git add -A && git commit -m "auto",这样连手动同步都省了。这个方案没有任何锁冲突和同步失败的问题,因为文本文件的冲突合并几乎总是容易处理。唯一要提醒的是私有仓库的访问凭据不要硬编码在脚本里,要使用系统凭据管理器,避免把密钥写进明文脚本。
5. 常见问题与排查实录
任何工具用久了都会遇到问题。下面是我在开发和使用 caveman 过程中真实踩过的坑,整理成一个速查表供读者参考。
5.1 脚本没权限或找不到命令
刚写完脚本时总是遇到command not found。原因无非两个:文件没有可执行权限,或者脚本所在目录不在PATH里。解决办法是执行chmod +x caveman,然后把脚本放到/usr/local/bin或者~/bin,并确认PATH里包含对应目录。用echo $PATH检查一下,一目了然。
5.2 参数里有空格、引号和特殊符号
这是 Bash 新手最容易踩的坑。假设要添加的任务包含空格:caveman todo add 周三 10:00 开会,Shell 会把这个命令拆成 5 个参数,最后保存的内容可能变成周三 10:00 开会——恰好没问题,但如果参数里有需要保留的空格,比如caveman note add 记得买 牛奶和鸡蛋,就可能被错误拆分。
解决方法是始终用引号包住参数:caveman todo add "周三 10:00 开会"。在脚本内部,对应地使用"$@"而不是$*来保留每个参数的边界。这也是我在代码里反复强调的地方:凡是拿不准的地方,加引号,没坏处。
5.3 done 和 delete 的行号对不上
早期版本里我做了一个“已完成任务过滤后序号”的功能,结果任务一旦完成,列表序号就会整体前移,下一次按序号操作就会改错行。后来改成用grep -n输出真实文件行号,才彻底解决这个问题。
这里给读者的经验是:在 shell 工具里处理“行”时,永远让命令直接操作真实行号,不要自己在逻辑里维护一套“可见序号”。否则一旦数据有增删,两套数字立刻就会错位。
5.4 Linux 和 macOS 的 sed 差异
同一个sed -i在 Linux 上能用,在 macOS 上会报sed: 1: "..." : extra characters at the end of c command。因为 BSD 版的sed规定-i后面必须跟后缀参数。我用的兼容写法是sed -i.bak "s/^.../.../" 文件,这样两个平台都能跑,缺点是会生成.bak文件,可以在命令后面再补一条rm -f "$CAVE_TODO.bak"清理掉。
5.5 备份与恢复
caveman 的备份最简单的方式就是复制目录:cp -r ~/.caveman ~/.caveman.backup。因为全是文本,整个目录通常只有几百 KB。可以再用crontab定期打包到其他磁盘,或者配合 Git 每天提交。恢复就更简单了:把备份目录放回去,工具直接就能读,不需要任何额外初始化。这是纯文本方案最大的红利,也是我推荐大家尝试的重要原因。
6. 后续还能怎么扩展:让这个原始人工具再进化一点
caveman 目前的形态已经满足了我的大部分需求,但它并不是只能停在这里。我试过几个很实用的扩展方向,分享给有兴趣的读者。
第一个是把时间追踪的结果汇总成周报。因为timer.log是 TSV 格式,用awk按任务名称聚合、按周求和,就能输出这周各项目花了多少时间。我最初的实现是每天手动查看,后来改成每周五跑一段三行的 awk 脚本,效率提升非常明显。
第二个是给待办加一个简单的“计划日期”字段。不需要复杂的日历系统,只要在任务末尾追加一句on 2025-03-05,然后日常用grep过滤出当天和过期的任务即可。这个方案能让你在终端里也获得“今天该做什么”的视图,同时保持数据仍然是纯文本。
第三个是把系统通知接进来。在 cron 或定时任务里跑一句带notify-send的命令,就能在指定时间弹出桌面提醒;macOS 上则可以用osascript -e 'display notification "..."'实现。这算是给 caveman 补上一个“主动提醒”的能力,用最简单的方式覆盖它原本缺失的功能。
我自己实际用下来的体会是:造这样一个工具最大的收获不是省了多少时间,而是重新建立了一种“我的数据我做主”的掌控感。今天很多软件都想把用户留在产品里,而 caveman 的理念恰恰相反——它希望你尽快记完、尽快离开、尽快回到真正的生活和工作中去。如果你也厌倦了被工具绑架的感觉,可以花一个周末试着给自己写个“穴居人工具”,哪怕只有两三个功能,用起来也会是完全不同的体验。