尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Ubuntu初始化脚本解析:从手动配环境到自动化重装
每次给电脑重装 Ubuntu坐在终端前敲那二三十条命令的时候我都觉得自己像个手动流水线上的工人git 要装、curl 要装、软件源要换、显卡驱动要找半天、Python 环境配来配去……等这些都折腾完两三个小时就没了这一天原本想干的正事一个字都没动。后来我花了一下午时间把这些重复劳动固化成一整套 Ubuntu 系统初始化脚本从那以后重装系统的耗时从半天压缩到半小时出头而且装出来的环境比我手动装的时候更干净、更统一。今天这篇就聊聊这套脚本是怎么设计的、每一段代码干什么用、以及我在反复重装和改脚本过程中踩过的那些坑。适合所有被重装系统后还要手动配环境折磨过的开发者和桌面用户参考。1. 重装一次系统哪些时间是被白白浪费掉的1.1 手动重建环境的真实时间账单先算一笔账装完 Ubuntu 之后有多少时间是花在必须做的事上又有多少是花在重复劳动无谓搜索上以我自己的使用习惯为例一次干净的系统安装之后至少要执行这些操作替换软件源、apt update apt upgrade、安装build-essential、git、curl、vim、htop、网络工具然后装 GPU 驱动如果显卡是 NVIDIA还要先确认型号、找驱动版本、禁用 nouveau接着是 Python 相关环境、Node 环境、Docker、配置 git 用户名和邮箱最后还有输入法、字体、终端、常用软件。这一套下来少说三四十条命令遇到网络波动、源同步延迟、驱动冲突时间就直奔一下午去了。更麻烦的是手动操作很难保持一致。同一个软件这次忘了装某个依赖下次又多加了一个参数最终两台机器上的环境根本对不上。环境不一致带来的问题比环境本身难搞多了——排查半天发现是某台机器少装了一个库这种事情我遇到太多次。1.2 哪些工作适合写进脚本哪些不适合动手写脚本之前第一件事是划分边界。不是所有东西都适合塞进安装脚本否则脚本会变成一个谁都不敢碰的大炸弹。适合脚本化的操作有三个特征确定性强、无需人工决策、重复成本高。比如替换软件源、安装固定列表的软件包、写入配置文件、启用 systemd 服务、创建用户组、克隆配置仓库这些都是不管执行多少次预期结果都一样的操作。不适合脚本化的操作也有三类第一类是需要物理介入或硬件相关的比如提前在 BIOS 里改启动项、处理磁盘分区这些必须在系统安装阶段完成第二类是需要交互授权的比如涉及公司内部证书、需要输入密码的私密配置我不会明文写进脚本第三类是频繁变化的软件版本策略例如某个工具链的版本策略每个月都在变与其写死版本号不如在脚本里留一个开关由使用者自己决定。1.3 脚本的适用边界桌面版与服务器的差异这套脚本我最初是给桌面版 Ubuntu 写的后来发现服务器上也基本通用只是要砍掉一批桌面相关的模块。所以脚本里我会刻意区分两个分支一个目标是装完能直接干活、能正常上网办公另一个目标是最小化开发环境不装任何图形界面相关的东西。两者的差异集中在输入法、字体、GUI 软件和 GPU 驱动这几个模块上。写脚本的时候我建议用一个开关变量来控制而不是维护两套脚本——两套代码迟早会因为修改不同步而漂移。2. 安装脚本的模块设计先装驱动还是先配源不是拍脑袋定的2.1 模块清单与优先级背后的依赖逻辑我的脚本分六个模块系统基础配置、开发工具链、运行时环境、容器与虚拟化、桌面与常用软件、收尾清理。每个模块的先后顺序不是随便排的背后是依赖关系和稳定性考量。系统基础配置必须放在最前面因为apt的安装源决定了所有软件包的下载速度和可用性换源之后紧接着apt update和apt upgrade保证系统在干净的基线状态上继续操作。开发工具链放在第二因为大部分后续安装过程都依赖gcc、make、git、curl这些基础工具。运行时环境Python、Node、Java 等放在第三它会依赖前面安装的工具来编译或者下载。Docker 和虚拟化放在第四因为 Docker 的安装源和配置最好在系统稳定之后再动。桌面软件放在第五最后再统一清理缓存。显卡驱动我会单独抽出来做成一个独立脚本不放进主流程——原因是它太容易出问题一旦装坏了会导致进不了桌面应该单独可控地执行。2.2 为什么把重复执行安全当作第一原则这个原则值得永远放在第一位同样的脚本跑十次结果应当是收敛的既不能报错也不能产生重复配置。我第一版脚本就是没想清楚这一点每次都无脑apt install一大堆包。重装系统后跑一次没问题但第二次调试脚本时就开始出现各种Package already installed的噪音甚至配置文件被重复追加。后来我写了一个简单的幂等思路安装软件包前先检查是否已安装配置文件的写入用不存在才写入的逻辑需要追加内容时先检查是否已包含目标字符串。实现幂等并不复杂核心就是用 markdown 脚本里的检测-执行代替直接执行。以 apt 为例先dpkg -s 包名判断包是否存在再决定是否执行安装。配置文件更简单用grep -q判断目标内容是否已经存在不存在才执行写入。2.3 参数化与开关让同一个脚本兼顾两类机器一个脚本打天下的前提是参数化。我在脚本头部定义了几个开关变量比如INSTALL_DESKTOP、INSTALL_NVIDIA、INSTALL_DOCKER默认值按最常用的场景设定。执行的时候可以通过命令行参数覆盖比如./ubuntu-init.sh --minimal表示只装服务器最小化环境./ubuntu-init.sh --with-nvidia表示这次要装显卡驱动。这么做的好处是显而易见的。我只需要维护一套脚本不同需求之间的差异通过参数控制而不是每个人拉一个分支改一个副本。脚本里的每个函数都是相对独立的main入口根据参数决定调用哪些函数这个结构让脚本的可读性和可维护性都好了很多。3. 核心脚本代码拆解每一段都值得存进你的备份仓库3.1 脚本骨架与日志函数先看脚本的整体骨架。我用 Bash 写没用什么复杂的框架但开头的基础代码值得每一行都理解清楚#!/bin/bash set -euo pipefail SCRIPT_NAMEubuntu-init LOG_FILE$HOME/${SCRIPT_NAME}-$(date %Y%m%d-%H%M%S).log SCRIPT_DIR$(cd $(dirname ${BASH_SOURCE[0]}) pwd) # 开关参数默认按常规桌面环境 INSTALL_DESKTOP${INSTALL_DESKTOP:-1} INSTALL_DOCKER${INSTALL_DOCKER:-1} INSTALL_NVIDIA${INSTALL_NVIDIA:-0} SKIP_UPDATE${SKIP_UPDATE:-0}set -euo pipefail这三句话是 Bash 脚本保命三件套遇到任何命令返回非零立即退出-e使用未定义变量直接报错-u管道中只要一个环节失败就算失败pipefail。没有这三行脚本经常会带着隐患继续跑下去最后报的错误让你根本找不到源头。日志函数是脚本的可观测性基础log_info() { echo [INFO] $(date %Y-%m-%d %H:%M:%S) - $* | tee -a $LOG_FILE } log_error() { echo [ERROR] $(date %Y-%m-%d %H:%M:%S) - $* | tee -a $LOG_FILE 2 }我用tee同时把日志写到终端和文件里重装完系统后如果发现哪里不对劲直接翻日志文件就能看到每一步执行的时间和结果。这个小习惯排查问题的时候能省下一大半时间。3.2 系统基础配置段软件源、更新与常用工具换软件源是整个脚本里最敏感的一步出问题就是全网失联的节奏。我的做法是先检查当前系统的版本代号再判断源文件是否已经改过config_apt_source() { local codename codename$(lsb_release -cs) local sources_file/etc/apt/sources.list local target_mirrormirrors.example.com/ubuntu if grep -q $target_mirror $sources_file 2/dev/null; then log_info 软件源已经指向镜像跳过替换 return 0 fi log_info 正在备份并替换软件源系统代号: $codename sudo cp $sources_file $sources_file.bak.$(date %Y%m%d) # 这里是关键直接写入目标镜像的源配置 log_info source list 已替换执行 apt update 验证 sudo apt update }注意codename变量很重要。Ubuntu 每个版本的代号不同比如 20.04 是 focal22.04 是 jammy24.04 是 noble镜像站的路径会带代号。很多人在网上抄来一段源配置没注意版本对应关系最后apt update报一堆仓库没有 Release 文件的错根源就在这。我最初也吃过亏所以现在脚本里永远先读取当前系统代号再拼镜像地址。基础工具安装我用了一个包裹函数install_if_missing() { if dpkg -s $1 /dev/null 21; then log_info 已安装: $1跳过 else log_info 正在安装: $1 sudo apt install -y $1 fi } install_base_tools() { local packages(curl wget vim git htop net-tools unzip zip tree \ build-essential software-properties-common apt-transport-https ca-certificates) for pkg in ${packages[]}; do install_if_missing $pkg done }dpkg -s判断是否安装装了就直接跳过。这样整个函数天然可重复执行不会因为中途断掉再重跑就报错。3.3 开发环境段Python、Node 与 Git 配置Python 环境这块我的原则是系统全局只装一个python3-pip和python3-venv多版本管理交给 pyenv项目依赖一律用虚拟环境。不在全局环境里乱装包是避免环境冲突的根本手段。setup_python_env() { install_if_missing python3-pip install_if_missing python3-venv # 配置 pip 使用国内镜像加速并设置默认超时 local pip_conf$HOME/.pip/pip.conf if [ ! -f $pip_conf ]; then mkdir -p $(dirname $pip_conf) cat $pip_conf EOF [global] index-url https://mirrors.example.com/pypi/simple trusted-host mirrors.example.com timeout 60 EOF log_info pip 镜像配置已写入 $pip_conf fi }这里有一个细节值得留意我在代码中使用cat file EOF的写法EOF加了引号后变量不会展开可以避免意外把脚本里的变量值写到配置文件里。如果我不加引号$HOME会被当场展开成当前用户目录这在某些场景下是不希望发生的。Node 环境的思路类似。我不直接用 apt 装 nodejs因为 apt 源里的 Node 版本往往偏老而且安装后还跟 npm 的升级逻辑纠缠在一起。我更喜欢用 nvmNode Version Manager来管理安装脚本如下setup_node_env() { if command -v node /dev/null 21; then log_info Node 已存在跳过 nvm 安装 return 0 fi log_info 安装 nvmNode 版本由 nvm 管理 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash export NVM_DIR$HOME/.nvm # shellcheck disableSC1091 [ -s $NVM_DIR/nvm.sh ] \. $NVM_DIR/nvm.sh nvm install --lts npm config set registry https://mirrors.example.com/npm/ log_info Node 与 npm 镜像配置完成 }Git 配置没有技术难度但很容易被人忽略。我在脚本里统一写入user.name、user.email、默认分支名和常用别名保证每台机器的 Git 行为一致。这些配置是用户级别的写入${HOME}/.gitconfig不涉及任何全局改动。3.4 桌面环境段输入法、字体与常用软件桌面环境的配置因人而异我只说两个大部分用户都会碰到的模块输入法和终端。输入法框架现在主流是 Fcitx5 和 iBus 两套体系我选 Fcitx5因为整体兼容性和配置清晰度更好。安装命令很直接装完以后要注意设置环境变量否则输入法启用不了:setup_desktop() { sudo apt install -y fcitx5 fcitx5-chinese-addons fonts-noto-cjk # 写入输入法环境变量 local im_config/etc/environment if ! grep -q GTK_IM_MODULE $im_config; then echo GTK_IM_MODULEfcitx | sudo tee -a $im_config echo QT_IM_MODULEfcitx | sudo tee -a $im_config echo XMODIFIERSimfcitx | sudo tee -a $im_config fi }/etc/environment是系统级环境变量文件写入后注销重新登录即可生效。每次重装完系统最容易忘记的就是这几行环境变量忘了写好多人还以为是输入法软件本身有问题其实是系统根本没有把输入法挂到图形环境上。常用的图形软件VS Code、浏览器、截图工具等我统一集中在install_misc_apps函数里。这里我特别想提醒一点像 VS Code 这类软件的安装官方推荐的方式是把官方 GPG key 和源仓库写进 apt 配置再通过 apt 安装。这个流程完全可以用脚本自动化唯一要注意的是 GPG key 的下载地址偶尔会有网络波动脚本里要做好失败重试或日志告警。4. 最容易翻车的三个环节软件源、显卡驱动和 Python 环境4.1 软件源版本代号对不上update 查到怀疑人生软件源是最前置的步骤也是我最开始踩的第一个坑。当时我图省事从网上复制了一段源配置直接写进/etc/apt/sources.list结果apt update报错说找不到 Release 文件。我盯着屏幕愣了半天后来仔细一看才发现配置里写的是另一个发行版的代号。这种错误的坑点在于镜像站的路径中确实包含发行版代号不同版本的源是不能通用的。我在脚本里改用lsb_release -cs动态获取代号之后再也没出过这个问题。这里也给大家交个底如果你在改源之前不确定自己系统的代号直接在终端执行lsb_release -a看输出即可只需要记住你的配置必须和你实际版本完全对应。另外还有一个容易被忽略的点手动编辑软件源之前务必先备份。我现在脚本里会自动把原始文件复制成带时间戳的.bak文件。系统装好之后如果发现某些包版本异常随时可以回退到官方源重新更新这比出了问题再想办法恢复容易得多。4.2 显卡驱动装完进不了桌面怎么办NVIDIA 驱动应该是 Linux 桌面用户遇到最多问题的重灾区表现形式无非三种装完黑屏、进不了桌面、进是进去了但分辨率异常或nvidia-smi报错。我的建议是不要一开始就上官网手动下载驱动。多数情况下 APT 源里的驱动足够稳定而且能用系统更新机制持续跟进。先执行ubuntu-drivers devices查看系统推荐的驱动版本再执行sudo apt install -y nvidia-driver-xxx。驱动装完通常需要重启脚本只能做到安装标记重启这一步没办法替你完成。如果你不小心装坏了进不了桌面也不用急着重装系统。在登录界面按快捷键切到虚拟终端通常是CtrlAltF2或F3登录后把驱动包彻底卸载再重新安装大多数情况下都能救回来。这里多说一句如果涉及禁用 nouveau 的步骤建议单独做一个内核参数添加的脚本片段不要混在通用模块里——因为它在不同型号显卡、不同内核版本上的表现差异很大。4.3 Python 环境系统解释器与用户环境互踩Python 环境是另一个重灾区典型场景是这样的系统自带 Python 3.10你自己又装了 Python 3.11 并改了PATH某个包用pip install装进去了但命令行工具怎么都调不出来。大多数情况不是软件坏了而是你装的包在哪个解释器里和你在终端敲的 python 是哪个解释器根本不是一回事。我的经验是用统一的习惯来约束环境只要涉及 Python 项目一律在项目目录里执行python3 -m venv .venv创建虚拟环境然后激活环境后再装依赖。系统层面的 Python 不做任何除了pip/venv之外的扩展安装。脚本层面只需要保证系统里存在python3-venv余下的交给每个项目自己去隔离。额外再提一个很多人不注意的细节pip装了一些带命令行入口的包之后如果提示command not found先检查python3 -m pip show 包名的输出看包的Location是全局还是用户目录。然后检查~/.local/bin是否在你的PATH中——这个目录经常不在默认的 PATH 里导致装成功了却用不了。脚本里我会显式地把/home/$USER/.local/bin追加到PATH配置这是一个低成本高回报的修复。5. 提高脚本复用度日志、断点和备份三件套5.1 日志不只是记录它是排查问题的地图早期版本的脚本只在屏幕输出遇到问题一头雾水。后来我加上了日志文件每次执行就生成一个带时间戳的*.log脚本跑挂了直接查看最后几十行就能锁定挂在哪一步。我更推荐的做法是日志信息区分两类。普通步骤用[INFO]真正失败的地方用[ERROR]并且带完整上下文。这样即便你的脚本被安排成无人值守方式运行回来只需要一条grep ERROR ~/ubuntu-init-*.log | tail就能找到最需要关注的点。5.2 断点续跑中断之后不用从头再来脚本跑了一半被用户中断、或者某一步网络超时导致失败这种情况在真实环境里太常见。如果脚本不设计断点续跑你不得不从头执行一遍——虽然幂等设计保证了重复执行安全但前面那些已经装好的包还要重来一遍很浪费时间。我用的方案是标记文件机制。每个模块执行成功之后在脚本目录下写一个.done标记文件下一次执行时先检查标记文件是否存在存在就跳过整个模块。mark_done() { local module_name$1 touch $SCRIPT_DIR/.done-${module_name} } is_done() { local module_name$1 [ -f $SCRIPT_DIR/.done-${module_name} ] } if is_done base_tools; then log_info 基础工具模块已完成跳过 else install_base_tools mark_done base_tools fi这套机制配合幂等设计脚本的运行效率和稳定性都上了一个台阶。当然如果你想强制重新执行删掉对应模块的.done文件即可。我还加了一个--force参数方便一键清理所有标记文件重新跑全套。5.3 备份现有配置别让旧配置成为新系统的历史包袱每次重装系统之前我会先想办法把旧系统里的关键配置文件备份出来。这里面最有价值的往往不是点文件本身而是点文件里积累的自定义内容。我在脚本里做了一个backup_old_config的函数它会扫描用户的.bashrc、.zshrc、.gitconfig、.config下几个重点目录打包成一个归档文件放到自定义位置。这样重装之后可以对比新旧配置看看哪些习惯值得保留哪些旧的配置可以狠心抛弃——毕竟系统都重装了那些堆了好几年的无效配置也该清理掉了。6. 脚本的日常维护装上只是开始6.1 跨版本升级时的适配清单Ubuntu 大版本每两年发布一次升级之后脚本很可能需要跟着调整。我的维护经验是按照固定清单过一遍发行版代号是否变化影响了软件源路径、默认 Python 版本是否变化影响了 pyenv 和各项目的版本策略、输入法框架是否发生变化、Docker 安装源是否需要更新。这些适配工作看起来琐碎但每次大版本升级只需要花半小时就能完成。如果你把脚本扔在那里两年不管下一次重装系统大概率会遇到一批过时的源和安装方式到时候反而更浪费时间。6.2 我常用的测试方式和维护节奏脚本的测试策略我只有一个建议在虚拟机里先跑一遍。我习惯用一台虚拟机挂载新版 Ubuntu 镜像先执行最小化安装场景再执行完整桌面场景。虚拟机的网络和实际使用环境越接近越好因为源配置、GPG 请求这些环节最容易受到网络环境影响。测试的重点不是能不能装完而是装完之后环境是否一致。我会在跑完脚本后手动检查几个指标能否在一个新目录里创建 Python 虚拟环境、能否成功拉取 docker 镜像、输入法是否自动启动、显卡驱动是否加载成功。这些检查全过才说明脚本在新版本系统上真的可以放心用。6.3 最后分享一个习惯写这套脚本之后最大的收获不是重装系统变快了这么简单而是我开始用可复用、可验证的思路去处理环境配置。以前哪个工具出问题就临时敲命令修一下修完也不会留下任何痕迹现在我会顺手把这个修复写进脚本让下次重装系统时少踩一遍同一个坑。脚本本质上是一种可执行的经验记录。我建议你也从自己的实际需求出发把每次重装系统后的安装命令整理成一个简单脚本先不管写得多优雅跑通了再逐步优化。你不需要一开始就把所有模块都设计完从最常做的几个步骤开始随着反复重装慢慢积累这套脚本会越来越贴合你自己的使用习惯。重装系统的效率翻倍只是顺带的结果更值钱的是你终于把手动流水线工人这份工作辞掉了。
RELATED

