news 2026/10/13 5:45:13

Bash select命令实战:告别read+case,轻松写出交互式菜单

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Bash select命令实战:告别read+case,轻松写出交互式菜单

交互式菜单是每个写Shell脚本的人迟早要面对的需求。早期我写菜单都是靠read配合case硬拼,一个简单的三选项菜单能写出三十行判断逻辑,后面再想加个选项,改起来跟拆地雷一样。直到我真正用上Bash内置的select命令,才意识到原来菜单在Shell里可以是个"开箱即用"的东西——三五行代码就能出效果,界面还自带数字编号和输入提示。

这篇内容就围绕select命令展开,会用实际案例说明它怎么用、有哪些坑、适用边界在哪。适合刚开始写自动化脚本、想在脚本里加交互选择的人,也适合那些已经用read+case写了不少菜单、想重构的开发者。

1. 为什么需要select:手写菜单的痛点与select的破局

1.1 从手动case菜单的"拆地雷"体验说起

先还原一下很多人熟悉的场景。你要写一个部署脚本,让用户在"备份数据、更新代码、重启服务、退出"里选一个。不用select,最常规的写法是:

#!/bin/bash echo "选择要执行的操作:" echo "1) 备份数据" echo "2) 更新代码" echo "3) 重启服务" echo "4) 退出" read -p "请输入数字 [1-4]: " choice case "$choice" in 1) backup_data ;; 2) update_code ;; 3) restart_service ;; 4) exit 0 ;; *) echo "输入无效,请重新运行脚本。" ;; esac

这个方案在小规模场景下完全没问题。但当你提供7个、8个选项时,问题就来了:菜单打印部分要维护两份(echo列表一份,case分支一份),两者一旦不同步,用户看到的数字和实际执行的逻辑就对不上。增加选项时需要记住修改所有位置,漏一处就是bug。

还有一个更隐蔽的体验问题——read只接受一次输入,用户输错直接退出,没有"重新输入"的机会。如果你想实现"输入无效就重来",就得在外面套while循环,再把打印、读取、校验全部装进去。菜单越改越臃肿,最后你根本分不清这是一段交互脚本还是一团散沙。

1.2 select到底是什么:一个内置的菜单生成器

select是Bash自带的内置命令,它的定位非常明确:自动生成编号菜单并读取用户输入。语法长这样:

select 变量名 in 选项列表; do 处理逻辑 done

你给它一个选项列表,select会帮你做四件事:

  1. 把选项列表按编号打印出来(1、2、3……);
  2. 用PS3变量作为输入提示符;
  3. 读取用户输入,存进REPLY变量;
  4. 把用户选中的选项内容赋值给变量名,然后进入循环体执行。

按Ctrl+C或输入exit之外,真正的退出机制需要你自己在循环体里用break实现。因为select本身就是个循环结构,它会一直循环等待输入,直到循环体里执行break。

1.3 select与手写方案的直观对比

对比维度read + case 手动方案select 内置方案
菜单打印代码量需逐个echo自动完成
增加选项的维护成本打印列表+case分支都要改只需改选项数组
输入错误重试需额外写while循环内置循环处理
输入校验代码需自行判断范围配合case处理
输出格式无编号或无提示自动编号+清晰提示

对新手来说,select最大的价值是把"菜单打印"和"循环等待输入"这两件基础设施直接给做了。你只需要关心一个事情:用户选中某项之后,你要执行什么逻辑。

2. 把select的核心机制吃透

2.1 PS3提示符:决定交互体验的关键变量

select默认的输入提示是?#,难看也不够明确。稍微像样一点的脚本都会重新定义PS3,比如:

PS3="请选择操作:" select opt in 备份 更新 重启; do ... done

这里的关键在于PS3在select循环中会作为输入提示输出。它可以是纯文本,也可以包含变量、反引号、颜色转义码。实战里我喜欢带上当前用户或当前目录信息,让提示更具指向性:

PS3="[$(whoami)@$(hostname)] 请选择操作:"

不过提醒一句:PS3里如果用了\n转义,在部分Bash版本下可能不生效。想要把提示换行,用$'\n'语法,而不是直接写"\n"。

提示:改PS3后如果没看到效果,先确认是不是在select之前赋的值。在select内部动态改PS3也可以,但下一次迭代才会生效。

