news 2026/9/17 3:55:51

用纯Bash实现单文件配环境Agent:自动检测、安装与验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用纯Bash实现单文件配环境Agent:自动检测、安装与验证

我上周刚拿到一台新开发机,装完系统以后光把 Node、Java、Go、Docker 这些轮子配齐,来回切窗口、找安装包、改 PATH、翻报错,就折腾了大半个下午。这不是第一次了。所以第三次重复做这件事的时候,我实在没忍住,把整套路子抽成了一个脚本:d.sh,一个用纯 bash 写成的、把整个 Coding Agent 体系塞进同一个文件里的配环境 Agent。

它解决的事情非常具体:在一台新机器上,自动完成“系统探测 → 工具检测 → 缺失安装 → 环境变量配置 → 二次验证”这条链路的全部动作。你只需要一行 curl 拿到脚本,再执行./d.sh auto,剩下的活儿它自己安排。

这篇文章写给三拨人:被配环境反复折磨的开发者、想用 bash 做点正经自动化的运维/后端、以及对“最小 Agent 实现”感到好奇的人。我会把设计思路、完整模块拆解、真实踩坑,以及最后的使用效果都摊开讲。如果你只是想要一个能直接抄的文件,我也把关键代码贴在对应小节里,按顺序拼起来就是一个能跑的 d.sh。

1. 为什么非要用 bash 做一个配环境 Agent

1.1 配环境不是“装软件”,而是一棵决策树

很多人觉得配环境就是“下一步下一步装软件”,其实不是。它本质是一个条件判断特别多的决策过程:

  1. 先判断这台机器是什么系统,Linux、macOS 还是 Windows 下的 Git Bash;
  2. 判断系统里装了哪些包管理器,apt、dnf、brew 还是 winget;
  3. 逐个检查目标工具在不在,在的话版本够不够;
  4. 不在就安装,装完还要配置 PATH、JAVA_HOME 之类的环境变量;
  5. 最后重新检测一遍,确认刚才的动作真的有效。

这五步正好对应一个 Agent 的经典循环:感知(探测环境)、决策(判断缺什么)、行动(安装配置)、验证(二次检测)。所以从思路上说,把配环境脚本做成一个 Agent 不是夸张,而是这类任务天然适合。

问题是大多数人配环境的时候是“人肉 Agent”:眼睛看报错,大脑查 Google,手上敲命令。一次两次还好,换到第三台、第四台机器,或者帮团队新人配环境的时候,这种重复劳动就很浪费了。

1.2 为什么是 bash,而不是 Python、Go 或 Ansible

我在最初设计方案时不是没想过用“更正经”的语言,但逐个排除后还是回到了 bash。理由其实很朴素:

  • Python 有“先有鸡还是先有蛋”的尴尬:配环境的场景里,新机器有可能连 Python 3 都没有,或者系统自带的 Python 版本乱七八糟。你为了配环境先去配 Python 环境,等于问题没解决还多了一层依赖。bash 则不一样,Unix 系系统自带,Git Bash 装一个就能在 Windows 上用,几乎零前置条件。
  • Go 编译出来的二进制确实方便分发,但改一次逻辑就要重新编译一次,对快速迭代不友好。而且配环境这个场景大部分操作就是调用系统命令,用 Go 去包一层反而增加体积和复杂度。
  • Ansible 适合批量管理几十台机器,但在这台开发机上只是自用的话,引入 Python 环境和 playbook 目录结构太重了。一个文件能解决的问题,搞出一堆 yaml 文件反而难维护。

bash 最大的优点是:系统自带、语法简单、和命令行的距离最近。它的缺点——字符串处理别扭、没有原生数据结构、容易写出不可维护的“屎山”——完全可以通过结构化函数设计和命名约定来规避。说到底,配环境这个任务的复杂度是有限的,用一个轻量工具去做恰好合适。

1.3 d.sh 的“Agent 循环”:感知、决策、行动、验证

真正让我觉得“这玩意儿配得上 Agent 这个名字”的,是它的主循环设计。d.sh 的内部不是一坨顺序执行的命令,而是把一个状态机塞了进去:

当前状态 = 初始 循环: 感知:收集系统信息、工具状态 决策:比对目标清单,找出缺失项 行动:执行安装或配置动作 验证:重新收集状态,确认是否收敛 若还有缺失项,继续循环;否则退出

