OpenShell实战:用命令仓库与环境切换打造高效终端工作流
刚在一台新机器上把开发环境配置到一半我忽然意识到自己过去大半年一直在重复同一件事找命令、敲命令、改参数、再重敲一遍。项目环境反复切换、脚本散落在各个目录、同一个部署动作换了台电脑就要重新回忆。后来我把这套工作流整体收拢到一个开源工具层里它就是我今天想聊的OpenShell。这篇文章不会讲什么高深理论只分享我实际搭建、使用、踩坑和让它在团队里落地生根的全过程。OpenShell 简单来说是一个开源的 shell 增强层把常用命令、项目环境、自动化脚本统一管理起来解决命令记不住、环境切来切去、脚本没法共享这三个日常最烦人的问题。适合开发、运维、数据分析这类每天都在终端里泡着的人也适合那些刚接触命令行、不想把精力耗在记忆零碎命令上的新手。下面我会把模块设计、具体安装步骤、实战脚本、遇到过的坑和团队协作经验一并写出来照着做基本能直接上手。1. 为什么我会折腾一个开源的 shell 增强层1.1 重复劳动的真实成本日常开发里最消耗耐心的往往不是写代码本身而是那些反复出现的操作手势。比如我要启动一个前后端分离的项目前端要切 Node 版本、执行 pnpm dev后端要激活虚拟环境、跑 uvicorn还要顺手把 Redis 拉起来换到另一个项目时这些动作又完全不同。如果全凭脑子记隔两周再回来就成一团浆糊。我算过一笔账每次切换环境平均要敲 10 到 20 条命令每天切换五六次一个月下来就是几千条命令里面大半是重复且容易出错的。OpenShell 解决的正是这个问题——它把一条命令完成一组操作这件事做成了可配置、可共享、可版本管理的标准化流程。1.2 OpenShell 的核心定位OpenShell 不是要替代 bash 或 zsh也不是又一个美化终端工具。它更像一个粘合层把三样东西组织起来命令仓库把常用命令封装成短别名统一收纳换机器不丢。环境切换以目录为单位绑定环境变量、工具链版本和启动脚本进入目录自动生效。任务编排支持把多个步骤串成一条自动化流水线处理日志清理、依赖安装、部署等重复流程。这三个能力对应了三个真实场景记不住命令、环境容易混淆、重复操作太多。理解了这一点后面看它的配置文件就不会觉得零碎了。1.3 和 oh-my-zsh、fish 这些工具边界在哪很多人会问直接用 oh-my-zsh 加一堆 alias 不也能达到类似效果吗我的体会是oh-my-zsh 解决的是让终端更好用OpenShell 解决的是让工作流可管理。前者偏交互体验后者偏工程化管理。oh-my-zsh 的 alias 是扁平的一堆映射项目多了之后依然要靠脑力维护fish 虽然开箱即用体验好但脚本语法不兼容 POSIX自动化场景里容易碰壁。OpenShell 的做法是用一套结构化的配置文件描述在什么目录、用哪个环境、跑哪些命令这样命令行为是可推导、可测试、可回滚的。它的学习曲线比 alias 高一点点但换来的是长期维护成本的明显下降。2. OpenShell 的三个核心模块命令仓库、环境切换与任务编排2.1 命令仓库把记得住变成查得到命令仓库是 OpenShell 里最常用的模块。它的思想很简单把一条长命令或一组命令用语义化的名字保存下来。比如我在项目 A 里常用的是git pull origin master pnpm install pnpm dev这条命令本身并不复杂但每次手敲都要注意当前目录、注意分支名、注意是不是忘了 install。放到 OpenShell 里就是commands: dev: cd: /path/to/projectA steps: - git pull origin master - pnpm install - pnpm dev之后任何终端里执行ops dev就能完成整套动作。它的好处不只是少敲几个字而是把一组操作变成了一个概念。团队里新人来了不需要问我们项目怎么启动直接看命令列表就能明白。命令仓库的配置文件通常支持 glob 匹配当前目录。比如我在 backend 目录下定义run: python app.py在 frontend 目录下定义run: pnpm dev进入不同目录执行同一个ops run实际跑的是不同的命令。这个能力非常实用它让命令天然带着目录上下文避免全局别名互相冲突。2.2 环境切换项目级上下文管理环境切换是我认为 OpenShell 最有价值的部分。做 Python 开发的人都有这种体验项目 A 用 Python 3.10 venv项目 B 用 3.9 conda项目 C 用 Node 18 pnpm每次切换项目光环境变量就得手动 export 好几条。OpenShell 在配置里支持 per-directory 的环境定义environments: /path/to/projectA: python: /usr/local/bin/python3.10 venv: /path/to/projectA/.venv env: DATABASE_URL: postgres://localhost/projectA CACHE_TYPE: redis on_enter: - source /path/to/projectA/.venv/bin/activate我习惯把它理解成终端里的 .envrc。进入目录后 OpenShell 自动加载这些配置离开目录再还原。相比 direnv 这类独立工具OpenShell 的好处是环境配置和命令配置写在同一份文件里团队共享时一张配置表就能说清楚整个项目怎么跑。这里有个细节值得注意环境变量并集和交集的处理。OpenShell 默认把配置的环境变量注入到当前 shell如果同名变量存在由配置覆盖。这个规则在文档里写得很清楚但新手容易误以为会自动清理多余的旧变量其实不会。我自己的做法是每个项目的 env 字段只写该项目真正需要且可能会冲突的变量公共变量放到全局配置里避免每个项目都重复一长串。2.3 任务编排自动化的串联逻辑第三个模块是任务编排。命令仓库解决一条命令跑一个步骤任务编排解决一条命令跑完整条流水线。比如我每周处理一次日志归档和临时文件清理过去要手敲好几步带 find 和 tar 的命令现在定义成任务tasks: weekly_clean: description: 清理并归档过期日志 steps: - find /var/log/app -name *.log -mtime 7 -exec gzip {} \; - tar czf /backup/logs_$(date %F).tar.gz /var/log/app - find /var/log/app -name *.gz -mtime 30 -delete schedule: 0 2 * * 1schedule字段是 OpenShell 的定时能力支持 cron 表达式。配置好后周一凌晨两点自动执行清理。这个功能对个人来说是省事对服务器运维来说是刚需。任务编排还支持步骤之间的依赖和条件判断。比如部署任务我常常先跑测试测试失败了就不继续构建。配置里可以用when判断上一步返回码也可以用only_on限定只在 main 分支执行。这些能力让 OpenShell 不只是命令的集合而是一个轻量级的自动化引擎。3. 从零搭建 OpenShell安装、初始化与第一个自动化脚本3.1 环境准备与安装OpenShell 的运行环境要求不高需要 bash 4.0 或 zshLinux 和 macOS 都支持Windows 下我建议用 Git Bash 或 WSL原生 CMD 就别考虑了路径和换行符都会让你崩溃。安装方式有两种一是用包管理器二是直接从 GitHub 拉取源码。我习惯用 Git 克隆后做软链接理由是这样升级方便也容易看到更新日志git clone --depth 1 https://github.com/yourname/OpenShell.git ~/.openshell cd ~/.openshell ./install.sh安装脚本主要做三件事检查系统依赖、创建配置文件目录~/.config/openshell/、在.bashrc或.zshrc里追加初始化行。装完后重开终端输入ops --version能看到版本号就说明基础环境没问题。装完第一时间建议做的事是把当前 shell 环境备份一次因为 OpenShell 初始化会往你的 rc 文件写入 source 语句万一以后想卸载备份能帮你快速还原。这一步不花一分钟但真到要回滚的时候非常救命。3.2 初始化配置看懂三份文件初始化完成后配置文件目录里会生成三份文件作用各不相同config.yaml全局配置定义默认 shell、日志级别、是否开启定时任务等。commands.yaml命令仓库的入口以目录为 key 组织命令块。tasks.yaml任务编排的定义文件。我的建议是刚开始不要急着把所有内容塞进同一份文件就按默认拆分开来。一是后续排查问题方便二是团队协同时每个人按职责改各自的文件冲突少。下面这份是我实际在用的全局配置片段settings: shell: zsh log_level: info auto_env: true task_schedule: true default_timeout: 300auto_env是环境切换总开关task_schedule控制定时任务是否启用default_timeout默认 300 秒防止某个步骤卡死把整个终端堵住。这几个字段我建议保持默认或按需小调不用一开始就追求极致精简。3.3 第一个自动化脚本日志清理没有实操讲再多也抽象。我带你过一遍我自己写的第一个 OpenShell 脚本就是把当前项目的旧日志压缩并清理。在commands.yaml里加一段commands: /path/to/myapp: clearlog: steps: - mkdir -p /tmp/myapp_logs - find /path/to/myapp/logs -name *.log -mtime 7 -exec gzip {} \; - tar czf /tmp/myapp_logs/logs_$(date %F).tar.gz /path/to/myapp/logs - find /path/to/myapp/logs -name *.gz -mtime 30 -delete保存后在任意终端执行ops clearlog看到命令一步步执行终端里同时打印输出和返回码执行完再用ls /tmp/myapp_logs验证 tar 包是否生成。整个过程五到十分钟就能跑通这算是 OpenShell 的最小可用闭环。有一点必须提醒脚本里出现的明文字段比如路径、数据库地址等建议只在个人项目里这么写。涉及团队共享或生产环境尽量用环境变量或加密变量引用避免把敏感信息写进配置文件再推到 git 里。3.4 把常用命令逐步纳入仓库很多人的误区是一上来想把所有命令都收纳进去结果配得又乱又难维护。我的做法是新命令随用随加老命令慢慢迁移。每当我发现自己第三次敲同一条命令就把它写进命令仓库如果命令开始带条件判断或串联多步再考虑升级成任务。收纳命令时我还会顺手写一行description写明这条命令是干什么的、依赖什么前置条件。理由很简单三周后你回头看自己的配置一定会忘了当初为什么这么写。好描述省下的调试时间远超写描述花掉的时间。4. 我在真实项目里踩过的坑和修复思路4.1 命令行脚本的权限陷阱最早我用 OpenShell 跑一个需要 root 权限的日志清理任务直接在 steps 里写了sudo rm -rf ...然后发现每次执行都要交互式输入密码定时任务直接卡死在密码输入。后来改成在任务的options里声明执行身份避免在单步命令里到处塞 sudotasks: cleanup: options: run_as: root steps: - find /var/log -name *.log -mtime 7 -delete这样执行时统一提权而且可以配合免密配置把权限问题彻底解决。不过这里要着重提醒sudo 权限和危险命令要格外谨慎。find配合-delete、rm -rf这类命令环境变量一旦拼错删掉的可能是完全不同的目录。我后来在所有带删除步骤的任务前面统一加了一行回显把将要操作的绝对路径打出来确认无误再继续。4.2 Windows 下的换行符与路径兼容有一次在 Windows 的 Git Bash 里跑 OpenShell 脚本明明在 Linux 上正常的命令到了 Windows 一直报错。排查到最后发现是 CRLF 换行符的问题——Windows 下 Git 自动把文件换行符从 LF 转成了 CRLFbash 解析时把\r当成了命令的一部分。解决方案不复杂在仓库根目录加.gitattributes文件*.sh text eollf *.yaml text eollf *.yml text eollf这样 Git 强制这些文件使用 LF至少在 Git Bash 里能保证脚本行为一致。如果你用 WSL建议再检查一下挂载盘的 metadata 属性否则 chmod 权限位时常会丢失。路径分隔符是另一个容易忽略的点。Windows 的C:\path\to\file和 bash 的/c/path/to/file写法不同OpenShell 里尽量用相对路径或由变量拼接绝对路径避免在任何一步里手写死 Windows 风格路径换到 Linux 就断。4.3 环境变量覆盖引发的灵异事件有段时间我发现某服务在项目 A 里启动时连的总是项目 B 的数据库查了很久才发现是环境变量覆盖规则没捋清。OpenShell 按全局配置 - 用户配置 - 项目配置的优先级合并环境变量项目配置优先级最高。我在全局配置里写了一个DATABASE_URL项目 B 的配置里也写了一个结果项目 A 没有定义这个变量于是继承到了全局值。这个行为本身合理但问题出在我以为没定义就不会有。后来我给所有项目都显式定义了DATABASE_URL没有用到数据库的项目就写空值避免任何隐式继承。团队协作时也约定所有敏感且容易冲突的配置必须显式声明不能靠忘记写来回避。这条规则避免了大量难以追踪的异常链接问题。4.4 别名吞噬原生命令OpenShell 的命令仓库支持自定义命令名这自然带来一个风险如果你给命令起的名字和系统原有命令重名比如把python定义成用项目虚拟环境跑测试那么某个脚本里只要调用了python都会被重定向到你的自定义命令轻则报错重则行为完全偏离预期。我的规避方法是自定义命令一律带前缀比如ops开头或p-开头保证不会和系统命令冲突。在命令仓库里加命令时先跑一下type name看看有没有已存在的命令这个检查只要一秒能省掉后面大把排查时间。5. 团队协作时的配置沉淀与规范化5.1 用 git 管理配置和脚本OpenShell 的配置本身是纯文本天生适合用 git 管理。我把整个~/.config/openshell/目录做成了一个 git 仓库push 到私有远程作为团队共享的标准配置底座。这样每个人拿到的是同一套命令和环境定义不存在我机器上能跑你机器上跑不了的玄学。首次拉下来之后写一个bootstrap.sh做初始化把源码软链接、配置文件同步、依赖检查全部自动化。新人入职从零到终端可用基本只要跑一句话。这个脚本写完放到 git 仓库里后续有环境变更也只需要在这个脚本里追加。5.2 锁版本、可回滚配置是纯文本意味着 git 能帮你保留所有历史版本。我强烈建议团队给配置文件加版本标签比如v1.0.0对应旧项目的全套环境v1.1.0加上了新项目的命令定义。一旦升级后发现有问题直接 checkout 回旧标签再重载配置即可整个过程几秒钟。这里还有个更细的做法把命令分文件管理而不是全部塞进一个超大的commands.yaml。比如按用途划分git.yaml、deploy.yaml、database.yaml再用 include 指令组合。分工明确后多人同时改配置的合并冲突会大幅减少因为大家改的不是同一段文本。5.3 新人引导脚本我踩过最大的坑之一是以为给新人一份配置说明文档就够了。实际上大多数新人不会仔细读文档他们在终端前最需要的是一句跑这个命令环境就好了。所以我写了一个ops doctor任务专门检查本机各个依赖是否就位、路径是否存在、版本是否匹配tasks: doctor: description: 检查本机开发环境完整性 steps: - python --version - node --version - pnpm --version - test -d .venv echo venv ok || echo venv missing新人拿到项目后先跑ops doctor缺什么一目了然。实际效果远远好于让新人自己 reading docs 去猜环境有什么问题。配合命令仓库里预置的setup命令一条命令就能装好依赖并初始化环境新人的挫败感会明显降低。5.4 我个人习惯里值得推荐的两个小扩展最后分享两个我一直在用的扩展思路。第一个是用 OpenShell 的定时任务做个人提醒和日志汇总。比如每天早晨九点自动把昨天的部署日志和错误数汇总成摘要输出到终端这不是什么高级功能但大大减少了我每天早上手动翻日志的次数。第二个是把项目启动做成一个标准化的ops up命令所有项目都约定这个命令名。不同项目内部可以调用不同的进程管理器、不同的依赖安装方式但对外接口统一。团队里的人切换项目时不需要重新学习每个项目的启动方式这个统一接口带来的协作收益在多人维护多个服务时非常明显。配置维护这件事从来不是写一次永久有效它和代码一样需要持续迭代。OpenShell 对我来说最大的价值不是省了那几秒钟敲命令的时间而是把所有终端操作从靠记忆变成了靠配置。记忆会模糊配置文件不会。定期打开配置文件看一眼顺手清理掉那些已经用不上的旧命令让命令仓库保持瘦而准的状态这套工具的长期价值才会真正显现。

