简介:面向需要在无网络或弱网环境部署GCC的Linux运维及开发人员,这份资源将离线安装所需的全部RPM依赖包与一键安装脚本整合为一个zip压缩包,省去逐一下载和手动处理依赖的麻烦。压缩包共收录12个文件,包括11个适用于64位CentOS/RHEL 6系统的RPM包(涵盖cpp、gcc、gcc-c++、glibc-devel、mpfr、ppl等核心组件)和1个install_gcc执行脚本,整体大小约24MB,可快速拷贝至目标服务器使用。目前已有556人浏览学习,适用于内网服务器、教育实验环境等无法连接外网的场景。借助脚本可自动完成环境检查、依赖解析、批量安装与验证清理等步骤,即使不熟悉RPM机制也能按提示完成部署;同时附带的依赖包版本均经过验证,可显著降低因缺少库或版本不匹配导致的编译失败概率,为后续搭建本地开发环境打下扎实基础。
1. 离线装 GCC 永远卡在“依赖链”上:先把包捞全,再让脚本自动兜底
内网机器想装一套可用的 GCC 工具链,难点从来不在 GCC 本身,而在它背后那串看不见的依赖:gcc、g++ 本体只是入口,真正让编译器能跑起来的是 gmp、mpfr、mpc 几个数学库,以及 libc6-dev 里的标准头文件。大多数人第一次离线安装 GCC 都是这样翻车的:拷过去dpkg -i,系统提示缺 libmpc3;找到库装上,又提示 libisl 版本不对;折腾半天,gcc --version还是报 command not found。你需要的不是“再找一个包”,而是先在一台联网机器上把依赖链整体取下来,再带一个会重试、会补装、会验证的安装脚本去现场。这套方法在 Ubuntu / Debian / 麒麟(Kylin)和 CentOS / RHEL 上都能落地,下面就从打包讲起。
2. 把依赖链整个捞下来:在联网机器上打好离线包
2.1 先分清“GCC 安装包”和“GCC 工具链”:依赖到底分几层
先用一个最常见的场景说明问题:你在内网服务器上执行gcc hello.c,系统要依次完成预处理、编译、汇编、链接,这四步分别需要头文件、编译器本体、汇编器、动态库和链接脚本。一个孤零零的 gcc 包只解决了“编译”这一环,其余每一环都有独立的基础包。
以 Ubuntu 系为例,一套最小可用工具链至少包含以下几层:
| 层 | 包 | 作用 |
|---|---|---|
| 编译器本体 | gcc、cpp | 预处理与 C 编译 |
| C++ 编译器 | g++ | C++ 编译,依赖 gcc |
| 基础头文件 | libc6-dev、linux-libc-dev | 提供标准 C 库头文件 |
| 运行库 | libgcc-s1、libstdc++6 | 编译产物运行时依赖的共享库 |
| 数学依赖 | libgmp10、libmpfr6、libmpc3、libisl | GCC 做优化与代码生成时依赖 |
| 构建辅助 | make、dpkg-dev、binutils | 构建、打包与汇编工具链 |
理解了这条链,就能解释为什么“拷贝一个 gcc 的 deb 包”从来都不能用。真正要收集的是上面整一层闭包,这也是我坚持用包管理器下载而不是手动去官网找的原因,apt 或 dnf 的依赖解析远比人眼可靠,它能顺着包的元数据把依赖完整带出来。
2.2 Ubuntu / Debian / 麒麟(Kylin)打包命令:把 apt 缓存整个掏出来
准备一台和目标机器相同发行版、相同架构的联网机器。你要是拿 Ubuntu 20.04 的包去装 Kylin V10,库版本对不上,半路就会崩。确认架构后执行下面的命令:
# 在联网机器上执行 sudo apt-get clean # 清掉缓存里的旧包,避免混入 mkdir -p ~/gcc-offline && cd ~/gcc-offline sudo apt-get install --reinstall --download-only gcc g++ make build-essential # 把 apt 刚下载到 /var/cache/apt/archives 的 deb 全部搬到当前目录 cp /var/cache/apt/archives/*.deb ./ 2>/dev/null || true # 打包带走 tar -czf gcc-offline.tar.gz ./*.deb这里重点解释两个参数。--download-only告诉 apt 只下载、不安装,下载的包默认落在/var/cache/apt/archives;配合--reinstall则是强制它把已经是最新的包也重新下载一遍,否则 apt 会因为“软件包已经是最新版本”直接跳过,你最终拿到一个空的缓存目录。上面cp把 archives 下所有 deb 拷到当前文件夹,再打包的就是完整的离线源。
如果你连 build-essential 也不想装,只想拿纯 C 编译器,可以把包名收紧成gcc cpp make libc6-dev,但多数现实项目早晚需要 g++,这里直接给全,省得到现场再补一趟。
2.3 CentOS / RHEL / RPM 系怎么打全:yumdownloader 的 --resolve
在 CentOS 7.9、RHEL 或基于 RPM 的麒麟服务器版上,思路一样,工具换成 yumdownloader。问题是 yum-utils 这个包默认不装,先装它,再下载。
# CentOS 7 / 8 系 sudo yum install -y yum-utils mkdir -p ~/gcc-offline && cd ~/gcc-offline sudo yumdownloader --resolve --destdir=./ gcc gcc-c++ make tar -czf gcc-offline-rpm.tar.gz ./*.rpm如果你在 CentOS 8+ 或 RHEL 9,直接用 dnf 自带的功能:
sudo dnf download --resolve --alldeps gcc gcc-c++ make--resolve是这里的灵魂,它会沿着依赖树把所有运行库和开发库一并下载;--alldeps则把包括弱依赖在内的包也带进来。RPM 系打包时最容易漏掉 gcc-c++,很多内网项目在编译.cpp时才发现 g++ 根本没有,所以这里务必把 gcc-c++ 写进下载列表。
2.4 包齐不齐:在打包机上先做一轮验证
很多人打完包直接往 U 盘一拷,到现场才收到“缺这个缺那个”的报错。我一般会在联网机器上先跑一遍模拟安装,确认包目录自洽:
# 在联网机器上验证依赖是否闭合 cd ~/gcc-offline sudo dpkg -i ./*.deb < /dev/null 2>&1 | tail -20如果机器上本来就装了相同版本,dpkg 会提示“已安装”然后跳过,这正常。真正要留意的是依赖关系不满足这类输出,它说明你下载的清单里少了某一层。此时根据输出里的具体包名,回去补下:
# 补下遗漏包的通用姿势 apt-get install --reinstall --download-only 缺的包名 cp /var/cache/apt/archives/缺的包名*.deb ./RPM 系也能用rpm -Uvh ./*.rpm做同样的事,只是会输出较多“already installed”的警告,过滤掉再看剩余报错即可。这一步能筛掉九成现场问题。
3. 离线安装脚本怎么写:让失败变成可预期、可重试的流程
3.1 deb 系安装脚本:一组幂等的操作序列,而非一条命令
现场安装的复杂度不在于“安装”这个动作,而在于依赖包的安装顺序。dpkg -i 对多个 deb 的处理并不是把依赖挑出来按拓扑排序,它是逐个安装,遇到缺依赖直接停下来报错。如果你把几十个 deb 一口气丢给它,第一遍能装成功的只有那些没有依赖或依赖已经被系统满足的包。
所以安装脚本的正确思路不是追求一遍成功,而是设计成“多跑几轮、每轮都有进展”的幂等流程。下面是 deb 系脚本:
#!/bin/bash # install-gcc-offline-deb.sh # 用法:把脚本和所有 .deb 放在同一目录,root 执行 set -u cd "$(dirname "$0")" log() { echo "$(date +%H:%M:%S) $*" | tee -a install.log; } log "开始离线安装,看到 deb 包数量:$(ls ./*.deb | wc -l)" # 第一遍:让所有能装的包先就位 dpkg -i ./*.deb < /dev/null >> install.log 2>&1 if [ $? -eq 0 ]; then log "第一遍全部装完,跳过修复流程" else log "存在未满足的依赖,开始配置修复" dpkg --configure -a < /dev/null >> install.log 2>&1 || true # 第二遍:依赖顺序产生的缺口,此时已有一部分被补齐 dpkg -i ./*.deb < /dev/null >> install.log 2>&1 || true dpkg --configure -a < /dev/null >> install.log 2>&1 || true fi # 最后给个明确的结论 if command -v gcc >/dev/null 2>&1; then log "安装成功:$(gcc --version | head -1)" else log "gcc 仍不可用,请查看当前目录 install.log 的报错并补充缺失包" exit 1 fi脚本里刻意做了三件事:set -u防止未定义变量在自动流程里埋雷;dpkg -i ./*.deb因为目录里的文件按字母序展开,低层库会先被处理;第二轮用dpkg --configure -a把上一轮“已安装但未配置”的包状态修好,然后再整体装一次。注意全程没有用--force-depends,后面会单独解释为什么不要轻易用它。
这个脚本可以放心重复执行。dpkg 是幂等的,重复安装同一个包不会覆盖已有配置,--configure -a也只是把 install 成功后没走到 configure 的包补完。跑两遍没有任何副作用,这是离线安装脚本最关键的性质。
3.2 rpm 系安装脚本:rpm 顺序装 + dnf 兜底
RPM 系的依赖检查比 dpkg 严格得多,rpm 在依赖缺失时会直接拒绝安装整个依赖链,而不会装一半留一半。因此 rpm 系的脚本更简单,但需要加一条 dnf 兜底,因为 dnf 具备自动解析本地 rpm 依赖的能力:
#!/bin/bash # install-gcc-offline-rpm.sh # 用法:把脚本和所有 .rpm 放在同一目录,root 执行 set -u cd "$(dirname "$0")" log() { echo "$(date +%H:%M:%S) $*" | tee -a install_rpm.log; } # 先尝试整体安装,再尝试升级安装 rpm -ivh ./*.rpm >> install_rpm.log 2>&1 || true rpm -Uvh ./*.rpm >> install_rpm.log 2>&1 || true # 如果还有缺依赖,交给 dnf 从本目录解析 if command -v dnf >/dev/null 2>&1; then log "有依赖缺口,尝试用 dnf 从本地目录补" dnf install -y ./*.rpm >> install_rpm.log 2>&1 || true fi command -v gcc >/dev/null 2>&1 && { log "安装成功:$(gcc --version | head -1)" } || { log "gcc 仍不可用,请查看 install_rpm.log" exit 1 }这里-ivh做首次安装,-Uvh做升级。同样是幂等设计,整个脚本可以放心跑多次,不会把系统弄乱。dnf 兜底这行在完全离线时不会去访问网络,它只扫描命令里给的本地 rpm 文件。
3.3 分发文件与现场操作的规范化流程
到现场之后,建议按固定路径操作,避免脚本找不到包或日志不知道写到哪:
# 在离线目标机上执行 mkdir -p /opt/gcc-offline tar -xzf gcc-offline.tar.gz -C /opt/gcc-offline cd /opt/gcc-offline bash install-gcc-offline-deb.sh 2>&1 | tee /var/log/gcc-offline-install.log这个结构的核心约束是:install-gcc-offline-deb.sh必须和所有.deb处于同一目录。脚本第一行cd "$(dirname "$0")"会自动切换到脚本所在目录,所以它可以用相对路径./*.deb。如果你把脚本和包放在不同层级,这条路径就会失效,这也是新手最容易踩的细节。
在这个目录结构里,把原始 tar 包留一份不要删,就是最好的“后悔药”。万一装到一半发现版本不对,可以先dpkg -r $(dpkg -l | grep gcc | awk '{print $2}')撤掉再重新装,前提是 tar 包还在,GCC 的原始 deb 还在,随时能回到安装前的状态。
4. 离线安装 GCC 的高频踩坑现场与排查清单
4.1 现象:dpkg 报依赖不满足,但相关 deb 明明就在眼前
安装到一半,屏幕提示gcc-12 depends on libc6-dev, which is not installed,但你明明在 gcc-offline 目录里看到了 libc6-dev 的 deb。这是离线安装最常见、也最让人窝火的一类报错。
原因一般有两个:一是安装顺序,字母序把 gcc-12 排到了 libc6-dev 前面;二是你下载的 libc6-dev 版本与系统里 libc6 的版本不匹配。前者靠脚本里的第二轮重试就能解决,后者要回打包机上看一眼apt-cache policy libc6-dev确认版本。排查依赖不满足时,先在两轮脚本跑完后看install.log尾部:如果剩余报错指向的包在目录里不存在,就回联网机补下这个包再重新打包;如果存在却仍报不满足,基本是版本冲突,换用同版本源重新下载一次。
4.2 现象:gcc 升级后为啥还是旧版本,update-alternatives 没入戏
系统里明明装了新版本,gcc --version却还是打出老版本,这是麒麟升级 gcc 和 Ubuntu 切换版本时最经典的坑。/usr/bin/gcc在 Debian 系里通常是指向/etc/alternatives/gcc的软链接,而 alternatives 机制默认指到最早安装的那个版本。你离线装完新 gcc,只是多了一个gcc-12二进制,并没有让默认软链接改指向。
先查一下实际状态:
ls -l /usr/bin/gcc*如果是软链接,用 alternatives 把它切过去:
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 120 update-alternatives --config gcc然后gcc --version就会变成新版本。不处理 alternatives,脚本最后那句“安装成功”会给的是个错误版本的错觉,所以我脚本里的验证还会补一条gcc -v 2>&1 | grep 'gcc version'打印真实版本号,防止被软链接骗过去。
4.3 现象:gcc 能运行,但编译时找不到 stdio.h
gcc hello.c报fatal error: stdio.h: No such file or directory。gcc 能执行说明编译器本体装好了,但标准头文件没到位。Ubuntu 上这个头文件在 libc6-dev 包里,而 libc6-dev 是 dpkg 在安装 build-essential 元包时才拉取的,你离线打包时如果只写了 gcc 而漏了 build-essential,就很容易出现这种半截状态。
解决方案是回打包机执行apt-get install --reinstall --download-only libc6-dev linux-libc-dev libstdc++-12-dev,把补齐的 deb 加到原来的离线目录,再执行一次安装脚本。RPM 系对应的是 glibc-devel 和 libstdc++-devel,用dnf download --resolve一次带全。装完以后,可以用gcc -print-file-name=include确认头文件搜索路径是否存在,比凭空猜测快得多。
4.4 现象:dpkg 报 architecture 不匹配
报错形如package architecture (amd64) does not match system (arm64)。原因在打包阶段就注定了:准备包用的联网机器架构与目标机不一致,比如在一台 x86 的云主机上打包,然后把 deb 拷到了 ARM 架构的内网机器。离线包不像在线仓库,apt 不会自动帮你换架构,装一个不匹配的 deb 直接白给。
防这个问题的做法是打包机开工前先跟目标机核对架构:
uname -m dpkg --print-architecture 2>/dev/null || rpm --print-architecture 2>/dev/null如果两边输出一致再继续。某些麒麟版本的架构命名可能带 vendor 后缀,以uname -m为准最稳妥。在安装脚本开头加一道校验也值得做,省得现场装完才发现整套白搭。
4.5 现象:安装中断后再跑,提示“另一个进程正在使用 dpkg”或“请先修复 dpkg”
现场最容易发生的连锁事故:装到一半有人直接关了终端或拔了电源,再次跑 dpkg 时系统报dpkg was interrupted, you must manually run 'sudo dpkg --configure -a'。这是 dpkg 的锁与状态记录没退场,不是软件包损坏。
先看锁是谁占着:
sudo fuser -v /var/lib/dpkg/lock如果显示的是你自己的安装进程且卡死了,先结束掉;如果只是一个残留锁文件,直接清掉再修复。修复命令在离线环境要防止它外网拉包,可以把源临时指向空:
sudo dpkg --configure -a sudo apt-get -o Dir::Etc::sourcelist=/dev/null -o Dir::Etc::sourceparts=- -f install -y两个-o参数是在告诉 apt“不要用任何远程源”,只做本地修复。这一步做完,再回到安装脚本重新跑。
5. 装完不是终点:用验证与版本固定把离线环境固化下来
安装脚本最后那句校验只证明了 gcc 存在,不代表工具链能真正编译。到现场我最习惯做的第一件事,是写一个最小 C 程序走通“编译到运行”的完整链路:
printf 'int main(){return 0;}' > /tmp/c_test.c gcc /tmp/c_test.c -o /tmp/c_test && /tmp/c_test echo $? # 输出 0 说明编译和运行都正常如果项目里还用到 C++,额外测一下g++ -std=c++17编译并链接-lstdc++。这一条命令能同时暴露头文件缺失、动态库缺失、链接路径错误三类问题,比单纯gcc --version可靠得多。
版本固定是第二个习惯。离线装完的机器以后还会接入内网源,apt 升级时可能把 gcc 带到新版本,而离线脚本对应的依赖链是旧版本的。在内网策略允许的领域,我会把关键包 hold 住:
apt-mark hold gcc g++ libc6-dev # rpm 系用 yum versionlockhold 之后点升级时这些包会留在当前版本,不会把离线安装的成品变成一个“随时可能断链”的不稳定状态。如果你临时要试新版本,记得先apt-mark unhold再装,否则包管理器会一直拒绝动它们。
如果内网里有多台机器要做同样的事,脚本的升级玩法是:把gcc-offline目录放到内网一台主机的 HTTP 或 NFS 共享里,然后在目标机上用curl拉取并执行,这样不需要来回插 U 盘。目录里的 deb 与脚本不变,执行行为就不会偏差,这也是脚本化离线安装真正的价值——它不是解决一台机器,而是让“没有任何外网的机器也能稳定复现同一套构建环境”这件事变得机械可重复。
我自己的教训是:离线安装 GCC,最重要的从来不是安装包本身,而是把“下载依赖、校验完整、脚本安装、版本固定”这四个环节固化下来,缺一步,下次遇到同样环境还得重新踩一遍。希望帮到你。
本文还有配套的精品资源,点击获取