ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从“工具负担”到“穴居人”:用Shell和Python写极简命令行记录工具

从“工具负担”到“穴居人”:用Shell和Python写极简命令行记录工具

如果你手头的项目多到一定数量,大概率会产生一种感觉:真正消耗精力的不是干活本身,而是维护那些号称帮你干活的工具。最近几个月,我一直在用自己写的一个小工具,名字就叫caveman,直译过来是"穴居人"。它不是什么大型项目,也做不出啥花哨效果,唯一擅长的事情,就是用最原始、最直接的方式帮我完成记录、归档和查询。我把这套东西发在技术社区后,陆续有朋友来问设计思路和踩坑经历,这次索性把完整过程写出来:从为什么做、怎么设计,到实际跑起来有哪些问题,一次性讲清楚。

caveman 适合两类人:一类是手头工具太多、被各种"效率神器"绑架的开发者,另一类是刚接触命令行、想从零写一个顺手小工具的新手。前者能从中看到"做减法"的价值,后者可以直接抄作业,把里面的脚本改成自己需要的样子。我不想把它包装成什么框架或平台,它就是一堆不超过几十行的 Shell 和 Python 脚本,却已经稳定陪我跑了三个月,哪怕临时换台电脑,五分钟内就能把环境还原出来。

1. 这个项目到底是干嘛的:给"工具负担"做减法

1.1 问题从哪来

我日常的东西很散:工作上有待办、有项目笔记、有随手记下的灵感;生活里有采购清单、有健身记录、有读书摘抄。最早我用的方案是各种 App 组合,备忘录一个、待办事项一个、项目管理一个,再加上云笔记。听起来挺齐全,但用一段时间就发现问题:每个工具都有自己的账号体系、同步逻辑、数据格式,我想把一个想法从备忘录挪到云笔记,还得先复制粘贴、再调整排版。更难受的是,工具越多越不想打开,最后整个记录体系基本处于半废弃状态。

后来我试着把这堆工具砍到只剩一个,结果那个"全家桶"软件又太重了,启动慢、界面堆满了我根本用不到的按钮。我需要的只是一个"能存、能查、不丢"的地方,可这些工具给我的是一堆广告推送和付费套餐提醒。回头看,最讽刺的是:为了追求效率,我引入了一堆复杂工具,它们反而成了新的负担。

1.2 caveman 的定位与目标

caveman 的定位非常窄,就是一个命令行下的个人记录工具。它不管日历、不管邮件、不管文件同步,只管三件事:记录、查询、统计。

  • 记录:把一条笔记、一条待办、一条日志,以最快的速度写进本地文件。
  • 查询:用关键词、日期、类型去过滤之前记录的内容。
  • 统计:快速知道这个月写了多少条、连续打卡多少天、某个项目下有哪些条目。

这三点听起来简单,但真正限制自己只做这三件事,才能避免重蹈"工具膨胀"的覆辙。我给自己定了一条规矩:凡是超过五十行的功能,就先不做,等真需要时再说。三个月用下来,需求依然够用,说明最初对"最小可用"的判断没有跑偏。

1.3 为什么叫这个名字

取名"caveman"是一次自嘲,也是有意的提醒。穴居人给人的印象就是原始、简单、直接:生活里没有多余的东西,但该有的本事一点不少。我在设计这个工具时,就想让它保持这种"穴居人气质"——不追求花哨,不追求功能数量,只做少数几件事,但做扎实。

对比现在的主流工具,"穴居人"反而是个稀缺品质。我用表格列一下自己的感受:

对比点caveman主流效率工具
界面命令行,几乎没有界面图形界面,功能入口多
数据存储本地明文文件云服务,私有格式
启动速度毫秒级秒级起步,频繁更新
配置方式一个文本文件层层设置菜单
离线能力完全离线多数依赖网络
可定制性直接改脚本受限于官方设计

当然,这不是说图形化工具一无是处。团队协作、富文本排版、多端实时同步,这些确实是商业工具的长处。但如果只是个人记录,用"穴居人"方案会轻松得多。数据在自己手里,格式完全透明,哪怕将来这个工具不维护了,文件还在,还能用其他脚本解析,这才是真正的长期主义。