2.2 选项列表、REPLY与选择值的三角关系

很多刚接触select的人会搞混一件事:select语句中的变量、REPLY、还有选项数组到底谁是谁。看这段代码:

options=("显示日期" "显示时间" "退出") PS3="请选择:" select opt in "${options[@]}"; do echo "你输入的原始内容是: $REPLY" echo "你选中的选项值为: $opt" done

这里有个很容易踩的坑:select返回给opt的值不是数字,而是对应选项的字符串内容。如果你选择输入"2",opt的值是显示时间,不是2。数字2在REPLY里。

所以后续做逻辑分支时,你要搞清楚到底匹配哪个:

case "$REPLY" in 1) echo "执行显示日期";; 2) echo "执行显示时间";; 3) break;; esac

按REPLY做分支,还是按$opt做分支?两种方式都可行,看场景:

  • 按REPLY:适合选项少、编号固定、和外部约定好的场景;
  • 按$opt:适合选项顺序允许调整、以内容语义为准的场景。

我个人的偏好是:脚本面向自己用的,按REPLY数字分支更简洁;脚本面向别人用的,按$opt字符串分支更可读,就算菜单顺序变了,逻辑也不会错乱。

2.3 选项数组与空输入的特殊行为

select的选项列表建议用数组展开的方式传入。直接写也可以:

select opt in 启动 停止 重启; do ... done

但如果选项里有空格,或者你想动态变化,数组更好用。比如:

envs=(开发 测试 生产) select env in "${envs[@]}"; do echo "选中的环境: $env" done

还有一个隐藏特性:当用户输入为空(直接按回车)时,select不会把空值传给你的逻辑,它会重新打印菜单,然后继续等待输入。这个机制天然解决了"空输入直接退出"的体验问题,你不用额外写校验。

但注意:如果用户输入了菜单编号以外的数字,比如菜单只有3项,用户输入了99,那么$opt是空的,REPLY是99。你必须在循环体里处理这种情况,否则脚本会进入无限循环——因为select在输入无效时不会自动break。

3. 实战:用select写一个完整可用的部署菜单

3.1 场景设定:一个带校验和环境选择的部署脚本

为了真正体现select的能力,我模拟一个常见的运维场景。某公司有一套部署脚本流程,分为构建、备份、发布、回滚、退出五个操作,同时需要选择目标环境(开发、测试、生产)。这个脚本面向的是负责部署的同事,要求如下:

  1. 进入脚本先选环境,再选操作;
  2. 每次操作后自动回到操作菜单,直到用户选择退出;
  3. 用户输入无效编号时给出提示,不退出循环;
  4. 关键操作要求二次确认;
  5. 整个交互过程要有清晰的提示和终止方式。

3.2 第一版:环境选择+操作菜单+case分发

先把整体框架搭出来:

#!/bin/bash # 环境定义 env_names=(开发 测试 生产) env_dir_map=(dev test prod) # 第一步:选择环境 PS3="请选择部署环境:" select env_name in "${env_names[@]}"; do case "$REPLY" in 1|2|3) env_index=$((REPLY - 1)) ENV="${env_dir_map[$env_index]}" echo "已选择环境: $env_name" break ;; *) echo "无效环境编号,请重新选择。" ;; esac done # 第二步:选择操作 PS3="[$ENV] 请选择操作:" select action in 构建 备份 发布 回滚 退出; do case "$REPLY" in 1) echo "[执行] 构建操作..." # 实际这里可以调用构建函数 ;; 2) echo "[执行] 备份操作..." ;; 3) echo "[执行] 发布操作..." read -p "确认发布到 $ENV 环境吗?(yes/no): " confirm if [[ "$confirm" == "yes" ]]; then echo "[发布] 已确认,开始发布。" else echo "[发布] 已取消。" fi ;; 4) echo "[执行] 回滚操作..." ;; 5) echo "再见。" break ;; *) echo "无效操作编号,请重新选择。" ;; esac done

这段代码在真实场景中已经能跑了。值得留意的是环境选择阶段我用$((REPLY - 1))做了一次数字到数组下标的转换,这样一来env_names和env_dir_map只要保持顺序一致,后面增删环境时只需维护这两个数组。