因为没有真正的 AI 模型在跑,这个 Agent 本质上是一个确定性有限状态机。它不会遇到问题就“想”出计划外的方案,但它能把整个配环境流程自动化地收敛到一个可预期的结果。对配环境这个领域来说,这已经够用了。

我把这个循环拆成了五个子命令,让每种使用需求都有对应的入口。下一部分细讲。

2. d.sh 的功能边界与命令设计

2.1 命令设计:check、install、doctor、auto、list

d.sh 采用类 git 的子命令风格,一共五个命令,每个命令对应一种完整的使用场景:

命令行为典型场景
./d.sh list列出当前支持的全部工具清单刚拿到脚本,先看看它能管啥
./d.sh check只读检测,输出每项工具的状态和版本检查一台机器还缺什么
./d.sh install <tool>安装或配置指定工具只想补某一个缺失项
./d.sh doctor <tool>对一个工具做深度诊断工具存在但用不了,查原因
./d.sh auto自动完成检测、安装、验证的完整闭环新机器一键配环境

设计原则非常明确:默认只读,写入必须有显式意图。也就是说,check永远只打印信息,绝不改动系统;只有输入installauto才会真正调包管理器。这个约定很重要,既能防止手滑执行了不该执行的命令,也让新人敢在陌生机器上先跑一遍check看状态。

doctor是我后来补的一个命令。因为实际使用中发现,很多“配不上环境”的坑不是工具没装,而是装了但 PATH 不对、版本冲突、符号链接断了。doctor会打印工具的安装路径、软链情况、版本号、以及 rc 文件里相关的配置,一次给足排查信息。

2.2 工具清单的登记方式:约定优于配置

bash 没有像样的数据结构,所以我没有搞一个复杂的配置表,而是用了一套命名约定来组织工具信息。每个工具在d.sh里对应三样东西:

  1. 一个数组元素,表示工具名;
  2. 一个tool_check_xxx()函数,负责检测;
  3. 一个tool_install_xxx()函数,负责安装配置。

主程序在 dispatch 的时候,直接用工具名拼函数名动态调用。比如要检测 node,就执行tool_check_node,检测逻辑在函数里自己写。

# 工具注册表 TOOLS=(git curl wget node python3 go java docker) # 按名字执行检测函数 dispatch_check() { local tool="$1" # 通过函数名拼接实现“多态” if declare -F "tool_check_${tool}" >/dev/null 2>&1; then "tool_check_${tool}" else log WARN "工具 ${tool} 还没有对应的检测函数" fi }

我特意没有用 bash 4 的关联数组(declare -A),因为 macOS 自带的 bash 还是 3.2,关联数组会直接报错。用“数组 + 命名约定”这种方式,虽然老套,但兼容性极好,Git Bash、macOS、Linux 全都能跑。这也是单文件脚本必须考虑的妥协。

新增一个工具的流程被压缩到两步:在TOOLS数组里加个名字,然后写两个函数。听起来简单,实际用下来也确实简单。我自己从最开始的 5 个工具扩到现在 12 个,没有因为新增工具而改过主逻辑。

2.3 输出与日志:终端友好、机器可读、事后可查

脚本的输出质量和日志能力决定了工具“好不好用”。d.sh 有三层输出:

第一层是终端彩色输出。INFO 用绿色,WARN 用黄色,ERROR 用红色,一眼扫过去就能看出当前卡在哪。但颜色输出有个坑:当 stdout 被重定向到文件或管道时,转义序列会变成乱码。所以我在日志函数里做了一次判断:

log() { local level="$1"; shift local ts ts="$(date '+%Y-%m-%d %H:%M:%S')" if [[ -t 1 ]]; then # stdout 是终端时才输出色彩 case "$level" in INFO) printf '\033[0;32m[INFO]\033[0m %s %s\n' "$ts" "$*" ;; WARN) printf '\033[0;33m[WARN]\033[0m %s %s\n' "$ts" "$*" ;; ERROR) printf '\033[0;31m[ERROR]\033[0m %s %s\n' "$ts" "$*" ;; esac else printf '[%s] %s %s\n' "$level" "$ts" "$*" fi printf '[%s] %s %s\n' "$level" "$ts" "$*" >> "$LOG_FILE" }

[[ -t 1 ]]的作用是判断文件描述符 1 是否连接到终端。在终端跑就显示颜色,接管道就有干净的纯文本。同时每一行都会附加时间戳追加到日志文件里,日志文件路径放在${TMPDIR:-/tmp}下,按时间戳命名,方便事后排查。

这套输出设计让我在使用时省了不少心。./d.sh check的输出可以直接grepcut处理,出问题又能去日志文件里翻完整时间线。

3. 核心模块实现:从探测到安装的完整链路

3.1 跨平台系统探测与包管理器分发

配环境 Agent 的第一步是搞清楚自己在哪。uname -s是跨平台探测的基础,我用一个 case 分支把系统归成三类:

detect_os() { case "$(uname -s)" in Linux*) echo "linux" ;; Darwin*) echo "macos" ;; MINGW*|MSYS*|CYGWIN*) echo "windows" ;; *) echo "unknown" ;; esac }

Windows 这里需要特别说明:Git Bash 底层是 MSYS2 或 Cygwin 模拟层,所以uname -s会返回MINGW64_NT之类的字符串。把它识别成windows后,后面很多系统调用行为就和 Linux 不一样了,比如安装命令要走 Windows 的包管理器,而不是 apt。

识别完系统,紧接着是识别包管理器。这一步直接决定后续所有安装命令怎么发:

detect_pm() { case "$OS" in linux) if command -v apt-get >/dev/null 2>&1; then echo "apt" elif command -v dnf >/dev/null 2>&1; then echo "dnf" elif command -v yum >/dev/null 2>&1; then echo "yum" elif command -v pacman >/dev/null 2>&1; then echo "pacman" else echo "unknown"; fi ;; macos) if command -v brew >/dev/null 2>&1; then echo "brew" else echo "missing-brew"; fi ;; windows) if command -v winget >/dev/null 2>&1; then echo "winget" else echo "unknown"; fi ;; esac }

有了OSPM两个变量,后面写安装函数就简单了。每个工具的安装函数内部做一个包管理器分发,比如install_node里就是:

  • apt 系执行sudo apt-get install -y nodejs npm
  • brew 执行brew install node
  • winget 执行winget install OpenJS.NodeJS

这个分发逻辑看起来琐碎,但它是全脚本机制的核心。没有它,同样的功能就得写三套完整的安装路径,脚本体积会膨胀一倍。

3.2 检测函数:command -v与版本解析

检测一个工具是否安装,最可靠的不是which,而是command -vwhich在很多系统上不是标准命令,不同平台的实现行为不一致,在某些 shell 环境里还会拖慢启动。command -v是 POSIX 标准自带的,行为统一,判定结果干净。

tool_check_node() { if command -v node >/dev/null 2>&1; then local ver ver="$(node --version 2>/dev/null | sed 's/^v//')" log INFO "node 已安装,版本 ${ver}" echo "${STATUS_FOUND}" else log WARN "node 未安装" echo "${STATUS_MISSING}" fi }

版本解析这里有个容易被忽略的点:像node --version输出的是v20.11.0,前面带个v。直接拿去比较版本号会出问题。我的做法是用sed 's/^v//'把前缀剥掉,然后交给统一的版本比较函数。

比较版本高低时又有个兼容性坑。GNU 的sort -V能直接比较版本号,但 macOS 自带的 BSD sort 不支持-V参数。所以我在 d.sh 里写了个简单的版本字符串比较函数,用按点拆分的数字逐个比,彻底摆脱对sort -V的依赖。这个细节如果不处理,在 macOS 上脚本会直接报invalid option -- V

3.3 安装与配置分离的实践

我在设计工具函数时,刻意把“安装”和“配置”拆成了两个逻辑阶段。以 Java 为例,apt-get install openjdk-17-jdk装完以后,还需要设置JAVA_HOME环境变量,否则很多构建工具找不到 JDK。安装动作和配置动作的触发条件不同:

  • 安装只发生在“命令不存在”时;
  • 配置则每次都会检查 rc 文件里有没有对应条目,没有才补充。

配置函数的实现关键在幂等性。往~/.bashrc~/.zshrc里追加内容之前,一定要先判断内容是否已经存在,否则每次跑auto都会重复插入一行,越插越多。

ensure_rc_line() { local rc_file="$1" local line="$2" if [[ -f "$rc_file" ]] && grep -qF -- "$line" "$rc_file" 2>/dev/null; then log INFO "${rc_file} 已包含配置,跳过" return 0 fi printf '\n# added by d.sh\n%s\n' "$line" >> "$rc_file" log INFO "已写入 ${rc_file}: ${line}" }

这条grep -qF判断是整个配置模块的护城河。-F把匹配模式当作固定字符串,避免JAVA_HOME里的$符号被当成正则处理,从而误判或漏判。我自己在这个地方犯过两次错,一次没加-F导致包含特殊字符的行永远匹配不上,一次忘了判断直接追加导致 rc 文件被刷屏。

3.4 环境变量持久化的处理

环境变量持久化是配环境里最容易被忽视的一环。很多人装完 Java 发现java能跑,但JAVA_HOME没生效,这就是配置阶段没做好。

d.sh 的原则是:优先写用户级 rc 文件,不碰系统级文件。具体做法是先探测当前用户用的是 bash 还是 zsh,然后往对应的 rc 文件里写。Git Bash 环境下还要额外处理 Windows 风格的路径转换,比如C:\Program Files\Java\jdk-17要转成/c/Program Files/Java/jdk-17才能被 bash 识别。

写完之后脚本会明确提示用户执行source ~/.bashrc或重开终端。这个提示不能省,因为一个会话里已经加载的环境变量不会因为 rc 文件改变而自动刷新,很多新手在这一步会以为配置失败。

3.5 auto 全流程的组装

auto命令是 d.sh 的最终形态,它把前面所有模块串起来。执行流程如下:

  1. 打印本次要处理的目标工具清单;
  2. 对所有工具做一轮check,收集缺失项;
  3. 如果缺失项为空,直接输出“环境已就绪”并退出;
  4. 逐个调用缺失工具的安装函数;
  5. 全部装完后,再做一轮check,对比前后状态;
  6. 输出最终摘要:哪些工具搞定,哪些工具仍然失败。

第一次跑auto时最怕中途某个工具安装失败导致后面全乱。所以 d.sh 在循环里对每个安装函数单独捕获返回值,失败就记入“失败列表”,继续装下一个。全部跑完后统一报告,而不是一遇到错误就退出。这个设计让脚本在一台缺很多东西的机器上也能尽量收敛——装不上的可以在最后看清单手动处理。

4. 换行符、返回值与幂等性:四个真实踩坑记录

4.1 /bin/bash^M: bad interpreter——Git Bash 里最常见的换行符事故

d.sh 在团队里流传开后,出现频率最高的报错是/bin/bash^M: bad interpreter: No such file or directory。这个^M不是脚本内容问题,而是文件里的换行符是 CRLF,不是 Unix 标准的 LF。bash 在执行脚本时,把行尾的\r当成了解释器路径的一部分,自然找不到。

原因几乎都出在 Windows 上。用 Visual Studio Code 或记事本编辑过脚本后保存,文件被写成了 CRLF;或者 Git 在 clone 仓库时因为core.autocrlf配置把 LF 自动转成了 CRLF。解决方法不复杂,把文件转回 LF 就行:

# 命令行直接清掉行尾的 \r sed -i 's/\r$//' d.sh

但“能修”和“不再踩”是两码事。我的建议是双管齐下:在项目根目录放一个.gitattributes文件,强制 shell 脚本以 LF 存储;同时在编辑器里把默认换行符设成 LF。如果你只是下载了 d.sh 单文件,没有仓库,那就记得下载后用sed -i 's/\r$//' d.sh处理一遍再运行。

4.2 Git Bash 的 crontab“失踪”与 PATH 的差异

另一个频繁被误报的场景是-bash: crontab: command not found。很多人以为 Git Bash 是“Windows 里的 Linux”,所以 Linux 上有的命令它都应该有。事实是 Git Bash 只提供了常用的 GNU 工具集,crontab、systemctl、service这类系统级命令是不存在的。

这对 d.sh 的设计有一个直接影响:在工具检测清单里,它把 cron 相关的检查标记为“可选项”,而不是“必选项”。如果检测到crontab不存在,只输出 WARN 而不是 ERROR。因为对 Windows 用户来说,正确的做法是去任务计划程序里配定时任务,而不是纠结 Git Bash 里为什么没有 crontab。