2. 整体设计与技术选型:刻意"落后"反而省事

2.1 设计原则:三个"只"

动手之前,我先给自己立了三条设计原则,所有功能都必须过这三道关:

  • 只用一个入口:所有操作都通过caveman这个命令完成,不搞多个子程序。
  • 只存本地文件:不引入数据库,不做远程同步,数据就是几个普通文件。
  • 只读必要的内容:启动时不做任何初始化检查,不预加载数据,执行完立刻退出。

这三条原则看着简单,实际回过头看非常关键。单一入口保证了心智负担最低,我不用记十几个命令;本地文件保证了可迁移性,备份就是复制文件夹;延迟读取保证了启动速度,基本零等待。

我见过不少"个人项目"一开始也打着极简旗号,后来为了加数据库上了 Redis,为了做看板上了一套前端框架。最后变成一个小型 ERP。caveman 的这三条原则,就是专门用来防止这种事发生的。

2.2 为什么选 Shell 加 Python

选技术栈时我认真想过一轮。候选有三个:纯 Shell、Shell 加 Python、Go 或 Rust 编译成二进制。

纯 Shell 的好处是几乎任何系统都有,坏处是处理字符串和日期时很痛苦,尤其跨平台兼容性容易出问题。Go 或 Rust 的好处是可以发布单一二进制文件,但每次改功能都要重新编译,对一个频繁调整的小工具来说拖慢节奏。最后落在 Shell 加 Python 的混合方案上:

  • 命令入口、参数分发用 Shell,因为它天然就是"胶水",适合把用户输入转发给后端处理。
  • 数据解析、日期计算、统计输出用 Python,因为处理这些逻辑更安全,代码也更清晰。

实际用下来,这个组合非常舒服。Shell 部分只有几十行,主要负责参数校验和调用 Python;Python 部分按命令拆成几个小函数,没有复杂的类继承,纯粹面向过程写,基本谁都能看懂。我在 macOS 和 Debian 上都跑得挺好,没有遇到跨平台的大坑。

如果非要说一个小缺点,就是依赖 Python 环境。好在现在 Linux 和 macOS 基本都自带 Python 3,Windows 上稍麻烦,但我的场景主要在 Mac 和 Linux 服务器上,所以完全接受。

2.3 数据存哪:纯文本还是 JSON

这是设计里最纠结的一点。最初我用的是 JSON 文件,每条记录一个对象,包含类型、时间、内容、标签。好处是解析方便,Python 里json.load就能读。但有个问题:直接用文本编辑器查看或修改不太友好,而且万一文件里混入非法内容,整个 JSON 都会解析失败。

后来我做了折中:主存储用带行号标记的纯文本,每一行是一条记录,开头是类型、时间、标签,后面是正文。举个例子:

[note] [2025-01-12 12:30] [灵感] 需求可以反过来想,先考虑什么时候不该加功能 [todo] [2025-01-12 12:31] [项目A] 梳理接口文档 [log] [2025-01-12 12:32] [工作] 完成了版本检查脚本

这样做的优势非常明显:文件可以直接打开看,格式一眼就懂;就算某个功能坏掉,也不影响已有数据;用grep就能做临时查询,根本不需要专门工具。代价就是 Python 解析时要多写一点正则,不过这点成本完全值得。我甚至可以在没有安装 Python 的机器上用cat和grep直接查看内容,这种"退路"让人心里踏实。

2.4 整个目录结构长什么样

caveman 的代码和全部数据放在同一个目录里,方便整体备份。目录结构如下:

~/.caveman/ ├── caveman.sh # 命令入口脚本 ├── cm_cli.py # Python 核心逻辑 ├── cm_config.ini # 配置文件 ├── data/ │ ├── notes.txt # 笔记记录 │ ├── todos.txt # 待办记录 │ ├── logs.txt # 日志记录 │ └── stats.db # 统计缓存(可选)

总共就这么几个文件。没有node_modules,没有虚拟环境,没有构建产物。我从一台新机器恢复使用,只需要把这个目录拷过去,再把caveman.sh软链到PATH里的某个位置就行,前后不到五分钟。这种"整个家当一拷贝就能走"的感觉,是很多大型项目给不了的。

