ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

caveman:用纯Shell脚本打造极简任务管理工具

caveman:用纯Shell脚本打造极简任务管理工具 如果你翻开源社区那些动辄几百兆、依赖树比原始森林还密的“现代效率工具”难免会在某个深夜产生一个念头这到底是在管理任务还是在给电脑缴纳维护税我前一阵子清理系统时发现一个有趣现象——真正陪我度过无数个工作周期的反而是一个用纯Shell脚本写的、名叫caveman穴居人的任务管理工具。它没有数据库、没有图形界面、没有Node依赖甚至没有超过两百行代码却能精准承载我从晨会到深夜coding的所有待办事项。这篇博文就聊聊这个“穴居人”式项目背后的思路、实现细节和踩坑记录。如果你是那种厌倦了“安装完一个工具还要再装三个依赖才能让它跑起来”的开发者或者只是想用最朴素的方式把任务管理这件事重新拿回自己手里那这篇文章应该正对你的胃口。项目名caveman并非调侃它本身就是在提醒我很多时候最简单原始的工具反而最可靠。1. 为什么做一个“穴居人”风格的任务管理工具1.1 被现代工具逼疯之后的反思先说一段真实经历。之前我同时使用一款知名任务管理软件跨平台同步很漂亮插件生态也丰富但代价是每次启动都要等两秒转圈数据全在云端想改个排序规则得翻三层设置菜单。最崩溃的一次软件自动更新后某个第三方插件失效导致整个应用无法启动我手里积压了三十多个待办事项愣是半小时没能打开自己的任务清单。从那一刻起我开始思考一个问题一个任务管理工具的“必要复杂度”到底有多大本质上我需要做的事情只有三类记录一件事、查看当前有哪些事、做完后标记掉。这三件事完全可以用文本文件加几条命令来完成。数据库不需要。图形界面不需要。云同步Git仓库就够了。于是caveman这个项目诞生了它刻意保留“穴居人”式的原始一个纯文本文件当存储一组Shell命令当交互一颗绝对不用花哨依赖的心。1.2 设计原则原始、直接、可迁移caveman的第一个设计原则是“任何环境都能跑”。它只依赖POSIX标准命令比如grep、sed、awk、date和cat。这意味着从我的MacBook到一台素未谋面的Linux服务器再到别人家的树莓派我都能在五秒钟内把它用起来不需要安装任何运行时。这种选择不是技术水平倒退而是对可靠性的极致追求——越是底层的工具越不容易被淘汰。第二个原则是“数据所有权永远在自己手上”。所有任务都是一个纯文本文件中一行一行的记录编码采用UTF-8。我可以用vim直接编辑它用它做任何我想要的过滤和统计甚至写个Python爬虫去读取它没人能阻挡我。现代软件把数据锁在自有格式里的做法本质上是一种数字圈养而caveman的设计让一切透明可见。第三个原则是“一次学习终身受用”。由于核心逻辑就是Shell命令学到的每一招都能迁移到其他日常操作中。比如我用grep筛选任务的同时也学会了如何快速搜索日志用sed修改任务状态的同时也理解了批量文本替换的精髓。这比学会某个软件独有的快捷键更有价值。2. 核心设计拆解纯文本、Shell脚本、标准命令2.1 数据结构设计一行一条任务的纯文本存储caveman没有选择JSON或YAML这类格式化数据原因很简单它们需要额外的解析逻辑而且人类直接编辑体验不好。最终采用了自定义的紧凑格式每条任务占一行字段之间以制表符分隔。大致结构如下2029-01-25 2029-01-25 3 写季度复盘报告 工作 blog第一列是创建日期第二列是到期日第三列是优先级数字1最紧急数字5最不紧急第四列是任务描述第五列是分类标签第六列开始可以追加任意数量的上下文标签。这种设计有两个好处一是sort、cut、awk等标准命令能直接操作无需写额外解析代码二是即使完全不懂脚本的人也能用文本编辑器手动看懂并修改内容。有人可能会质疑没有唯一ID怎么精确操作某一条任务答案是用行号。尽管排序和筛选后行号会变化但caveman在内部采用“先匹配、再确认”的机制命令会根据你输入的子串找到目标行再通过行号进行修改或删除。因为任务数量通常控制在几十条以内这种朴素映射的准确性足够高而且完全规避了ID生成和自增维护的复杂度。2.2 状态管理优先级、进度、标签的约定任务状态没有用单独的字段而是遵循一套约定如果一行以[x]开头代表已完成否则是未完成。已完成的任务不会被删除只是会被list命令默认过滤掉。只有显式执行clean命令后已完成且超过30天的事务才会从文件中移除。这样设计既保留了历史记录又不会让文件无限膨胀。优先级直接用数字表示支持通过和-微调规则十分直觉caveman -p 1 3表示将优先级数字为3的任务上调一级变为2。标签系统也是纯约定分类标签放在第五列采用短英文单词如work、home、study。上下文标签则从第六列开始可以加phone、waiting等用来标注“需要在手机上处理”或“等待某人的回复”。一切可读、可查、可脚本化不搞隐性魔法值。2.3 为什么选择Shell而不是Python/Gocaveman在最初原型阶段其实用Python写过一版代码比现在更长而且带来了一个致命问题想要使用它目标机器上必须装有对应版本的Python解释器。虽然Python如今近乎通用但总有比你想象更简朴的生产环境比如精简版的Docker容器里只有sh和基本工具。换用Shell之后不仅依赖彻底清零还有一个额外红利冷启动速度几乎可以忽略不计。现代Electron应用启动要数秒而这个脚本在Shell里跑一轮循环只需要几十毫秒。另一个原因是组合性。Shell天然支持管道和重定向所以caveman可以跟cron、git、curl等工具配合得天衣无缝。例如我可以通过caveman -l work | grep phone就能把“工作中的手机任务”一条命令筛出来再结合osascript在Mac上提醒整个过程像积木一样灵活。如果用Python写这种外部交互往往要重新封装一层接口反而锁死了扩展性。3. 实操过程从零搭建caveman脚本3.1 基础框架添加任务的add函数完整的caveman是一个单一Shell脚本安装在PATH下即可使用。下面抽取最核心的添加任务逻辑做演示去掉参数校验等细枝末节# 文件: caveman TODO_FILE${TODO_FILE:-$HOME/.caveman/todo.txt} mkdir -p $(dirname $TODO_FILE) add() { local title$1 local due$2 local pri${3:-3} local category${4:-inbox} local today today$(date %Y-%m-%d) printf %s\t%s\t%s\t%s\t%s\tnow\n \ $today $due $pri $title $category $TODO_FILE }这里值得注意两个细节。第一默认分类是inbox这借鉴了GTD思想——凡是来的事情先扔进收件箱等未来某刻再被整理到具体分类。第二TODO_FILE支持环境变量覆盖这让caveman能在不修改脚本的前提下切换不同任务文件比如为工作和生活分别维护一份清单。其他人安装时只需将$HOME/.caveman/todo.txt换成自己习惯的路径即可。调用方式非常直白caveman add 给服务器升级内核 2029-02-01 2 dev critical命令结束后todo.txt中会出现对应的一行。没有创建表格、没有检查到期日是否合法、没有弹窗确认——就是如此“原始”。这带来的好处是脚本体积被压到极低几乎不可能出现自身Bug代价是用户必须对输入格式稍微负责但这种责任反而让人更珍惜每一次添加任务的机会。3.2 列表与筛选基于grep和awk的查询查询是caveman用起来最频繁的功能也是Shell组合艺术的集中体现。基础列表命令会读取所有行然后按到期日排序并输出list() { grep -v ^\[x\] $TODO_FILE | sort -t$\t -k2,2n | \ awk -F\t { printf %d\t%s\t%s\t%s\n, NR, $2, $3, $4 } }这里的sort按第二个字段到期日做数字排序因为日期字符串形如2029-01-25字母序即为时间序。输出中使用行号替代ID用户后续就用这个行号来做完成或删除操作。grep -v过滤掉已完成任务非常简单可靠。对于筛选需求我会用不同的选项来区分-d按到期日筛选-c按分类筛选-s按文本关键词筛选。内部实现就只是把grep条件拼接起来。有一个使用心得值得分享在把参数传入grep时一定要用--作为分隔符否则任务描述中以-开头的关键词会被误判成选项grep -v ^\[x\] $TODO_FILE | grep -- $pattern这个细节我在早期版本中踩过坑当时有一条任务是“修复 - 号导致排序错误”结果每次都搜不到后来才发现是被grep当作命令行选项了。3.3 完成任务与清理机制标记完成和删除是两个高频动作代码里实现得异常朴素。先根据行号取出对应行然后用sed在行首插入[x]标记done() { local line$1 sed -i ${line}s/^/[x]\t/ $TODO_FILE }注意macOS自带BSD版本sed需要空字符串参数才执行原地编辑而Linux上的GNU sed则写-i即可。为了让一个脚本通吃两边我选择在脚本头部根据系统类型定义一个SED_INPLACE变量if [[ $(uname) Darwin ]]; then SED_INPLACE(-i ) else SED_INPLACE(-i) fi这里有个隐藏技巧sed的s命令中使用了^在行首添加内容这使得已完成任务的行会变成[x]\t2029-01-25 ...这种格式。由于列表命令默认过滤以[x]开头的行所以标记完成后该任务立刻从视图中消失而数据依然保留在文件里。clean命令则利用awk过滤掉超过30天的完成项简单干脆awk -F\t !/^\[x\]/ || $230*86400 systime() $TODO_FILE /tmp/todo.tmp mv /tmp/todo.tmp $TODO_FILE3.4 配合cron的每日提醒可选光有存储和查询还不够任务管理必须能唤醒你的注意力。caveman提供了一个remind命令输出今天到期的所有任务。在Mac上我把它和系统通知结合起来用osascript弹窗在Linux上则可以通过notify-send或写入~/.forward发送邮件。最简单的方式其实是配合cron每天执行一次并把输出重定向到邮箱0 9 * * * ~/bin/caveman remind如果想在终端得到更醒目的提醒可以在Shell启动文件中加入caveman remind然后进入终端就会有当日任务清单印入眼帘不用任何额外快捷键。这种低成本的做法比装一个常驻后台的提醒软件要干净得多因为它只在打开终端或到达计划时间时触发平时不占用任何资源。4. 常见问题与排查实录4.1 空格和特殊字符导致的任务解析错乱用制表符作为分隔符已经过滤掉了空格干扰但任务描述中如果包含换行符则会产生令人困惑的行为。Shell命令替换时会吞掉尾部换行而printf又可以在内容中插入字符串中的换行造成一条任务记录占到两行后续解析全部错位。解决的方式是在添加任务时做一个替换把输入中的换行符统一替换成空格。另一个高频事故是通配符扩展。假如任务标题是“检查 *.log 文件”同时当前目录下风好存在一个名为debug.log的文件那么Shell会先展开*.log存入文件的可能是一个意外扩展后的真实文件名。为了阻止这种现象在脚本开头必须给所有变量加上双引号并考虑在接收参数的调用方使用set -f临时禁用通配符扩展。我在实际使用中吃过两次亏之后直接把脚本全局的noglob打开了set -f4.2 跨平台兼容macOS的BSD sed vs GNU sed除了前面提到的-i参数差异另一个常见的坑是正则表达式的支持程度。BSDsed对某些转义符的处理和GNUsed不一致例如\t在BSD里并不会被解释成制表符。所以脚本中凡是要处理制表符的位置我都避免在sed正则里直接写\t而是改用实时生成的真实Tab字符TAB$(printf \t) sed -i s/${TAB}*/ / file对于文本处理中必须使用awk的情况兼容性则好很多但也要注意awk版本之间对正则符号如\n、[[:space:]]的接受程度。我的经验是在一开始就利用uname区分系统和版本来设置不同的命令参数比在几十处代码里塞满if条件要容易维护得多。遇到“这台机器跑不通那台机器又正常”的问题时第一时间就该想到版本差异而非脚本逻辑错误。4.3 中文乱码问题因为任务内容经常是中文文件编码必须明确为UTF-8。大多数现代系统默认没问题但某些老旧的Linux服务器locale可能不是UTF-8导致脚本打印中文时出现问号或乱码。解决思路并不需要改脚本内容只要确保终端环境和caveman的输出流显式设置UTF-8编码。在脚本开头加上export LC_ALL${LC_ALL:-en_US.UTF-8}如果系统没安装对应locale同时中文也仍然乱码那就需要检查终端模拟器本身的字符编码设置。另外一个偏门但常见的问题是从Windows平台拷贝过来的todo.txt文件会携带BOM头导致第一行任务的第一列出现不可见字符排序和匹配都会异常。处理方式是添加一个小功能专门剥离BOMsed -i 1s/^\xEF\xBB\xBF// $TODO_FILE4.4 性能任务多了会不会卡理论上一万个任务的文本文件也只有不到几百KBgrep对这么小的文件完全是瞬间完成。即使同时执行多个筛选也只是毫秒级别。我在一个项目里把历年所有日志任务都积累在一个文件里达到了一万多条caveman list依然感觉不到卡顿。真正影响速度的步骤反而出现在编写脚本时的一种错误在循环里反复打开同一个文件追加内容。例如用cat file对每条记录执行一次文件大时会有额外的I/O消耗。正确做法是像前面那样用printf一次追加或把输出先累积到变量再统一重定向。如果实在追求极端可以用tuned的方式将文件按月份分片但这会让脚本复杂度上升完全违背了caveman的初衷。面对一万条任务我更推荐的做法是定期归档每月自动把已完成任务挪到一个叫archive-YYYY-MM.txt的文件中保持主文件短小而高效。5. 进阶扩展让“原始”工具更顺手5.1 增加颜色高亮Shell终端天然支持ANSI颜色转义序列这可以让列表输出在一众黑白文字中突出重点。实现上不需要任何外部库只需要给不同优先级设置不同的颜色码pri_color() { case $1 in 1) printf \033[31m;; # 红 2) printf \033[33m;; # 黄 3) printf \033[36m;; # 青 *) printf \033[0m;; esac }在打印列表时根据每行第三个字段优先级选择对应的开始颜色并在行尾恢复默认色。这会让临近到期的红色任务非常醒目同时保持纯文本文件的底层数据不受任何污染。注意终端种类对色彩支持不一有些旧终端需要设置TERM环境变量但现代模拟器基本都支持。5.2 与Git结合实现历史版本由于所有任务都存放在一个文本文件里版本管理变成了简单到极致的一件事。在$HOME/.caveman/目录初始化Git仓库每次添加任务或标记完成时自动提交git add todo.txt git commit -m update tasks $(date %F-%T)这样每一次状态变更都有完整历史误删或者想回溯一周前的任务安排都变得轻而易举。甚至可以加一行配置让caveman在每次执行写操作后自动调用这个提交过程我担心自动提交可能会太频繁于是额外加了个逻辑如果距离上次提交超过15分钟才执行Git提交。这个批处理式提交既保留了较细粒度又不会让Git日志刷屏。5.3 其他可以“穴居化”的场景做完了任务管理我发现“穴居人”哲学也能复制到其他工具上。比如用纯Shell写一个记账小工具一行一笔流水月底用awk汇总各项支出再比如用纯文本加make维护一个极简博客每次发新文章就写一个Markdown文件然后通过make publish用三行命令完成构建和部署。这类工具的共同特点是数据永远是纯文本逻辑永远不依赖外部服务行为永远可预期。我在把这些思路分享给朋友的时候有人问“为什么不直接用工具有现成的非要自己造轮子”我的回答是用现成工具当然可以但自己造一个符合心意的“原始”轮子最大的回报不是功能而是对整个系统的掌控感和从容感。当工具出问题时你能读懂它的源代码当环境变化时你能在三分钟内把它迁移到任何角落这种自由是那些黑盒软件给不了的。最后的一个小技巧caveman用了很长一段时间后我发现它最强的定位不是替代所有任务管理软件而是充当一个“任务接入中枢”。无论是Siri快速添加、通过邮件创建任务还是从网页剪藏内容我都可以写一个极薄的中转层把它们转化成一行写入todo.txt的文本。比如我在Mac上用快捷键⌃Space唤起快捷指令然后快捷指令通过ssh在服务器上执行caveman add $text整个过程不到一秒钟却拥有了全平台录入能力。这比任何闭源软件的“快速捕获”都更透明且可控。如果你也被复杂度困住了不妨试试从手头最常用的一个小工具开始“穴居化”。不必追求一步到位只需要把存储换成纯文本、把交互换成几条Shell命令你就能重新触摸到工具最本源的价值。
返回列表