Hotdry.
systems

用 Bash 管道构建可 hack 的个人新闻阅读器:流式处理与模块化设计实践

剖析 haron 项目的管道化架构设计,展示如何通过纯 Shell 命令组合实现 RSS 过滤、LLM 智能筛选与零依赖部署的个人新闻阅读工作流。

在命令行工具日益复杂的今天,一个名为 haron 的项目选择了回归本源 —— 它完全用 Bash 写成,不依赖任何重型运行时或框架,仅通过管道将若干 Unix 工具串联起来,就实现了一个可定制、可 hack 的个人新闻阅读器。这种设计理念不仅令人耳目一新,更揭示了管道式架构在 CLI 工具开发中的独特优势。

管道化架构的核心设计

haron 的架构可以用一句话概括:它是一条流动的数据处理管道,每个阶段职责单一,通过标准输入输出无缝衔接。从功能上看,这条管道可以划分为四个核心阶段:数据获取、格式解析、智能过滤、以及终端呈现。每个阶段都是一个独立的 Bash 函数,它们之间不共享状态,传递的仅仅是 JSON 格式的文本流。

数据获取阶段负责从配置的 RSS 源拉取原始内容。这里使用 curl 命令下载 feeds,并借助 feedparser 库进行 XML 解析。值得注意的是,haron 选择了 uv 来管理 Python 依赖,而非将 Python 脚本打包成独立的可执行文件。这种做法的好处是,用户只需要安装一个 uv,就能自动获取所有必要的 Python 包,而项目本身不需要维护复杂的依赖声明文件。

格式解析阶段是整个管道的承上启下环节。原始 RSS 数据经过解析后,被转换成统一的 JSON 格式,每条新闻条目包含标题、链接、语言、发布时间等标准化字段。这一阶段主要依赖 jq 命令完成数据清洗和转换 —— 删除空值字段、规范化日期格式、过滤掉格式异常的条目。jq 在这里扮演了 "管道胶水" 的角色,它的表达式既可以完成简单的字段选择,也能实现复杂的条件判断和数据转换。

智能过滤是 haron 区别于传统 RSS 阅读器的关键所在。它没有使用基于关键词的规则过滤,而是引入了一个 LLM 作为个人兴趣过滤器。用户在一个私密的 Gist 中用自然语言描述自己的关注点,LLM 会根据这段描述判断每条新闻是否值得阅读。过滤逻辑同样通过管道实现:JSON 数据被转换成提示词发给 LLM,LLM 返回布尔值,jq 根据这个值决定条目的去留。整个过程完全是流式的 —— 不需要把所有数据加载到内存,LLM 可以逐条判断并立即返回结果。

终端呈现阶段负责将过滤后的结果格式化并展示给用户。这里用到了 bat 来提供语法高亮和分页功能,用 pandoc 处理可能存在的富文本内容。呈现函数被设计成可以 "优雅降级" 的:如果用户没有安装 bat,就用原始的 cat;如果没有 pandoc,就跳过格式转换。这种设计让 haron 能够在各种环境下运行,不必对用户的工具链提出苛刻要求。

模块化组合与扩展机制

haron 的模块化设计体现在两个层面:函数级别的职责分离,以及管道级别的灵活重组。每个函数都遵循 "输入从 stdin 来,输出到 stdout 去" 的原则,这使得用户可以在任何环节插入自己的处理逻辑。比如,如果你想给新闻标题加上 Emoji 前缀,只需要在 format 函数之前插入一行 jq 命令:

entries | jq '.title = "[📰] " + .title' | format

这种即插即用的能力正是管道架构的魅力所在。与面向对象设计中的依赖注入相比,管道的组合方式更加直观 —— 你不需要理解函数的内部实现,只需要知道它的输入输出格式,就能把它嵌入到合适的位置。

扩展 feeds 同样简单。项目的 README 展示了如何修改脚本底部的 feeds 调用:

feeds \
    https://hnrss.org/best \
    https://your-favorite-feed.com/rss \
    &

每个 feed URL 都是独立的,它们并行下载,结果汇入同一条管道。如果你有特殊的订阅源,甚至可以写一个自定义函数来获取数据 —— 只要它的输出格式与其他 feeds 保持一致,管道就能无缝衔接。

翻译功能是另一个值得借鉴的扩展点。haron 默认将非英语新闻的链接包装成一个翻译服务的 URL,用户可以在两个地方自定义这一行为:一是顶部的 TRANSLATION 变量,它指向不同的翻译服务;二是 format 函数中的 jq 过滤器,它决定哪些语言需要翻译。这种 "配置 + 过滤" 的双层定制机制,既照顾了大多数用户的默认体验,又给有特殊需求的用户留足了空间。

零依赖部署的工程考量

haron 的依赖列表极为精简:uv、jq、bat、pandoc,再加上一个 LLM 客户端(默认用 llm 工具调用 Gemini)。这四个依赖都是 Unix 生态中的经典工具,在大多数包管理器中都能一键安装。更重要的是,它们都是静态链接的独立二进制,不需要运行时、不需要配置环境变量、不会与其他软件产生冲突。

uv 的引入是近两年 CLI 开发领域的一个有趣趋势。作为 Python 的现代包管理器,uv 不仅安装速度快,还能生成独立的虚拟环境。haron 利用 uv 运行 feedparser,实际上是把 Python 项目的复杂性隐藏在了 uv 背后。用户不需要知道什么叫 pip、什么是 requirements.txt,只需要运行 uv run ./news.sh,uv 会自动创建环境、安装依赖、执行脚本。这种 "零配置" 的使用体验,对于分发给非技术用户尤其有价值。

bat 和 pandoc 的存在则体现了 haron 对终端体验的追求。bat 是 cat 的增强版,它提供了语法高亮、行号显示、Git 集成等功能,让纯文本输出也具备了一定的视觉层次。pandoc 是文档转换的瑞士军刀,能够把各种格式的文本转换成 Markdown 或其他格式。这两个工具的可选性也很有意思 ——haron 并不强制要求它们,而是通过函数内部的条件判断来决定是否使用。如果你只需要最基本的阅读功能,完全可以移除它们,管道的其他部分照常运行。

生产可用的参数配置

在将 haron 这样的管道工具部署到生产环境时,有几个关键参数值得注意。首先是 LLM 调用的超时设置 —— 网络不稳定时,LLM 可能长时间无响应,建议设置 30 秒到 1 分钟的超时,避免整个管道被阻塞。其次是 feeds 的并行度,默认的并行下载数量应该根据网络状况和 API 限流来调整,如果某个 feed 频繁超时,可以考虑将其单独串行化。

日志和调试是另一个实用话题。haron 支持通过 DEBUG 环境变量开启调试模式,此时管道中会插入额外的 tee 命令,将中间结果输出到 stderr。对于排查 feeds 解析错误或 LLM 判断异常,这个功能非常有用。生产环境中,建议将 DEBUG 输出重定向到日志文件,而不是直接打印到终端。

最后是 Gist 的访问权限问题。由于兴趣描述可能包含敏感信息(如公司名称、项目名称),haron 要求使用私密 Gist 来存储这份配置。这带来了一点运维复杂度 —— 你需要在多台机器上配置 GitHub 认证,才能让脚本访问这个私密 Gist。一个常见的解决方案是使用 GitHub CLI 的 gh auth token 命令,将认证信息缓存到本地,这样就不需要在环境中明文暴露 GIST_ID。

从 haron 看管道式工具的边界

haron 的设计让人重新审视管道式架构的适用场景。它最适合处理 "流式、无状态、可组合" 的数据处理任务 —— 比如 RSS 过滤、日志分析、数据转换。但它也有明显的局限性:复杂的业务逻辑(比如需要维护会话状态、需要与多个外部服务交互)在管道中会变得笨拙;错误处理和重试机制难以优雅实现;调试大型管道也需要相当的耐心。

haron 的选择是承认这些局限,然后在自己的领域内做到极致。它不试图做一个通用的 RSS 阅读器,而是做一个 "可 hack 的个人新闻工作流"。用户可以根据自己的需求增删管道阶段,甚至把整个项目当作模板,从零开始构建自己的自动化流程。这种定位让 haron 在众多 CLI 工具中独树一帜 —— 它不是终点,而是起点。

资料来源:haron/news.sh 项目(https://github.com/haron/news.sh)

查看归档