3. 核心实现解析:把"穴居人"风格落到代码里

3.1 命令入口与参数解析

入口脚本caveman.sh最核心的工作是判断用户想干嘛,然后把参数转给 Python。我用的是最原始的case语法,清晰直接,不依赖任何额外工具。

#!/usr/bin/env bash # caveman.sh - 极简记录工具入口 set -euo pipefail BASE_DIR="$HOME/.caveman" PY_SCRIPT="$BASE_DIR/cm_cli.py" CMD="${1:-}" shift || true case "$CMD" in note) python3 "$PY_SCRIPT" note "$@" ;; todo) python3 "$PY_SCRIPT" todo "$@" ;; log) python3 "$PY_SCRIPT" log "$@" ;; list) python3 "$PY_SCRIPT" list "$@" ;; stats) python3 "$PY_SCRIPT" stats "$@" ;; config) python3 "$PY_SCRIPT" config "$@" ;; help|--help|-h) echo "用法: caveman [note|todo|log|list|stats|config]" ;; *) echo "未知命令: $CMD,使用 caveman help 查看用法" >&2 exit 1 ;; esac

这段脚本的关键点有两个。第一,set -euo pipefail打开,这样脚本遇到未定义变量或管道错误时不会闷声失败,因为记录工具宁可报错也不能悄悄写坏数据。第二,参数转发用"$@"而不是$*,确保带空格的内容能完整传给 Python,我第一次写的时候在这吃过亏。

3.2 核心命令:快速写入

Python 侧的逻辑不玩花活,我把每个子命令拆成函数,按参数个数和数据格式处理。以note为例,最简版本的写入函数如下:

import sys, os, json from datetime import datetime BASE_DIR = os.path.expanduser("~/.caveman") DATA_DIR = os.path.join(BASE_DIR, "data") def ensure_config(): if not os.path.exists(DATA_DIR): os.makedirs(DATA_DIR, exist_ok=True) def write_record(cmd, args, tag=None): ensure_config() path = os.path.join(DATA_DIR, f"{cmd}s.txt") now = datetime.now().strftime("%Y-%m-%d %H:%M") tag = tag or "默认" content = " ".join(args).strip() if not content: print("内容不能为空") sys.exit(1) with open(path, "a", encoding="utf-8") as f: f.write(f"[{cmd}] [{now}] [{tag}] {content}\n") print(f"已记录到 {path}") if sys.argv[1] == "note": # 用法: caveman note [-t 标签] 内容... if sys.argv[2] == "-t": write_record("note", sys.argv[4:], sys.argv[3]) else: write_record("note", sys.argv[2:])

有朋友问为什么不用print("写入成功")这样更友好的反馈,而是返回文件路径。我的想法是,极简工具的每一次输出都要有信息量,路径比"成功"两个字有用得多:万一将来想手动打开文件,路径可以直接复制。另外,追加模式用"a"而不是"w",这个看似基础,但真的很关键,写错一次就会把之前的记录清空。好在我只在测试环境踩过这个坑。

3.3 查询与统计怎么处理

查询是数据积累之后最重要的能力。我设计的list命令支持按日期范围、按关键词、按类型过滤。实现思路很简单:读文件,拆行,用startswith和in做匹配。没有用数据库索引,因为这些文本最多也就几千行,每次全量扫描的时间几乎可以忽略不计。

def list_records(cmd, keyword=None, date=None): path = os.path.join(DATA_DIR, f"{cmd}s.txt") if not os.path.exists(path): print("还没有任何记录") return with open(path, "r", encoding="utf-8") as f: for line in f: if keyword and keyword not in line: continue if date and date not in line: continue print(line.rstrip())

统计这块稍微做了点"聪明事"。我维护了一个stats.db,其实也不是数据库,而是一个 JSON 缓存,记录每天的写入次数。统计时先读当天缓存,再做简单聚合。为什么不每次现场扫文件?因为文件会越积越多,而大多数统计场景只关心最近三十天,缓存能省掉不必要的扫描,同时保持逻辑简单。

3.4 极简配置:一个 INI 文件搞定

配置方面我刻意做得很保守。没有命令行配置文件生成器,默认配置写在cm_config.ini里,内容如下:

[default] data_dir = ~/.caveman/data default_tag = 默认 time_format = %Y-%m-%d %H:%M history_days = 30

Python 里用configparser读取,读取失败就用内置的默认值。这样设计的好处是:配置文件丢了,工具还能跑;想改默认值,直接编辑这个文件就行。我不做复杂的多环境配置,因为个人工具的使用场景就是"一台电脑、一个用户、一份配置",多环境需求往往是自己给自己加戏。

4. 完整实操:从一台干净机器把它跑起来

4.1 环境准备

实操前需要准备的非常少:一台安装了 Python 3 的 Linux 或 macOS 机器,一个能用的文本编辑器。我用的是 Debian 服务器和 MacBook 两个环境,实测一致。

我建议把整个目录放在~/.caveman/,这样既不会污染项目文件夹,也方便统一备份。如果对路径有洁癖,也可以放在自定义目录,但必须在脚本里同步修改BASE_DIR,否则运行时会找不到数据。

4.2 安装与初始化

安装其实就是三步:拷贝脚本、创建数据目录、建立软链。

mkdir -p ~/.caveman/data cp caveman.sh cm_cli.py cm_config.ini ~/.caveman/ chmod +x ~/.caveman/caveman.sh ln -s ~/.caveman/caveman.sh /usr/local/bin/caveman

执行完后,在任意目录输入caveman help,应该能看到用法说明。这里有个很多人第一次踩的坑:/usr/local/bin可能不在当前用户的PATH里,或者没有写权限。如果command not found,可以用echo $PATH检查,或者直接用sudo ln -s。我自己更喜欢用~/bin目录,然后把~/bin加进PATH末尾,这样不用动系统目录。

4.3 日常使用演示

初始化完成后,我模拟一次真实的记录场景:

# 记一条笔记,带标签 caveman note -t 灵感 把某些重复工作脚本化的关键是要先约3分钟流程 # 记录待办 caveman todo -t 项目A 完成caveman博客文章初稿 # 记录日志 caveman log 上午整理了模块接口设计,下午写了测试用例

每执行完一条,终端都会返回数据文件路径,方便我随时去查看原始文件。查询时可以用:

caveman list note # 查看所有笔记 caveman list todo -k 博客 # 只看提到"博客"的待办 caveman list log -d 2025-01-12 # 只看某一天的日志 caveman stats # 查看最近统计

我有一个使用习惯:每次记完待办,完成后不会删除,而是追加一条[x]标记。这样保持历史完整,统计完成率时也有据可查。删除记录这件事我本来就没做,因为文件是纯文本,真想删除直接打开文件编辑,反而比数据库操作更直白。

4.4 与系统级工具联动

caveman 的另一个舒服之处,是能和系统自带的工具天然配合。比如每天下班前,我通过 cron 或 systemd timer 让它自动生成当天的工作日志占位符:

# 每天 18:00 自动插入当天日志占位符 0 18 * * * /usr/local/bin/caveman log -t 工作 自动占位,请在复盘后补充

另外我给caveman note做了个 shell alias,让输入更快:

alias cmn='caveman note -t 速记'

现在遇到临时想法,直接敲cmn 这是个值得验证的思路,三秒钟就完成记录。这种"随手记录"的顺畅感,是我用过的花哨工具中很少有的。

5. 常见问题与排查实录

5.1 命令找不到与环境变量问题

很多人第一次跑就卡在caveman: command not found。排查顺序我用表格整理出来,省得每次翻文档:

症状可能原因排查方式
command not found软链未建立或路径不在 PATH执行which caveman或type caveman
Permission denied脚本没有执行权限执行ls -l ~/.caveman/caveman.sh,补chmod +x
在某些终端能用,某些不能PATH 配置不跨 shell检查~/.bashrc、~/.zshrc里的 PATH 配置
软链状态是坏的原文件路径被移动过执行ls -l /usr/local/bin/caveman看链接指向

我自己的情况是 Mac 上默认 shell 是 zsh,但早期只在.bashrc里加了 PATH,导致新开终端时命令时有时无。后来统一在.zprofile里配置,所有终端会话都正常了。

5.2 中文内容乱码