相关新闻

C#高性能Socket封装:同步异步混合架构与粘包处理

C#高性能Socket封装:同步异步混合架构与粘包处理

1. 项目概述:为什么一个Socket封装类值得花三天重写三次?“C# Socket通讯封装类和DEMO”——这行字看起来平平无奇,像极了某次加班到凌晨两点时,在Git提交记录里随手敲下的commit message。但如果你真在工业现场调试过PLC数据采集…

2026/10/15 11:32:12 阅读更多 →
机械设计课程设计说明书:从传动方案到强度校核的完整设计链路

机械设计课程设计说明书:从传动方案到强度校核的完整设计链路

简介:这份机械设计课程设计说明书面向机械类专业的在校学生与课程设计指导教师,围绕单级蜗轮蜗杆减速器这一典型课题,提供从任务书到图纸的完整设计范例。资源包内含1个doc文档,压缩包约53KB,内容涵盖参数选择、传动装…

2026/10/15 11:32:12 阅读更多 →
ADINA中文手册高效阅读与避坑指南:从建模到非线性分析实战

ADINA中文手册高效阅读与避坑指南:从建模到非线性分析实战

简介:《ADINA中文手册》是一份面向ADINA初学者、在校生及结构/流体分析工程师的中文入门教程,系统梳理非线性有限元分析的核心流程与关键设置。压缩包共3个文件,以PDF主教程为主体,辅以TXT电子书说明与HTML更多学习资源链接&#…