操作菜单中,发布这个动作加了一个read二次确认。因为select天然是循环,确认取消后不会退出,而是回到菜单再选一次,这个交互逻辑非常顺畅。

3.3 第二版:把菜单封装成函数,复用才是王道

第一版代码没问题,但存在重复。如果脚本里还有"配置文件管理""日志查看"等多级菜单,每一段都复制粘贴,代码会越长越难维护。更好的做法是把"菜单循环"抽象成一个函数。

一个比较常见的设计方案是:传入选项数组和标题,返回用户选中的选项值。但这里有个技术难点——Bash函数很难有效地返回数组,所以我会选择返回REPLY或全局变量。

下面是一个我实际项目中反复用过的封装模式:

#!/bin/bash # 通用菜单函数 # 用法: run_menu "提示文本" 数组引用 目标变量名 # 例: run_menu "请选择环境:" "env_names" "SELECTED_ENV" run_menu() { local prompt="$1" local arr_name="$2" local target_var="$3" # 通过间接引用展开数组 eval "local arr=(\"\${${arr_name}[@]}\")" PS3="$prompt" select selected in "${arr[@]}"; do if [[ -n "$selected" ]]; then eval "$target_var=\"\$selected\"" return 0 else echo "无效选项,请重新输入。" fi done } env_names=(开发 测试 生产) run_menu "请选择环境:" "env_names" "ENV_NAME" echo "你选择的环境是: $ENV_NAME"

注意这里面用了eval+间接引用,在Bash里这是合法的常用手法,但会引入代码注入的潜在风险。如果选项数组中包含用户可控的字符串,需要格外小心。如果选项内容固定且可信,这个封装的便利性是非常明显的。

还有一种不需要eval的替代方案,就是用全局数组+下标:

choose_from_array() { local prompt="$1" local -n ref="$2" # 需要Bash 4.3+ PS3="$prompt" select item in "${ref[@]}"; do if [[ -n "$item" ]]; then echo "$item" return 0 else echo "无效选项,请重新输入。" fi done }

用local -n声明一个变量引用,本质上是对原数组的别名。这个语法需要Bash 4.3以上版本,一般主流Linux发行版自带的Bash都满足。但如果你的脚本要跑在macOS的旧版Bash(3.2)上,这个写法会直接报错,需要退回eval方案。

3.4 边界处理:中断、意外输入与脚本退出码

写完菜单逻辑,真正决定一个脚本是不是"能用"的往往是边界处理。我整理了几个高频场景:

中断信号。用户在菜单里按Ctrl+C,默认行为是直接终止脚本。如果想在退出前做清理工作,需要trap:

cleanup() { echo "" echo "捕获到中断信号,清理临时文件后退出。" # rm -rf /tmp/deploy_*.lock exit 130 } trap cleanup INT

退出码设计。菜单里每一路分支执行完,最好设置明确的退出码。比如发布失败返回非0,脚本外部就能根据退出码做后续联动(比如失败时发通知)。

case "$REPLY" in 3) echo "开始发布..." if ! deploy_func; then echo "发布失败,请检查日志。" exit 1 fi ;; esac

无效输入不卡死。select对无效数字的行为是重新打印菜单,如果选项很多,用户每输错一次就刷一遍菜单,体验很糟糕。可以在循环体开头加一个计数器,连续输错N次就主动退出。

error_count=0 select action in "${actions[@]}"; do if [[ -z "$action" ]]; then ((error_count++)) if [[ $error_count -ge 3 ]]; then echo "连续输错3次,脚本退出。" exit 1 fi echo "无效选项,请重新输入。" continue fi error_count=0 ... done

4. select不是万能的:适用边界与替代方案

4.1 select的优势与局限

先客观说优势。select是纯Bash内置命令,不依赖任何外部程序,一个标准Linux环境就能跑。它对终端的要求极低,SSH会话、串口终端、CI日志里都能显示菜单。代码量也小,相比手写read+case能省一半以上。

局限也很明显。select的输出永远是"编号+选项"的线性列表,单选模式,不支持多选(比如让你勾选多个组件)。它的交互依赖标准输入,脚本被别的程序调用、无法交互时,select的菜单根本不会显示——这一点很多人踩过坑,所谓"脚本在Jenkins里执行卡死",十有八九是select在等输入。

另外它的视觉呈现非常朴素。没有颜色高亮、没有光标移动、没有边框,选项多了以后整个终端就是一列编号。如果你要做一个"好看"的菜单,select做不到。

4.2 非交互参数化:getopts的适用场景

如果你的脚本大概率在无人值守环境里跑,优先考虑getopts而不是select。它们解决的是两码事:

  • select:人坐在终端前,按数字选;
  • getopts:调用方通过命令行参数指定行为,如deploy.sh -e prod -a deploy。

getopts的好处是脚本可以被其他脚本安全调用,配合--help和默认值,比交互菜单更适合做自动化。很多部署平台(比如Jenkins、GitLab CI)都只认命令行参数,不认交互输入。

实际项目中也常见两者混用:脚本支持-e参数指定环境;如果没有传参数、且检测到终端是交互式([[ -t 0 ]]),才弹出select菜单让用户选。这样兼顾自动化和人工使用场景。

4.3 全屏图形化:whiptail与dialog

如果你的Linux发行版装了whiptail(很多系统自带)或dialog,脚本里可以做出带框的菜单、进度条、输入框,交互体验比select高一个档次。

whiptail --title "部署菜单" --menu "请选择操作" 15 60 4 \ "1" "构建" \ "2" "备份" \ "3" "发布" \ "4" "退出"

但需要注意:这类工具渲染的是类似图形界面的终端画面,在普通终端里体验尚可,在日志重定向、管道、无TTY环境(比如部分CI)里会直接报错或输出乱码。再者它们会清掉当前屏幕,回显内容和退出后画面都会受到影响,不适合所有场景。

4.4 模糊搜索型:fzf

fzf本身不是菜单生成工具,而是一个模糊查找器。管道喂给它选项列表,用户输入关键字过滤,回车选中。当选项数量达到几十个甚至上百个时(比如选择一台历史主机、选择一个镜像版本),select会印出一长屏编号,fzf反而游刃有余。

# 依赖 fzf 安装 selected_env=$(printf "%s\n" "${env_names[@]}" | fzf --prompt="请选择环境:")

fzf也有它的前提:需要额外安装,且在纯内网服务器的默认环境里不一定有。产品环境里我倾向于把select作为兜底方案,fzf作为增强体验。

4.5 几个方案的选型建议

需求场景推荐方案理由
非交互自动化、参数化调用getopts不依赖 stdin,可直接被脚本调用
选项少且需要在任意Linux上运行select零依赖,内置实现
选项多、带方向键全屏效果whiptail/dialog视觉体验好,适合内部工具
选项非常多、需关键字搜索fzf模糊搜索效率极高
远程SSH且终端类型未知select兼容最广,不容易乱码

一句话总结选型思路:先问自己这个脚本是"给人交互操作用"还是"给程序调用用"。前者选select或whiptail;后者直接用getopts。

5. 常见问题与排查技巧实录

5.1 输入回车后没有反应,一直刷菜单

这是最多人遇到的问题。原因通常是:用户输入了"1"但select循环体里没有一个能break或正常分支的逻辑。如果循环体里的case没有匹配项,且没有*)兜底,那么循环会继续——视觉上就是"菜单刷了一遍又一遍"。

排查思路很直接:在循环体开头加一条调试输出:

select opt in "${options[@]}"; do echo "DEBUG: REPLY=$REPLY opt=$opt" ... done

看输出就能判断到底REPLY和opt拿到了什么值。常见情况是用户输入"1"但case匹配的是$opt而不是$REPLY,结果两边没对上。

5.2 set -u引发的REPLY未定义报错

很多shell脚本规范开头都会写set -euo pipefail,其中-u表示遇到未定义变量直接报错。但select有一个特殊的默认行为:用户直接按回车(空输入)时,REPLY仍然是空串,这没问题;可如果你的代码在select之外读$REPLY,而用户从未进入过任何一次select迭代,REPLY就是未定义的,set -u会直接中断脚本。

解决方式是提前初始化:

REPLY="" select opt in ...

或者只在select内部访问REPLY,不要拿到外面用。

5.3 选项内容带空格,选中的值被截断

比如:

select opt in "生产环境" "开发环境"; do ...