如果数据文件里存了中文,在终端查询时出现乱码,大概率不是工具的问题,而是终端编码环境没设对。处理方式:

export LANG=zh_CN.UTF-8 export LC_ALL=zh_CN.UTF-8

Python 侧读写文件时,我已经强制指定encoding="utf-8",所以只要终端环境变量正确,中文显示就不会有问题。如果之前已经写入了乱码文件,可以用 iconv 做一次转码补救,但如果原始内容已经损坏,基本救不回来。这也提醒我一个重要原则:记录工具宁可保存速度慢一点,也要保证写入的数据是完整的,所以我在代码里避免使用快速但可能断行的拼接方式。

5.3 文件损坏与备份

纯文本文件看起来简单,但也有自己的风险。最典型的是:如果你同时开了两个终端窗口,在同一秒各自执行一次追加写,理论上可能发生相互覆盖。虽然概率极低,我在代码里也不做文件锁,这种情况下最常见的表现是写完的文件行尾异常。

我的方案不是锁文件,而是做多重备份。每天凌晨用 cron 自动把~/.caveman/data打包压缩到backup/目录:

0 3 * * * tar czf ~/.caveman/backup/caveman_$(date +\%F).tar.gz -C ~/.caveman data

保留最近三十天的备份,其余清掉。这样即使某个文件坏了,也能找到昨日版本。毕竟记录工具最大的价值,是长期积累的数据,不是代码。

5.4 多端同步的冲突问题

说到同步,我必须提醒:caveman 这种"单机纯文本"的方案,是不适合多端实时同步的。我试过把它放在网盘目录里同步,结果两台电脑同时编辑时,文件冲突版本会让人崩溃。后来我调整了用法:以一台主力设备为主,其他设备只读。真正要跨设备查看时,用网盘的历史版本恢复即可。

如果你确实有几台设备轮流用,我给两个建议:一是把每天的记录任务集中在主力机上,二是同步前手动更新一下文件时间戳。不过说实话,个人记录里 95% 的场景用不到多端同步,为了这 5% 的需求引入同步冲突,反而破坏极简属性。

5.5 极简陷阱:过度简化会变成另一种负担

最后想说一个不太被人注意的问题:极简本身也可能被过度形式化。有一次我把命令参数改成"尽量短",比如caveman n代替caveman note,结果一周后我自己都忘了n代表什么。那时我突然意识到,极简的边界应该是"降低认知成本",而不是"字面意义的短"。现在的命令已经固定为完整的note / todo / log / list / stats,输入多几个字母无所谓,重要的是看一眼就知道在干嘛。

6. 后续扩展与个人心得

6.1 把 caveman 变成"项目周报生成器"

用了一段时间后,我发现log命令里的内容可以二次利用。我写了个小的 Python 片段,每周一把logs.txt里本周的记录按日期汇总,自动生成周报初稿。实现也不复杂:过滤时间戳在最近七天的记录,按天分组,输出成简单的 Markdown 列表。这让我彻底摆脱了"每周回忆上周干了什么"的窘境。

6.2 加密与远端备份

数据是明文,放在电脑里一般没问题,但如果涉及到敏感信息,我建议在备份层面处理,而不是改 caveman 本体。我是这样做的:用 gpg 对备份包做对称加密,然后把加密文件上传到自带的对象存储空间。恢复时先解密再解压。整个过程只有两条命令,不在工具的代码里增加任何复杂性。

6.3 我在实际使用中的最大体会

如果让我说这段折腾里最有价值的部分,倒不是脚本本身,而是那个"做减法"的决策过程。每想加一个功能,我都会先问自己:不加它会有什么实际损失?大多数时候答案是"没有"。caveman 三个月没增加任何新命令,我却越用越顺手,因为所有注意力都集中在数据内容上,而不是工具功能上。

最后再分享一个小技巧:我会定期打开notes.txt,用编辑器做一次人工整理,把过时的想法删掉、把有价值的内容合并。这个过程看起来"原始",却比任何自动整理工具都有效。因为自动整理只能按照你设定的规则移动文字,而人工整理能真正帮你重新思考那些记录的价值。这就是我觉得"穴居人"风格最迷人的地方:不依赖复杂的自动化,把控制权牢牢握在自己手里。

返回列表