相关推荐

普通个人电脑跑大模型:显存、量化与CPU+GPU混合推理实战指南

普通个人电脑跑大模型:显存、量化与CPU+GPU混合推理实战指南

前阵子有个朋友问我:“普通个人电脑到底能跑多大的大模型?”我当时没直接甩个数字给他,因为这个问题本身就是一个“多变量博弈”:同样叫“7B”的模型,float16 版本和 int4 量化版本,显存需求能差出三倍&…

📅 2026/10/19 7:16:11
个人电脑跑大模型:显存、量化与CPU/GPU混合推理实战指南

个人电脑跑大模型:显存、量化与CPU/GPU混合推理实战指南

经常有人问我:“我的电脑就一块普通显卡,显存不大,到底能不能跑得动现在流行的大模型?”这个问题听起来很玄,其实拆开就是一笔账:大模型跑起来消耗多少显存,取决于它的规模,也取决于…

📅 2026/10/19 7:16:11
ADPAM自适应双感知注意力:提升红外小目标检测的YOLOv11改进实践

ADPAM自适应双感知注意力:提升红外小目标检测的YOLOv11改进实践

中午刚从标注平台拉完一批红外测试图,视线还没从屏幕上挪开,我就把几个常用检测模型跑了一遍对比——YOLOv8n、YOLOv9t,还有最新的YOLOv11n。结果跟在座大多数做小目标检测的兄弟预想的一样:漏检一串,虚警一堆&#xf…