这本身没问题,因为双引号在in列表里是合法的。但如果你用"${options[@]}"展开数组,数组每个元素是一个完整的域,这点不用担心截断。真正容易出问题的是在步骤里再对$opt做二次解析,比如for i in $opt,这就会把带空格的字符串拆开。记住一条原则:select选出来的值,凡是当整体使用的地方,一律加双引号"$opt"。

5.4 与read混用时的输入残留坑

在select循环体内调用read时,read会读取终端输入。此时如果用户执行了某些操作导致在标准输入流中残留数据,比如在脚本中通过管道喂入输入,read可能读到的是残留字符而不是用户新输入的内容。

一个实用技巧:在调用read前先清空输入缓冲区:

read -t 0.1 -n 10000 discard 2>/dev/null || true read -p "确认吗?(yes/no): " confirm

第一行read会尝试读取并丢弃缓冲区里已有的数据。注意,如果是在管道输入场景(比如echo "1" | script.sh),这个技巧会直接中断脚本的执行方式,所以只在交互式终端里用为妙。

5.5 脚本要退出却没有合适的出口

select不会自动退出循环,你必须给它提供退出路径。一个比较容易忍的设计是只有选择"退出"才break。更灵活的做法是监听EOF(比如用户按Ctrl+D):

select opt in "${options[@]}"; do ... done

在某些终端的Ctrl+D场景下,select会直接结束循环,后面的代码会继续执行。但有些Bash版本在EOF时表现不统一,稳妥的退出设计还是菜单里显式的"退出"选项,这也有益于用户理解。

5.6 select在小窗口和窄终端下的显示问题

如果你的脚本会在很小的终端窗口里运行,选项一多,打印的菜单会换行混乱。虽然这不是select本身的问题,但快速缓解的办法是给选项名字精简,或者分两级菜单,主菜单只放大类,进入后再展开子项。select没有光标定位能力,完全靠行输出,所以"保持选项名称简短"是提升终端体验最有效的手段。

5.7 代码的"可读性陷阱":REPLY匹配还是选项名匹配

实际写代码时,我发现很多同事在case里习惯写:

case "$opt" in "备份") ... "发布") ... esac

这种写法在连续输错重选时没有副作用,但如果选项名称里带空格或特殊字符,就很别扭。我个人更推荐用REPLY做分支,但会在case条件上加注释,标明1对应哪个选项。长脚本和团队协作场景下,维护者不需要盯着菜单数组数序号,减少对应关系的认知负担。

注意:用REPLY做分支时,务必保证选项数组序号和case分支序号完全一致。调整数组顺序时必须同步修改case,两者是强耦合关系。如果选项较多,建议按选项内容分支。

6. 进阶玩法:把select嵌进更复杂的脚本结构

6.1 多级菜单联动

单级菜单只是select的起点。实际部署场景往往有两级甚至三级菜单:主菜单选组件,子菜单选具体操作,再下一级选参数。

多级菜单联动时,我习惯把每一级菜单的PS3和选项都放进函数:

show_group_menu() { local group=$1 case "$group" in "app") PS3="App组件操作:" select action in 启动 停止 重启 返回上层; do case "$REPLY" in 1|2|3) perform_operation "app" "$action" ;; 4) break ;; *) echo "无效选项";; esac done ;; ... esac }

这种逐层函数化的设计可以避免全局变量污染,也让"返回上层"变得很容易——内层select的break退出后,自然就回到了外层菜单。

6.2 将select用于配置向导

另一个高频场景是脚本第一次运行时自动生成配置文件。比如初始化一个工具,需要用户提供环境类型、数据目录、端口号等。用select选择离散值(比如环境类型),用read输入开放值(比如端口号),两者结合:

PS3="选择环境类型:" select env_type in 开发 生产 自定义; do case "$REPLY" in 1) PORT_DEFAULT=8080 ;; 2) PORT_DEFAULT=8080 ;; 3) read -p "请输入自定义环境名称: " env_type read -p "请输入端口号: " PORT_DEFAULT ;; esac break done

这种向导式脚本配合select的"输错重输"能力,用户体验相当稳。

6.3 隐藏彩蛋:调试和快捷指令

