先把场景交代清楚:某单位内网服务器要编译程序,管理员上去发现系统没装编译器,敲yum install gcc gcc-c++直接报“无法连接网络”,然后全网找 gcc 的 rpm 包,拷过去安装又开始连环报依赖错误。这个场景在 CentOS 7 的离线环境里太常见了。gcc 不是单个包,是一整条工具链,它依赖 cpp、binutils、glibc-devel、libgcc、libstdc++-devel 这些包,而这些包又各自挂着一堆依赖。如果只是随便下一个 gcc rpm 往机器上扔,几乎不可能装成功。
这篇文章就把我实际操作过的离线安装路径完整写出来:先在联网机器上把需要的 rpm 包一次性抓全,再拷贝到离线机器上完成安装,还有本地 yum 源建法,以及我踩过的几个典型坑。
1. GCC离线安装的依赖难点:为什么“只带一个包”行不通
1.1 GCC的真实依赖树(以CentOS 7的gcc-4.8.5为例)
CentOS 7 默认的是 GCC 4.8.5,这个版本虽然不算新,但系统里大量原生软件包都基于它编译。它的 gcc rpm 包本身只有几 MB,但安装时要求系统里必须存在一串依赖包,缺一个就装不成。
我拆过这个依赖关系,大致是这样一条链:
gcc主包:依赖cpp、binutils、libgcc、glibc-devel、libmpc、mpfrgcc-c++包:在gcc的基础上,额外依赖libstdc++-devel,而libstdc++-devel又要求libstdc++glibc-devel包:依赖glibc-headers、glibc,属于 C 标准库的头文件层glibc-headers包:依赖kernel-headers,也就是 Linux 内核头文件mpfr、libmpc、isl这几个包:它们是 GCC 做表达式运算和优化时的数学库基础
光看这一层就明白,离线安装 gcc 必须带走的不只是 gcc 本体,而是上面这一整条链。很多新手栽的第一个跟头,就是在某个 rpm 下载站只下了gcc-4.8.5-44.el7.x86_64.rpm,传到离线机上执行rpm -ivh gcc*.rpm,立刻弹出一串:
error: Failed dependencies: libmpc.so.3()(64bit) is needed by gcc-4.8.5-44.el7.x86_64 cpp = 4.8.5-44.el7 is needed by gcc-4.8.5-44.el7.x86_64这不是 rpm 命令有问题,而是缺的依赖根本没出现在安装事务里。
1.2 两种离线准备思路对比:全量依赖 vs 最小裁剪
面对依赖链,有人会手动一个个去找缺失的包,缺一个下一人,下完报错再继续,这种方法就是“最小裁剪”——只拿目标包和它当前缺的依赖。它的优点是文件体积小,缺点是过程很痛苦,因为你不知道系统本身已经安装了什么、缺少什么,容易陷入“装一个缺一个”的循环。
我更推荐第二种思路:全量依赖收集。把 gcc、gcc-c++ 以及它们的完整依赖树全部下载下来,打包传到离线机,一次性交给 rpm 处理。虽然包多了点,但容错性和可复用性都高得多。下面这张表对比了两者的差别:
| 对比项 | 最小裁剪 | 全量依赖 |
|---|---|---|
| 包数量 | 少,通常十几个 | 通常二三十个 |
| 抓取难度 | 手工逐个找,容易漏 | 一条命令自动解决 |
| 安装成功率 | 依赖顺序不对就失败 | 一次事务安装基本成功 |
| 后续扩展 | 再装别的包还得重来 | 可以顺便当本地 yum 源 |
| 适用场景 | 只装一次、网络传输极慢 | 多台机器都要装、内网维护 |
后面所有命令和步骤,都是围绕“全量依赖”这个思路展开的。
2. 在联网机器上抓取离线安装包
2.1 准备条件:同版本同架构的联网主机
要在离线机上装 gcc,第一步不是去 rpm 下载站乱翻,而是找一台能联网、并且和离线机“同源”的 CentOS 7 主机来做抓包机。同源指的是:
- 系统大版本一致,比如都是 CentOS 7.x
- CPU 架构一致,绝大多数是 x86_64,可以用
uname -m确认 - yum 源里的 base 仓库可用,gcc 系列包在 CentOS 7 的 base 源里有
抓包机用什么版本不会影响太大,但建议尽量和离线机的补丁级别接近。举个例子:联网机如果已经升到 7.9,离线机还是 7.2,那么下载下来的 gcc 或 glibc 可能是 7.9 的高版本,装到 7.2 上会出现依赖版本冲突,甚至牵扯到 glibc 升级,这类问题后面单独讲。
先执行两步确认:
cat /etc/redhat-release uname -m输出类似CentOS Linux release 7.9.2009 (Core)和x86_64,就说明条件满足。
2.2 核心命令:yumdownloader 抓取指定包及依赖
CentOS 7 上最稳定的抓包工具是yumdownloader,属于yum-utils包。先安装它:
yum install -y yum-utils然后在抓包机上建一个目录,比如/root/gcc-rpms,执行:
mkdir -p /root/gcc-rpms yumdownloader --resolve --destdir=/root/gcc-rpms gcc gcc-c++ glibc-devel libstdc++-devel解释一下这条命令的意图:
--resolve:让 yumdownloader 自动解析依赖,并把所有需要的 rpm 一起下载,不用自己手工一个一个找--destdir:指定下载文件保存目录- 后面的四个包名:
gcc是 C 编译器本体,gcc-c++是 C++ 编译器,glibc-devel是为了确保 C 头文件和链接库齐全,libstdc++-devel是 C++ 标准库的开发文件
命令执行完,目录下会多出一堆 rpm。我实际跑下来的结果大致包括:
binutils-2.27-44.base.el7.x86_64.rpm cpp-4.8.5-44.el7.x86_64.rpm gcc-4.8.5-44.el7.x86_64.rpm gcc-c++-4.8.5-44.el7.x86_64.rpm glibc-devel-2.17-326.el7_9.x86_64.rpm glibc-headers-2.17-326.el7_9.x86_64.rpm kernel-headers-3.10.0-1160.el7.x86_64.rpm libgcc-4.8.5-44.el7.x86_64.rpm libmpc-1.0.1-3.el7.x86_64.rpm libstdc++-4.8.5-44.el7.x86_64.rpm libstdc++-devel-4.8.5-44.el7.x86_64.rpm mpfr-3.1.1-4.el7.x86_64.rpm isl-0.14-2.el7.x86_64.rpm2.3 另一种抓法:yum downloadonly 插件
如果你不想记yumdownloader的参数,还有一条同样好用的路:yum-plugin-downloadonly插件。先安装插件:
yum install -y yum-plugin-downloadonly然后执行:
yum install --downloadonly --downloaddir=/root/gcc-rpms gcc gcc-c++这条命令的效果是“模拟安装 gcc 和 gcc-c++,但只下载 rpm 包,不真正执行安装”。它在 yum 解析依赖时的逻辑和真实安装完全一致,所以下载出来的包比较可信。要注意的是,它同样只会下载“当前系统里没有安装的”依赖,如果抓包机已经装过 gcc,它会漏掉不少包。
我在实际项目里通常两条命令搭配。第一次抓包用yumdownloader --resolve,遇到“某依赖已在抓包机存在,但没有下载”的情况时,再用yum install --downloadonly补一轮。两个目录合并后,包就非常齐全。
2.4 已安装依赖为什么会漏抓,以及怎么规避
这是离线抓包最容易踩的暗坑。假设你在抓包机上之前已经装过 gcc,此时跑yumdownloader --resolve --destdir=/root/gcc-rpms gcc,yum 会觉得“gcc 已安装,依赖也都满足”,于是只下载一个 gcc 包,甚至什么都不下载。这样拷到离线机上必然缺一堆依赖。
规避办法有三种:
- 用一台干净的最小化安装抓包机,什么开发工具都不装,再执行下载命令
- 把抓包机上已有的 gcc、gcc-c++、libstdc++-devel 等包先卸载,再执行下载
- 在容器或者虚拟机里做一次干净环境下载,然后把下载目录整个拷出来
我自己的习惯是维护一台专门干这个事的“工具机”,平时只装基础系统,需要抓哪个环境的包,就在那个环境对应的干净系统里抓,避免依赖漏网。
2.5 抓完包的自检清单
拷贝之前,先花两分钟确认一遍包是否齐全:
- 用
ls /root/gcc-rpms/*.rpm | wc -l看包数量,一般二三十个属于正常范围 - 检查是否存在
gcc-*.rpm和gcc-c++-*.rpm,这两个是主包 - 执行
file /root/gcc-rpms/*.rpm | grep -c "x86_64",确认没有下成 i686 或别人平台架构 - 把 rpm 文件列出来看一眼,如果能找到
cpp、binutils、glibc-devel、libstdc++-devel这几类,基本就不会出大问题
最后打个包,方便后续传到离线机器:
tar czf gcc-rpms.tar.gz /root/gcc-rpmsgcc-rpms.tar.gz 就是最终要拷贝到离线机的东西。
3. 离线环境安装:顺序、命令与本地yum源方案
3.1 直接 rpm 安装的正确姿势
把 gcc-rpms.tar.gz 传到离线机上,解压后进入目录:
mkdir -p /home/gcc-rpms tar xzf gcc-rpms.tar.gz -C /home/gcc-rpms cd /home/gcc-rpms/root/gcc-rpms我的习惯是第一步试着用一条命令直接装:
rpm -ivh *.rpm这里的*.rpm会由 shell 展开成全部 rpm 文件名,rpm 会把它们当成同一个事务统一处理。只要包里包含了所有依赖,即使顺序不是严格的“先依赖后依赖方”,rpm 也能正确解析。绝大部分情况下,这一步就能直接成功,输出类似:
Preparing... ################################# [100%] Updating / installing... 1:mpfr-3.1.1-4.el7 ################################# [ 5%] 2:libmpc-1.0.1-3.el7 ################################# [ 10%] ... 11:gcc-4.8.5-44.el7 ################################# [100%]假如报出依赖错误,说明下载时漏了包,或者系统里已经有旧版本软件造成冲突。这时候不要急着加--nodeps,而是先看报错缺什么。比如报cpp = 4.8.5-44.el7 is needed by gcc,就确认一下目录里有没有对应版本的cpp-4.8.5-44.el7;没有的话就回抓包机把这个包补下来。
如果只是想手动按顺序装,推荐这个顺序:
rpm -ivh libgcc*.rpm mpfr*.rpm libmpc*.rpm isl*.rpm rpm -ivh cpp*.rpm binutils*.rpm rpm -ivh kernel-headers*.rpm glibc-headers*.rpm glibc-devel*.rpm rpm -ivh gcc*.rpm rpm -ivh libstdc++*.rpm libstdc++-devel*.rpm gcc-c++*.rpm每组之间用换行隔开,如果下一组依赖上一组,系统会明确提示。
3.2 进阶方案:createrepo 搭建本地 yum 源
如果你不是只装一台,而是要给一批离线机器装 gcc,或者以后还要离线装其他软件,用rpm -ivh *.rpm就不够优雅了。更好的办法是建立本地 yum 源,让这些机器通过yum install正常安装、正常解析依赖。
createrepo 工具的作用,是扫描一个目录里的所有 rpm,生成一份 yum 能识别的元数据,目录里有这些元数据,就能被当作 yum 源使用。这一步在联网机器上做最方便:
yum install -y createrepo createrepo /root/gcc-rpms执行后/root/gcc-rpms目录下会出现一个repodata子目录,这个目录就是关键。把整个/root/gcc-rpms(包括 rpm 包和 repodata)拷贝到离线机,比如放到/srv/local-repo/。
然后在离线机写一个 repo 配置文件:
cat > /etc/yum.repos.d/local-gcc.repo << 'EOF' [local-gcc] name=Local GCC Repository baseurl=file:///srv/local-repo/gcc-rpms enabled=1 gpgcheck=0 EOF执行:
yum clean all yum makecache yum install -y gcc gcc-c++ glibc-devel libstdc++-devel这样 yum 会从本地源里自动选择所有依赖完成安装,跟在线安装的手感一致。需要注意:repo 文件里的baseurl路径必须与你实际存放 rpm 的绝对路径一致,写成file:///srv/local-repo/gcc-rpms而不是file://srv/...,这是初学者最容易写错的地方。
3.3 编译验证与版本检查
安装完成后,先做最基本的验证:
gcc --version which gccgcc --version应该输出gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)这样的信息。然后是编译实测,写一个最简单的 C 程序:
cat > hello.c << 'EOF' #include <stdio.h> int main(void) { printf("gcc offline install OK\n"); return 0; } EOF gcc hello.c -o hello ./hello能打印gcc offline install OK,说明 C 编译器没问题。C++ 也一样试一下:
cat > hello.cpp << 'EOF' #include <iostream> int main() { std::cout << "g++ offline install OK" << std::endl; return 0; } EOF g++ hello.cpp -o hello.cpp.bin ./hello.cpp.bin如果 C 命令成功但 C++ 失败,最常见的原因是libstdc++-devel没装或版本不对,回头检查抓包清单即可。
4. 实际踩坑:依赖冲突、缺包与版本不匹配的排查
4.1 “Failed dependencies”的典型报错与定位
离线安装 gcc 时,见到最多的就是下面这类报错。我列一个常见的对照表:
| 报错信息 | 缺失/冲突原因 | 处理方法 |
|---|---|---|
libmpc.so.3()(64bit) is needed by gcc | 缺 libmpc 包 | 下载 libmpc rpm |
cpp = 4.8.5-44.el7 is needed by gcc | gcc 与小版本 cpp 不匹配 | 下载同版本 cpp |
glibc-devel >= 2.17 is needed by gcc | 缺 glibc-devel | 下载 glibc-devel 及 glibc-headers |
libstdc++-devel = 4.8.5-44.el7 is needed by gcc-c++ | 缺 C++ 标准库开发包 | 下载同版本 libstdc++-devel |
libgomp.so.1()(64bit) is needed by gcc | 缺 libgomp | 下载 libgomp rpm |
遇到失败,先读完整报错,别只看第一行。报错里会明确写出“哪个包需要什么依赖”,把这个依赖对应的 rpm 补进目录再重新安装。最忌讳的是想都不想就rpm -ivh --nodeps gcc*.rpm,这样 gcc 是“装上了”,但运行时找不到头文件、找不到库,随便编译一个 hello world 都会失败,排查起来更加麻烦。
4.2 系统已存在部分包时的混合处理
离线机不是全新系统的情况很常见,尤其是libgcc、libstdc++这种基础包,系统里几乎必然存在旧版本。这时候用rpm -ivh libgcc*.rpm会提示“package libgcc-4.8.5-39.el7 already installed”,无法继续。
正确的做法是升级式安装:
rpm -Uvh libgcc*.rpm libstdc++*.rpm-Uvh会把已存在的旧版本升级为新版本,同时处理新装包。不过升级glibc、libgcc这类最底层的库,一定要有风险意识:如果新版库和系统里旧的应用二进制不兼容,可能出现命令跑不起来的连锁反应。所以在离线机上,我建议只升级 gcc 相关的包,不要顺手把glibc主版本升太高。如果只是gcc -ivh *.rpm整批安装,rpm 会自动把需要升级的包升级掉,这通常没问题,但如果报出和现有包的文件冲突,优先考虑-Uvh。
4.3 系统精简到没有 yum,怎么办
有朋友遇到过更极端的情况:内网机器为了保证安全,只装了最精简的 CentOS,很多工具都没带,yum命令可能因为依赖缺失或 python 相关问题直接不可用。这时候不要慌,rpm 包本身还能用。
检查一下能不能跑:
rpm --version能跑就行。直接按照上面第 3 节的“手动按顺序装”流程,用 rpm 命令完成安装。如果系统连rpm都异常,那说明基础系统本身已经损坏,属于另一个问题,不在本文范围。另外,如果离线机上createrepo也没有,可以考虑一个有 createrepo 的机器生成好repodata目录后一并传过去,这样就能跳过在离线机上安装 createrepo 的步骤。
4.4 小版本不一致带来的隐患
抓包机 CentOS 7.9,离线机 CentOS 7.4,这类“大版本相同、小版本不同”的情况最容易出问题。base 源里的 gcc、glibc 会随系统更新版本,7.9 的 gcc 可能依赖某些比 7.4 更新的底层库。拷过去安装时,rpm 会提示需要升级 glibc、glibc-common 等一揽子基础包。
能装吗?理论上能,只要把需要的全套包都带上。但我要给一个非常明确的建议:不要在离线生产环境里轻易升级 glibc。glibc 是系统几乎所有动态程序的“地基”,升级过程一旦中断,或者新版 glibc 与系统内某些老软件不兼容,后果就是大批命令无法启动。
稳妥的做法是让抓包机尽量和离线机保持同一个小版本,至少相差不要太大。如果离线机确实很旧,最好直接在同样版本的干净系统里构造抓包环境。万不得已必须装高版本 gcc 时,优先考虑使用不会覆盖系统 gcc 的独立工具链方案,而不是强行升级系统自带 gcc。
最后分享一个我自己的习惯:把常用工具链的 rpm 包按系统版本归档保存,比如gcc-4.8.5-offline-rpms-centos7.tar.gz,每装一批机器就验证一次,验证通过后归档。下次再遇到离线装 gcc 的需求,直接拿归档包去装,十分钟搞定,不需要重新联网抓包。这套方法处理的不只是 gcc,其他离线软件也可以复制同样的流程:联网机器抓包、自检、打包、离线机器建本地源、统一安装。熟练掌握一次,内网装机的工作量能省下一大半。