📅 2026/10/19 7:16:11
MORE NEWS

更多资讯

📰

AI辅助毕业论文写作全指南:九类工具按流程用才高效

一到毕业季,后台私信里被问得最多的就是:学姐,专科毕业论文真的写不动了,有没有好用的AI论文写作软件推荐?以前我会犹豫一下,现在我一般会回:能,但你先别把它们当成“论文生成机”。…

📰

Diagram设计实操指南:架构图与流程图从理清关系到工具选型

拿到“diagram-design”这个项目标题,我第一反应是:这不是一个“画图”的活儿,而是一个“把关系说清楚”的活儿。不管是画系统架构图、业务流程图、时序图,还是做一套可视化设计规范,diagram-design最终都要回答同一个…

📰

PHP flush函数实战:分块输出与流式响应优化页面体验

1. 为什么非要把页面“切开”给你看先聊聊我最早遇到 flush 的场景。做一个后台管理系统的日志查询页面,某个功能要拉取近百万行日志做过滤统计,后端处理完整理好数据再返回,用户眼睁睁看着白屏六七秒,然后整个页面“啪”地一下蹦…

📰

STM32Cube F1 V1.8.0 固件包实战:HAL与LL库选型、工程配置与避坑指南

简介:STM32Cube_FW_F1_V1.8.0 是意法半导体面向 STM32 F1 系列微控制器推出的官方固件封装库,配套 STM32CubeMX 图形化配置工具,适用于使用 Keil、IAR 进行嵌入式开发的初学者与工程师。它通过 HAL 与 LL 两层标准化 API 屏蔽底层寄存器差异&…

📰

FieldFox手持频谱分析仪选型指南:N9912A/N9917A/N9951A/N9961A对比与实操

一台只有A4纸尺寸的仪表,能塞进背包,能在楼顶、山顶、基站塔下连续工作一整天,还能顺手完成频谱分析、天馈线测试、微波链路测量,这就是是德科技手持频谱分析仪在外场射频工程师圈子里地位很高的原因。N9912A、N9917A、N9951A、N9…

📰

GATr:让Transformer从‘背坐标’进化到‘算几何’

前阵子看了个很有意思的工作:GATr(Geometric Algebra Transformer),全称是几何代数Transformer。我当时的反应和大多数人一样——Transformer不是处理文本和图像的吗,怎么跑去学"几何直觉"了?但深…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