有些人不知道select的输入其实不限于数字。只要你输入的字符串能精确匹配某个选项(例如输入"备份"两个字),REPLY会是备份,而opt同样会匹配到对应选项。这意味着"按数字选"只是最基本方式,还支持按选项名直接输入。

不过这个特性也有反作用:如果某个选项名称恰好是数字(比如选项列表里有"2号套餐"),输入"2"会与数字2冲突,select的处理优先匹配编号。这种模棱两可的情况尽量在选项命名时避免。

6.4 配合颜色输出的终端美化

select自身输出是纯文本,但选项里可以嵌入ANSI颜色码:

reset='\033[0m' red='\033[31m' PS3="请选择:" select opt in "查看日志" "${red}危险操作${reset}" "退出"; do ... done

注意颜色码会印在菜单显示上,但选项内容被赋值到opt时仍然包含颜色码。如果你的逻辑是case "$opt" in "危险操作"),因为实际值里带着转义前缀,匹配会失败。一个可行的技巧是:选项数组保持无颜色版本,只在打印到屏幕时做色码增强,但这需要自定义打印逻辑,select的自动打印就没法用了。权衡之下,生产脚本我更推荐保持朴素,把颜色用在echo的结果提示上而不是菜单选项本身。

7. 个人踩坑总结与实用建议

写了这么多,最后分享一下我在真实项目里积累的select使用习惯,有些是踩过坑换来的。

第一,select适合"人坐在终端前操作"的场景,这是它的主场。部署、初始化、诊断这些运维工具用它非常顺手。但千万别交给无人值守的程序调用,否则脚本会在等输入的地方无限期挂起。如果脚本需要同时兼容两类场景,就用[[ -t 0 ]]判断当前是否有终端输入,再决定是否弹出菜单。

第二,选项数组尽量集中管理。把菜单选项定义成独立的数组,放在脚本顶部,比散落在各个函数里好维护得多。如果需要调整菜单项,只需要动一个地方。case分支里用选项内容或者REPLY编号匹配,都要写清楚注释。

第三,交互脚本一定要有明确的退出途径和超时保护。select本身没有超时机制。如果担心用户长时间不操作,可以结合read -t的超时原理变通实现:把select放在一个受控的循环内,记录上次交互时间,超时后自动退出。但说实话,手工实现比较繁琐,大部分场景用不到;普通内部工具保持用户主动退出即可。

第四,如果发现自己的select脚本越来越复杂,该考虑换方案了。当你的菜单需要方向键、多选、模糊搜索时,select这些能力都给不了。换句话说,select应该在最简洁的"单层数字选择"场景发挥价值,复杂交互交给whiptail或fzf才是正路。

第五,也是最后一条:给脚本写一个--help选项永远是好习惯。哪怕有了漂亮的交互菜单,--help也能让新用户在不进入菜单的情况下快速了解脚本能力。菜单是给人临时选的,参数帮助是给人长期用的,两者并不冲突。

select不是Shell里最花哨的工具,但它的"恰到好处"正是它的价值——零依赖、少代码、易维护。在需要快速写一个能用、好用、不容易坏的交互脚本时,我到现在还是会第一反应想起它。把这篇文章里的代码亲手敲一遍,再把它嵌进你手头的部署或初始化脚本里,它大概率不会让你失望。

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

RSA加密算法从原理到实战:公钥私钥、数字签名与OpenSSL操作

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

作者头像 李华
网站建设 2026/10/13 5:44:02

率失真曲线平滑绘制:Matlab与Python的PCHIP插值实战指南

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

作者头像 李华
网站建设 2026/10/13 5:42:38

MinGW免安装版:嵌入式与CI场景下的可复现工具链方案

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

作者头像 李华
网站建设 2026/10/13 5:42:10

C++深拷贝原理与双重释放陷阱解析

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

作者头像 李华
网站建设 2026/10/13 5:42:01

OpenSSL 1.1.1 离线安装包制作与内网部署实战

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

作者头像 李华
网站建设 2026/10/13 5:41:03

从模糊代号到最小闭环:我用四层问题拆解一个叫“rea”的项目

有人丢给你一个项目,名字只有三个字母:“rea”。没有文档,没有需求清单,没有背景介绍,就一个词。你会怎么办?直接开干?那你大概率会踩进深坑。我这些年看到太多从“看不懂的代号”到“重写系统”…

作者头像 李华