2026/10/15 11:32:12 阅读更多 →

最新新闻

第一次做 GEO 要几步?从注册到第一篇内容被收录的启动路径

第一次做 GEO 要几步?从注册到第一篇内容被收录的启动路径

结论先放这儿:6 步。注册 → 搭知识库 → 生成用户问题 → 攒标题 → 补正文 → 跑监测。前 4 步搭台子,第 5 步出第一篇成稿,第 6 步验证它有没有被 AI 读到。先说这篇是写给谁的 如果你刚注册完 GEOchat,打开后台看到「知识库、…

2026/10/15 13:45:15 阅读更多 →
SQLite FTS5 实现上下文感知查询:MCP协议与BM25动态加权实战

SQLite FTS5 实现上下文感知查询:MCP协议与BM25动态加权实战

1. 项目概述:什么是 context-mode?它不是个独立工具,而是一套数据驱动的上下文感知工作流设计范式“context-mode”这个词最近在开发者社区里频繁出现,但它本身并不是一个可下载安装的软件、也不是某个开源项目的官方名称&#xf…

2026/10/15 13:45:15 阅读更多 →
SQLite上下文感知搜索:基于MCP与FTS5的本地语义检索实践

SQLite上下文感知搜索:基于MCP与FTS5的本地语义检索实践