这个案例给我最大的教训是:跨平台脚本里对“命令是否存在”的判断,必须结合平台语义。同样的命令在 Linux 上是基础设施,在 Git Bash 里可能压根不属于它该管的事。

4.3 set -e 与command -v的返回值冲突

bash 脚本最佳实践通常建议开启set -e,让任何命令失败时立即退出。但做环境检测时,set -ecommand -v之间存在一个隐蔽的冲突:当工具不存在时,command -v会返回非零状态,在set -e模式下直接让脚本退出。

我第一次写检测函数就栽在这上面。tool_check_node里只要 node 没装,command -v node返回 1,整个脚本就停了,后面的安装逻辑根本没机会执行。

解决办法有两种。一种是像前面代码里那样,把command -v放进if的条件里,bash 对if条件中的命令不启用set -e

if command -v node >/dev/null 2>&1; then # 已安装分支 else # 未安装分支 fi

另一种是在特定位置临时关闭set -e,用完后恢复。我实际写 d.sh 时两种都用了,但主力方案是第一种,因为更安全,不会出现“忘了恢复”导致后续一整段命令都不检查退出状态的情况。

4.4 颜色输出在管道与日志文件中的兼容处理

前面提过[[ -t 1 ]]这个判断。它解决的实际问题是:当./d.sh check | grep node这种管道执行时,如果脚本输出里带了 ANSI 颜色转义序列,grep匹配到的内容会包含一堆\033[0;32m之类的垃圾字符,肉眼几乎没法看。

更好的处理是兼容NO_COLOR环境变量。有些终端环境或 CI 系统默认设置NO_COLOR=1来关闭所有颜色输出,脚本如果不认这个约定,在 CI 日志里就会刷屏转义字符。所以我在颜色判断里加入了NO_COLOR检查:

if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then # 启用颜色 else # 纯文本输出 fi

4.5 重跑脚本的幂等性设计

配环境脚本一定会被反复执行:第一次跑到一半停了,第二次补跑;或者这次新增了几个工具,在已有环境上再跑。如果脚本不具备幂等性,第二次跑就会制造一堆重复配置。

d.sh 的幂等性主要靠两个机制保证。

第一是“安装前先检查状态”。auto在执行安装函数之前,会先确认目标工具确实缺失。已经存在的工具直接跳过,不会重新下载重新安装。

第二是“配置写入前先查重”。ensure_rc_line函数用grep -qF判断目标行是否已经在文件里,存在就跳过。这样无论跑多少遍,rc 文件里跟 d.sh 相关的内容永远是那几行,不会指数级膨胀。

还有一个方向是并发保护。虽然正常不会同时跑两个 d.sh,但为了防止极端情况下两个进程同时写 rc 文件,我加了个简单的锁文件机制:执行auto前先创建一个临时目录作为锁,结束或异常退出时清理。这个锁不复杂,属于“花了 5 分钟写但可能省不少麻烦”的投资。

5. 实测效果:从新机器到可开发状态的用时对比

5.1 一次新机器配置的实测时间线

上个月我在一台全新的 Ubuntu 24.04 开发机上跑了完整流程,记录如下:

步骤手动操作(估算)d.sh 操作
系统探测与包管理器确认5 分钟(还要查命令)1 秒
检查 12 个工具缺少哪些10 分钟(挨个敲命令)3 秒
安装缺失工具25 分钟(找包名、装错重来)10 分钟(自动安装)
配置环境变量10 分钟(不同工具不同写法)5 秒
二次验证5 分钟3 秒

从 55 分钟压到 15 分钟左右,关键是过程中不需要我盯着输出。真正省下来的是“思考下一步要做什么”的时间:脚本自己知道下一步该测什么、装什么、配什么。对经常换机或者带新人的场景,这个收益非常可观。

那次实际跑的时候有两个工具安装失败。一个是 Docker,因为新版 Docker Desktop 在 Ubuntu 上的安装方式改了包名;另一个是 Go 的版本冲突。d.sh 把这两个标成失败,打印出安装链接,我手动处理掉以后重新跑了一遍./d.sh check,状态就全部绿了。这个“部分失败但不中断”的设计在真实场景里尤其重要。如果脚本一遇错就退出,前面装好的东西也会让人不安,后面复跑时还得重新检测。

5.2 扩展建议:CI 配置检查器与团队内部分享

d.sh 的使用场景不止新机器。我后来发现,把它丢进 CI 里当“环境检查器”也很顺手。GitLab CI 或 GitHub Actions 的任务里加一步./d.sh check,如果输出里有STATUS_MISSING,就让 job 失败。这样任何一次构建跑起来之前,都能先确认构建机上的工具链是完整的,避免跑到一半才发现缺依赖。

团队场景下,d.sh 的价值还体现在“统一配置口径”上。新人入职配环境这件事,传统做法是拉一个文档让新人自己跟着操作,文档版本一多就乱。用 d.sh 之后,所有工具的版本、安装方式和配置项都收敛在一个文件里,新人只需要克隆脚本、跑auto,出现解决不了的问题再找人对症下药。排障效率高很多,因为脚本的输出本身就是线索。

当然,d.sh 的适用边界很明显:它面向开发机的单机场景。如果你要管几十台服务器的环境一致性,Ansible 或 Nix 是更合适的选择。工具选型不是越重越好,而是匹配问题规模。d.sh 适合的是“一台一台逐台配”的人和团队。

5.3 什么时候该停手:d.sh 的边界与我的取舍

写这个脚本的过程里,我无数次想把它做得更“聪明”:加上工具间的依赖关系、写一个交互式菜单、做一个内嵌的版本更新逻辑。最后都忍住了。原因是,每加一个复杂特性,脚本的体积和心智负担都会上一个台阶,而配环境这个场景的收益却不大。

我的取舍标准很简单:如果一件事用三五行 bash 能描述清楚,就留在 d.sh 里;如果它开始需要一个配置文件、一个状态数据库、或者一个图形界面,那它已经超出单文件 bash 脚本的合适边界了。目前 d.sh 稳定在 900 行左右,覆盖 12 个常用工具,新开发机上 80% 的环境配置需求都能覆盖,剩下 20% 的手工处理场景,脚本会把失败项和参考链接明明白白列出来,让开发者知道下一步该干嘛。

这就是我眼里一个“单文件 Coding Agent”该有的样子:有感知,有决策,有行动,有验证,但始终知道自己边界在哪。工具再小,能稳定解决问题的部分就是一个合格的 Agent。d.sh 不一定能帮你配完所有环境,但至少能让你少折腾大半个下午。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 3:54:59

Cucumber自动化测试实战:从BDD到Gherkin的完整指南

我第一次用 Cucumber&#xff0c;是在一个购物网站自动化改造项目里。当时团队已经维护了一套 Selenium 脚本&#xff0c;用例数量不少&#xff0c;但产品经理和项目负责人每次验收都要另开一场会&#xff0c;逐条解释“这个脚本到底验证了什么”。直到我们把用例全部改成 Gher…

作者头像 李华
网站建设 2026/9/17 3:54:54

SQL中count(1)、count(*)与count(列名)的区别及性能优化

年初我帮团队复盘一个慢SQL问题&#xff0c;优化完发现执行计划里count(1)被优化器和count(*)处理成了完全一样的东西。但到了count(列名)&#xff0c;情况突然不一样了。群里当时吵了一轮&#xff1a;有人说 count(1) 比 count(*) 快&#xff0c;有人说 count(列名) 最快&…

作者头像 李华
网站建设 2026/9/17 3:52:10

AI测试工具兴起,2027年非AI驱动工具淘汰,测试工程师如何转型

最近测试圈子里传得最凶的一件事&#xff0c;就是微软内部文件提到2027年要淘汰所有非AI驱动的测试工具。很多朋友跑来问我&#xff0c;说这是不是意味着我们这帮写脚本、点页面的测试工程师要集体失业了。我的看法比较直接&#xff1a;这份文件更像是一个行业风向标&#xff0…

作者头像 李华
网站建设 2026/9/17 3:51:58

把 CC-Switch 的上游 Key 换成 TaoToken 后,WSL2 里也能跑通 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/9/17 3:51:08

Linux卡在emergency mode?fstab的UUID错配是主因

周六早上我远程连家里那台Linux NAS&#xff0c;结果怎么都ping不通。过了一会儿家人拍来一张照片&#xff0c;屏幕停在黑底白字的启动界面&#xff0c;上面明晃晃一行字&#xff1a;“Welcome to emergency mode!”看到这行字我反而松了口气&#xff0c;因为这类故障我处理过太…

作者头像 李华