2026/10/9 12:37:47

命令行待办事项应用实战:从设计到日常使用

命令行待办事项应用实战:从设计到日常使用 1. 为什么我坚持用命令行做待办事项管理我一直有个执念日常待办这件事不应该被一个占着几百兆内存的图形应用绑架。两年前我换回命令行做待办事项应用把任务管理彻底搬进终端之后最大的感受不是变酷了而是负担消失了。打开手机备忘录要解锁、找图标、等加载、点新建再输两三行字这一套流程下来快也要十秒而命令行只需要一个固定命令输入任务名回车完事。这个差距在一天只记三五条时不明显但当它变成每天要操作几十次的高频动作时操作成本直接决定了你愿不愿意用。我构建的这套东西本质上是一个任务记录与执行状态跟踪器。它能让你在终端里随时随地新增任务、列出未完成事项、标记完成、删除误录条目还能按优先级排序、模糊搜索、输出统计报表。听起来功能不多但恰恰是这些有限的、明确的功能构成了一个稳定的日常中枢。它适合三类人一是日常重度使用终端、不想切换上下文的开发者二是习惯GTD但讨厌被花哨UI带偏节奏的效率控三是对数据归属极其敏感希望待办记录以纯文本形式掌握在自己手里的使用者。很多朋友会问市面上Todoist、滴答清单、Microsoft To Do不都挺好吗功能又多又好看你这不是自己造轮子吗我的回答是是但也不完全是。图形工具的打卡、日历联动、云同步确实香但它们的核心交互逻辑是打开应用、找到位置、点击按钮而命令行的核心逻辑是输入指令、直接执行。前者适合在App内浏览和管理任务后者适合在任何界面下无脑添加任务并快速看到结果。如果你的工作主战场就是终端那么一个工具如果不在终端里它就不存在。这才是命令行待办事项应用最大的意义——它把任务管理从一次任务变成了一次操作。本文会把我从设计到实现、再到日常打磨的全部过程拆开来讲包含数据存储选型、命令解析逻辑、显示优化、Shell集成、常见坑位以及我是如何去平衡够用就好和持续演化这两件事的。如果你也想做一个自己真正会天天用的命令行小工具这篇应该能帮你少走不少弯路。2. 定位与方案选型这不只是一段脚本而是一个随手就能用的系统2.1 从一次随手记录开始的产品边界做命令行工具最大的诱惑是无限加功能。今天想加个日历视图明天想加个番茄钟后天想接入云同步功能越加越多最终变成一个窝在终端里的四不像。我在第一版就给自己划定了边界它只做四件事——记录任务、查看任务、变更状态、删除任务。辅助能力只保留两项按优先级或时间过滤、统计完成情况。除此之外的一切功能都通过开放数据去交给外部工具完成而不是全部塞进应用本身。这个思路来自一个很朴素的观察待办事项这个品类的核心价值不是管理而是记录与提醒。图形应用把大量精力花在日历、分类、标签、项目、协作上但对个人高频使用来说真正每天被重度调用的还是我接下来要做什么和这件事做完了没有两件事。其余的记录周期越长的功能越不常用也就越不值得为它增加应用复杂度。确定边界后我在纸上列了一张功能优先级表优先级功能核心诉求实现复杂度P0新增任务 (add)最快的路径记下待办低P0列出任务 (list)一眼看清今天剩什么低P0完成任务 (done)状态流转低P0删除任务 (delete)误录后的兜底低P1编辑内容 (edit)修正任务描述中P1按优先级/日期过滤聚焦高价值事项中P2统计报表周度/月度自我回顾低P2自然语言添加如明天上午9点开会高P0是上线第一天必须有的P1是第二周迭代的P2则属于有空再想想的范畴。实际经验告诉我P2这两项的优先级后来证明排得很对——统计功能确实做完了且很好用但自然语言解析被我砍掉了因为引入时间解析库的收益远不如在输入时用固定格式写日期来得直接。2.2 数据存储选型JSON文件为什么比SQLite和CSV都顺手存储方案是我最先纠结的点。围绕这个问题我认真对比了三种主流方案CSV纯文本、JSON结构化文件、SQLite嵌入式数据库。它们的差异看似只在技术层面实际上直接决定了这个工具的备份方式、可调试性和后期扩展空间。CSV的问题在于表达能力太弱。任务描述里的逗号、换行、引号需要转义优先级、创建时间、完成时间、标签这些字段要么拆列要么挤在一个格子里。一旦字段结构有变化CSV的解析逻辑就要跟着乱。早期我也试过用CSV做速记版TaskManager到后面基本只能应付任务名完成状态两列再想往里面加截止日期和标签就得重构。SQLite是很多人的第一反应用数据库嘛稳、支持并发、查询灵活。但放到这个场景里SQLite有点杀鸡用牛刀。待办事项数据量撑死几千条单机单人写入频率不高而且SQLite文件不可直接用文本编辑器查看想快速改一条数据还要装个客户端或敲SQL语句这对一个务求随手的命令行工具来说反而增加了心智负担。还有一个最实际的问题如果把待办数据放在SQLite里git仓库的diff就完全不可读了我无法用版本管理看到今天改了哪条任务。最终我选择了JSON文件。理由有三一是结构表达能力强嵌套字段清晰字段变更时兼容性好二是人类可读随时随地打开文件用编辑器改改动后应用重新读取即可三是和版本管理天然亲和每一条待办的增删改都能在git diff里清清楚楚地看到。配套做一个简单的原子写入先写临时文件再替换就能规避掉大部分数据损坏风险。2.3 文件放哪里用户目录与当前目录的边界数据文件放在哪里直接影响这个工具在多目录下的一致性。我最初犯过一个低级错误把待办数据存在当前工作目录下的todo.json里结果换个文件夹打开终端就看不到之前的任务了。后来改成统一放在用户主目录下的隐藏配置目录比如~/.local/todo/tasks.jsonLinux/macOS和%USERPROFILE%\.todo\tasks.jsonWindows工具内部通过环境变量自动判断系统类型再拼接出完整路径。这样无论你在哪个目录下敲命令读到的都是同一份全局待办列表。当然有些偏执的用法是把任务文件和项目绑定比如每个Git仓库都有自己的TODO.json配合Commit信息做项目级任务管理。这个我后来也支持了使用规则是如果当前目录有一个显式的./.todo.json优先读它否则读全局文件。两种模式互相不干扰全局管生活日常项目级管开发任务各有各的使用场景。2.4 原子性写入防止断电和异常把数据写坏命令行工具最怕的是写文件写一半崩了导致整份待办丢失。我采用的方案很朴素每次写数据时先把内存中的数据序列化到一个临时文件比如tasks.json.tmp写完后再用os.replace()把临时文件改名为正式文件。这个操作的原子性由操作系统底层保证要么旧文件还在要么新文件完整替换不会出现读到半个文件的情况。此外我还在启动时做了一个快速自检读文件后立刻用JSON解析器验证如果解析失败不直接覆盖写入而是在当前目录生成一个tasks.json.corrupted-时间戳.bak保留现场以便事后恢复。这个机制看起来不起眼但真的救过我一命——有一次我手动编辑文件时语法写错了一半如果应用不管三七二十一直接写入数据就彻底废了。因为有这层保护我只需要从备份文件里手工恢复那一次没保存的改动就行。3. 核心命令链路设计add、list、done、delete是怎么互相配合的3.1 命令解析手写解析器还是用argparse很多入门教程教你用argparse做一个带子命令的工具todo add 买牛奶todo listtodo done 3。可是真到了每天要敲几十次的场景argparse那种带帮助信息、位置参数、可选参数的交互方式会显得笨重。比如todo add --priority high 写方案每次都要敲--priority手速快不起来。我最终的实现是两者结合外层用argparse做标准的命令分发和帮助文档但给高频选项做了短标志和默认值。比如优先级默认是中想标记高优先级只需todo add -p high 任务名。同时为了兼容随手输入的习惯我允许todo a 任务名这样的短命令。命令设计的原则只有一条减少手指移动。所有常用操作的按键次数不能超过15次。命令的规划如下命令别名作用示例adda新增任务todo a 写周报 -p highlistls列出未完成任务todo ls或todo ls --alldoned标记完成todo d 12deleterm删除任务todo rm 12edite修改内容/优先级todo e 12 --new 新内容statss查看完成统计todo s --week3.2 数据结构的核心字段状态机越简单越不容易错每一条任务在JSON里是一个对象长这样{ id: 12, content: 完成项目复盘文档, priority: high, created_at: 2025-04-01 09:30:00, done: false, completed_at: null }字段设计上我刻意保持克制。id是自增整数用于定位任务content是任务内容priority只取low、normal、high三档created_at和done是必填项completed_at只有任务被标记完成时才写入方便后续统计耗时。没有label、没有project、没有assignee个人工具用不到这些。done用布尔值而不是状态枚举是因为任务状态本质是线性流转的未做 - 已完成。如果你设计了待办/进行中/已完成/已取消四态那每个状态的迁移都是逻辑分支排查起来反而费劲。想让状态更丰富的同学可以在content里写[进行中] 改完登录页让文本自己记录进度状态字段保持最朴素的两态即可。3.3 list命令的显示策略默认只看未完成别让历史刷屏list是使用频率最高的命令它的显示策略直接决定了工具好不好用。我的规则是默认只列未完成且按日期过滤的任务--all才展示所有历史。每条任务的展示格式为[3] 高 写季度OKR (04-01 创建) [7] 中 预约牙医 (04-02 创建)方括号里是任务ID中间是优先级然后是内容和创建日期。ID在列表里靠左显示是为了一眼定位配合done命令快速操作。如果你有任务忘记录日期我还会在内容后面加一个(:日期未设置)的提示提醒你补时间让这个工具不仅仅是记录也在倒逼你用时间锚点来管理任务。3.4 done和undo状态流转的完整闭环标记完成是todo done id核心逻辑就是找到那条任务把done改成true写入completed_at。实现不复杂但我在实际使用中很快发现一个需求误标完成怎么办于是加了undo命令todo undo 12把done改回false、清空completed_at。这个命令让状态流转形成了闭环——可以正向推进也可以回滚数据不会因为一次误操作卡在错误的状态里。3.5 任务序号与删除间的处理策略任务ID是自增整数删除一个任务后后面新加的ID继续递增不会回头补号。这个小建模决定在初期引起过争议有朋友建议删除后重新排序ID让显示更连续。但我的经验是ID一旦被引用就不应该变化。因为同事之间互相分享帮忙看下第12条任务时如果重新排序别人的引用就失效了。ID只是定位用的临时标识不连续并不影响使用反而避免了很多混乱。4. 让终端里的待办真正好用的显示与交互优化4.1 颜色与状态用最少的信息跳跃区分关键内容命令行输出的颜色设计很容易走极端。有人每行都上色结果整屏大红大绿视觉噪音比价值还大。我的原则是颜色只承载两类信号高优先级任务用红色系标记已完成任务在--all模式下用灰色/弱化色呈现。优先级中的normal默认用普通默认色不出现在屏幕上造成额外负担。这样一眼扫过去哪些事最紧急、哪些事已经结束立刻就能分辨出来不至于在五颜六色里重新做一次信息筛选。在实现上我写了一个极简的色彩包装函数使用ANSI转义码不依赖任何第三方库def c(text, code): return f\033[{code}m{text}\033[0m red lambda s: c(s, 31;1) gray lambda s: c(s, 90)判断当前输出是否为终端其实也值得留个心眼——当你把输出重定向到文件时ANSI颜色码会混进文本。为此我加了一个检测如果不是TTY就关闭所有颜色转义直接输出纯文本。这个细节不注意的话你在管道里一重定向日志文件里就全是什么\033[31;1m之类的乱码。4.2 模糊查找与ID定位两种方式应对不同场景ID定位适合任务多、能确定序号时快速操作但日常中最常用到的反而是模糊搜索。我把搜索能力放进了list命令里todo ls 关键词匹配成功的结果会在界面里高亮显示可以多次叠加关键词做精确筛选。这比单独做一个search子命令更顺手因为它没有退出当前的列任务上下文。这里有个搜索性能的疑问几千条任务遍历一遍会不会慢完全不会。单文件JSON在几千条量级下全文匹配的时间在毫秒级压根不需要索引。那些数据上了几万条才开始考虑的搜索优化在这个场景下属于过早优化。4.3 Shell集成别名、Tab补全、自动提示命令行工具要真正融入日常光有软件本身还不够外壳配合至关重要。我把关键操作绑定了别名比如t、ta、td、tl让手指工作量大减。这一步的体验提升比任何内部功能优化都明显# ~/.zshrc 或 ~/.bashrc alias ttodo alias tatodo add alias tltodo ls alias tdtodo doneTab补全则解决了不记得命令长什么样的问题。给工具配了简单的补全脚本后在zsh里敲todo d然后按Tab会直接列出done、delete等候选在待办条目很多时盯着屏幕想想它到底叫什么名这个心智负担就被移除了。此外我还在Shell的提示符Promote里加了未完成任务数。比如在~/.zshrc里读取待办文件的未完成数显示为[TODO:5]。这个设计极其管用——你不需要主动打开列表任务数量就一直在提示符里盯着你。当数字是0时候那种今天可以收了的感觉是很有成就感的。4.4 多终端协同让待办数据跟到每一台设备上我有一台办公主机、一台家庭本和一个云服务器三台机器都装在终端里调同一个待办列表。做法是把存储目录放到一个同步盘上比如Dropbox或坚果云的本地同步目录再让各个机器上的工具都指向同一个文件路径。因为JSON文件本身是纯文本同步进程对它的低冲突写入非常友好。虽然技术上极端情况下两个终端同时写会有一次冲突但个人场景下这个概率极低。这块如果不想折腾也可以退回主用一台机器——关键是命令行方案天然让待办数据到底在哪这件事完全由你掌控这是云服务给不了的确定性。5. 统计、提醒与系统能力接入让工具不再只是一份清单5.1 完成率与耗时分析用统计回归数据价值当任务被记录了日期和完成时间统计就有了原料。我给工具加了stats子命令支持查看当日完成数、本周累计完成数、全量完成率还能算出平均每项任务从创建到完成的时间。这些指标一个月的积累后看自己的真实产出比任何打卡软件都更直观。统计的实现不复杂遍历一次JSON文件即可def week_summary(tasks): now datetime.now() week_start now - timedelta(daysnow.weekday()) done_in_week [t for t in tasks if t[done] and parse(t[completed_at]) week_start] return { week_done: len(done_in_week), total_done: len([t for t in tasks if t[done]]), avg_hours: average_duration(tasks), }这种设计带来的隐性收益是我开始对自己的时间感有更实际的锚点。原来总觉得一天做了很多事可统计上只有两条任务完成有些天只标了两三条任务但都是硬骨头。数字帮你校准主观感受这是待办工具最被低估的价值。5.2 终端通知任务到期提醒的正确姿势命令行工具最缺的就是主动提醒能力——用户不开终端它就沉默。我的解决方案是配合操作系统的通知服务做了一层轻量提醒。在Linux上用notify-send弹出桌面通知macOS用osascript -e display notification ...Windows则走PowerShell的[System.Windows.Forms.NotifyMessage]。工具本身只管当前时间有没有快到截止时间的任务提醒的具体方式交给系统去适配。用法上我在安装了这层提醒能力后配置了一个crontab任务每30分钟检查一次*/30 * * * * /usr/bin/python3 ~/bin/todo.py remind --due-in 60这样终端不常开的人也能收到通知。对于那些全程在终端里的朋友甚至不需要系统通知——直接在todo ls里高亮今天到期的任务就足够配合Shell欢迎语里的待办数字实现每日自动播报。5.3 自然语言日期解析妥协和取舍我之前说砍掉了自然语言解析但在提醒功能做到一半时还是忍不住做了一个极简版本。支持了今天、明天、下周X和MM-DD四种表达式其余一律走完整日期输入。这个设计避开了引入dateutil之类的重型依赖也能覆盖八成使用场景todo a 下班取快递 --due 今天 todo a 周会材料 --due 明天 todo a 纪念日准备 --due 07-01解析逻辑用正则实现十来行代码至今没出过毛病。如果你判断自己的工具用户对自然语言的诉求很强再去上大而全的解析器否则大部分Web应用里的明天下午3点在命令行里完全可以被2025-04-02 15:00替代因为敲下的成本并没有高到不可接受。5.4 一个Bash最小版本当你连Python都没有时有时候你在别人的服务器上临时要记一条待办又不想搬一套Python脚本过去。这种情况下一个3行Bash的函数也能顶一阵子todo() { local f$HOME/.todo.list [ ! -f $f ] touch $f case $1 in add) echo $2 $3 $f ;; ls) cat -n $f ;; done) sed -i $2d $f 2/dev/null || sed -i $2d $f ;; esac }这段函数极其简陋连优先级和日期都不支持但它能在完全依赖Bash的机器上完成记、看、删三连操作。更重要的是它再次印证了我的核心观点待办工具的实质不是技术架构多恢弘而是能以零负担的速度进入你的工作流。真正复杂的版本都是在满足这个前提之后再慢慢长出来的。6. 实战避坑从能用到经得起每天用的几道坎6.1 Windows与Linux/macOS的编码差异如果只在macOS或Linux上跑编码问题几乎不用操心。但一旦要在Windows的cmd或PowerShell里显示中文坑就来了。第一次在Windows上跑todo ls时列表里的中文全部变成了乱码排查一圈发现是编码问题Python默认按UTF-8写入JSON文件而Windows控制台的默认编码是GBK。解决方案分两层输出时统一用sys.stdout.reconfigure(encodingutf-8)强制标准输出按UTF-8处理同时提醒用户在cmd里先执行chcp 65001切换到UTF-8代码页。如果用的是Windows Terminal而不是老旧的conhost这个问题基本绝迹。现在跨平台测试时我第一关注点就放在中文输出上这一道坎我一共踩过三次教训是永远不要假设控制台编码和你预期一致。6.2 多人同时操作同一份数据的缓存问题虽然工具定位是个人待办但我确实遇到过一次真·并发场景手机端通过SSH连到服务器同时家里的电脑也在跑着同一个工具。两个进程各自读取了JSON文件的一份缓存然后分别写入结果后写的人把先写的人那条记录覆盖了。这个问题严格来说不是命令行工具该操心的但既然发生了我把写入逻辑调整成了写前重新读取最新数据再把改动合并进最新数据的方式从根源上缓解了覆盖风险。对于99%的个人用户来说这个问题不需要上线时解决但设计数据结构时保留updated_at字段会在将来省很多事——冲突发生时至少能判断谁是更新的版本。6.3 自动备份放在Dropbox里不等于有备份我把待办文件放在同步盘里之后曾一度觉得同步就是备份直到一次校验中误操作把整个文件清空同步盘立刻把空文件同步到了所有设备。那次教训让我意识到同步是同步备份是备份不要把两者混淆。现在我给工具加了一个策略每次启动时自动备份昨天的文件到backup/tasks-日期.json只保留最近30份。这样即使文件被清空或错误写入也能找到当天较早的备份。这个设计用了大概十行代码但它是整个工具里最值得的十行之一。6.4 从玩具到工具的分水岭放弃频繁重构早期的我特别喜欢重构——今天改存储结构明天换命令格式结果数据迁来迁去命令换来换去工具停摆了好几周最后还是回退到最初的用法。后来我给自己定了一条规矩存储结构和命令名是稳定接口一旦稳定下来至少半年内不动。功能可以加入口不能轻易改。真正的分水岭不是我加了多炫酷的提醒而是我终于意识到工具的需求是使用者的习惯沉淀出来的不是凭空设计出来的。我砍掉了很多潜在功能的冲动把时间花在让add快一点、让ls更清楚一点、让done更不容易手抖上。投入的这些琐碎改进最终让这个命令行待办事项应用从一个技术玩具长成了我每天都离不开的基础设施。7. 末尾一个值得一试的扩展方向最后分享一个我最近在做的小扩展把待办和Git提交记录联动。每当我标记一个任务完成时工具会生成一条包含任务ID和内容的commit消息模板配合我惯用的commit规范自动落地。这样一来项目管理里的任务完成和代码仓库里的变更记录就自然挂上了钩。如果你也想从零构建一个命令行待办事项应用我的建议是把第一版控制在一百行代码以内支持add、ls、done、delete四个命令加上JSON存储就够了。后续的颜色、提醒、统计、同步都是在你已经把每次打开终端会下意识敲一下t这个习惯养成了之后再慢慢加进去的。工具做到最后真正的壁垒不是技术而是你愿意每天维护它的那一点点惯性。