1. 项目概述:从“context-mode”这个词开始,我们到底在聊什么?“context-mode”这个词乍一看像某个IDE的隐藏开关,或是某款新出编辑器的实验性功能,但结合它旁边高频出现的关键词——MCP、SQLite、FTS5、BM25——你就该…

2026/10/15 13:45:15 阅读更多 →
SpringBoot+Vue构建城市垃圾分类管理系统实践

SpringBoot+Vue构建城市垃圾分类管理系统实践

1. 项目概述:城市垃圾分类管理系统的现实意义与技术选型 垃圾分类作为现代城市治理的重要环节,其信息化管理需求日益凸显。这套基于SpringBootVue的技术方案,正是针对社区、物业等场景设计的轻量级管理平台。我在实际参与某新一线城市智慧社区…

2026/10/15 13:45:15 阅读更多 →
第三范式:从数据库理论到信息治理的操作系统

第三范式:从数据库理论到信息治理的操作系统

1. 项目概述:什么是“第三范式”?它真不是数据库课上的老古董“第三范式”这个词最近在社交平台和职场交流中高频出现,但绝大多数人听到的第一反应是——“啊?数据库设计里的3NF?那个我大二挂过科的概念?”…

2026/10/15 13:45:15 阅读更多 →
sklearn决策树可视化:零依赖SVG生成方案

sklearn决策树可视化:零依赖SVG生成方案

1. 这个标题背后的真实困境:为什么有人想“绕过”graph.render?在用scikit-learn做机器学习项目时,绝大多数人根本不会接触到graph.render这个调用——因为它压根就不属于 sklearn 的 API。这是个典型的“跨库误用”现象,也是标题…

2026/10/15 13:44:14 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →