ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux软件安装全解析:从包管理器到源码编译与依赖清理

Linux软件安装全解析:从包管理器到源码编译与依赖清理 1. 安装的本质不是“装”文件布局、依赖关系与权限模型很多人第一次接触Linux拿到的第一台云服务器第一件事就是装环境、装软件。按着网上的教程敲apt install nginx界面刷刷一顿输出然后浏览器一开欢迎页出来了就觉得自己“会”了。直到某天你装了一个新程序它把旧程序的库文件顶掉了另一个程序紧接着崩了你才发现自己压根没搞懂Linux的安装是怎么回事。在Windows上装软件本质上是“双击一个巨大的自解压包它把几千个小文件撒到Program Files、System32、注册表里然后把快捷方式丢到桌面”。在Linux上安装的本质完全不同——它是一套把文件按照约定放进系统目录、同时把依赖关系登记清楚、并赋予程序可执行权限的体系。你理解了这一层再去看任何安装方式都会有种“原来如此”的通透感。1.1 Linux程序在系统里留下的“痕迹”一个Linux程序安装完成后通常会把这几类东西放进系统文件类型典型目录说明可执行文件/usr/bin、/usr/local/bin你在Shell里敲命令时系统按PATH变量去这些目录里找程序库文件/usr/lib、/usr/lib/x86_64-linux-gnu程序运行依赖的.so动态链接库类似Windows的DLL配置文件/etc全局配置通常放这里比如nginx的/etc/nginx/nginx.conf数据文件/var/lib、/opt程序运行产生的持久化数据比如MySQL的数据库文件日志/var/log程序运行记录服务脚本/etc/systemd/system、/lib/systemd/system注册为系统服务支持开机自启、systemctl start/stop也就是说“安装一个程序”翻译成Linux的动作就是“把这几个文件分别放到对应的目录然后让系统知道它叫什么、怎么启动、依赖谁”。其余的全是细节。我第一次意识到这件事是自己写了一个小工具想装到系统里用。当时直接把编译好的二进制文件拖到了/usr/bin发现敲命令报“command not found”——折腾半天才发现光有文件还不够还得有可执行权限chmod x而且PATH里得包含这个目录。Windows下双击就能跑的程序在Linux下被拆成了“文件在不在”“能不能执行”“能不能被找到”三个独立的问题。1.2 动态链接安装最隐蔽的“隐形依赖”新手最容易踩的坑不是文件找不到而是“程序装了一运行就报错”。常见的错长这样error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory这行报错翻译过来是程序本体装好了但它运行时需要一个叫libssl.so.1.1的动态链接库系统里没有。动态链接是Linux程序运行的基本方式——程序在编译时并不把所有的库代码打包进自己体内而是运行时才去系统指定的路径找.so文件来加载。好处是节省磁盘、多个程序共享同一份库坏处是程序A依赖库的1.1版本程序B依赖1.0版本你升级了库A可能就崩了。这就是所谓“依赖地狱”的雏形。理解了这个机制你就能明白为什么下面要讲的软件包管理器这么重要——它不只是帮你把主程序装上还会自动把程序依赖的库、数据文件、配置模板一并装好并且记录依赖关系升级时告诉你“升级这个包会连带影响哪些其他包”。手动装程序时这些全靠你自己盯着盯漏一个后面全崩。2. 包管理器才是日常主力apt/dnf背后的依赖解析逻辑如果你只在Linux上装过三四个软件可能感受不到包管理器的价值。但当你需要装一个带几十个依赖的桌面环境或者部署一套LNMP组合时你会真心感谢那个帮你把依赖全部理顺的工具。Linux的包管理器分两大生态Debian系Ubuntu、Debian、Deepin、麒麟用aptdpkgRed Hat 系CentOS、RHEL、Rocky、Fedora用dnf/yumrpm。命令不一样但思路高度一致包管理器维护了一个本地数据库记录系统里装了哪些包、每个包的版本、彼此之间的依赖关系安装时自动解析依赖从仓库里按需拉取并安装额外的依赖包。2.1 apt安装的完整链路不只是“下载解压”那么简单你现在敲一条sudo apt install nginx背后发生的其实是这么一串事apt读取/etc/apt/sources.list和/etc/apt/sources.list.d/下的软件源配置这些配置告诉它“去哪找软件包”。它会先更新本地包的索引如果本地缓存的索引过期了这个索引相当于仓库里所有软件包信息的“目录册”。解析nginx的依赖关系算出需要哪些额外的包比如libgd、libssl之类的形成一个待安装清单。下载所有包到本地缓存目录/var/cache/apt/archives/。用dpkg逐个解包、安装、执行安装后脚本。更新本地数据库让系统“知道”nginx这个包已经装了、装的是哪个版本。偏Debian系底层的dpkg可以类比为“解包安装器”它只知道把.deb文件解开、文件归位、记录数据库apt则是在这之上做依赖解析和仓库管理的“调度员”。你把这两者分清了今后遇到问题了才能判断是哪一环出的错——依赖没解析好锅多半在apt包本身安装脚本报错问题出在dpkg。Red Hat 系的dnf和rpm的关系也一样。rpm -ivh xxx.rpm是手动安装一个包它会傻乎乎地装缺依赖就直接报错退出dnf install xxx则会自动把依赖也装齐。所以日常使用永远优先用dnf/apt手动rpm/dpkg只在特殊场景比如下载了离线安装包才碰。2.2 高频包管理操作安装、卸载、锁定版本、查依赖我把日常用得最频繁的一批命令整理在这里方便直接抄目的Debian/Ubuntu系Red Hat系更新仓库索引sudo apt updatesudo dnf makecache升级所有可升级的包sudo apt upgradesudo dnf upgrade安装指定包sudo apt install 包名sudo dnf install 包名卸载但保留配置sudo apt remove 包名sudo dnf remove 包名卸载并清掉配置文件sudo apt purge 包名sudo dnf remove 包名搜索软件包apt search 关键词dnf search 关键词查看某个包的信息apt show 包名dnf info 包名查看某文件属于哪个包dpkg -S /路径/文件rpm -qf /路径/文件查看已安装包的依赖apt depends 包名dnf repoquery --requires 包名列出已安装的包dpkg -lrpm -qa这些命令里我特别想提醒两个一是apt remove和apt purge的差别。remove只删程序本体配置文件留在/etc里purge连配置文件一起删。如果你卸载一个软件后打算重装一个干净版本必须用purge否则旧的配置会残留下来新装的程序照样读旧配置你以为是新版的行为其实是旧配置在作祟。这个坑我踩过不止一次最典型的就是MySQL重装后密码死活不对最后发现是残留的配置文件覆盖了新装的默认配置。二是apt upgrade和apt dist-upgrade的区别。老教程里常看到dist-upgrade它会在升级包的同时处理依赖变化甚至可能因此删除一些旧包普通的upgrade只升级不额外解决需要变更依赖关系的包。现在的主流Debian/Ubuntu版本里apt upgrade已经足够应对日常升级dist-upgrade主要用在跨大版本升级比如Ubuntu 22.04升到24.04时普通用户别乱敲。2.3 软件源配置换源之前先搞懂你在换什么“Linux装软件慢”——这大概是被吐槽最多的问题。很多教程直接让你把/etc/apt/sources.list里的地址换成国内镜像源然后apt update一下就快了。但我要多说一句换源的本质是让包管理器从一个离你更近、速度更快的镜像仓库下载文件而不是从官方源绕半个地球去取。改源的时候有两点容易被忽视第一点是版本代号要对。Debian系软件源配置里通常带版本代号比如Ubuntu 24.04的代号是noble。如果你照抄了网上别人22.04的源配置apt update时大概率会报错或者拉到一堆不兼容的包版本。正确的做法是先用lsb_release -a看自己的系统代号再去对应的镜像站查这个代号支持的源列表。第二点是安全源和更新源都要配置。Ubuntu的源文件里除了main、universe这些软件包组件还包含security和updates。把它们全注释掉确实能加速更新但代价是系统安全补丁也断了。我的建议是只替换国内主干源security相关的更新保留官方地址或者用镜像站提供的完整配置块别为了快把安全更新牺牲了。3. 源码编译configure/make/install全链路动手实录如果说包管理器是“别人帮你把菜做好端上来”那源码编译就是“买回来食材自己洗菜切菜下锅”。前者快、省事、标准但你没得选——包管理器给什么版本你就用什么版本。后者慢、繁琐、容易出问题但你能干很多前者干不了的事。哪些场景需要源码编译我碰到过的大概就这几种官方仓库里的版本太旧你需要新特性而第三方源又不提供新版本。你需要对默认编译参数做裁减比如去掉某个不需要的模块或者加上某个默认没开的特性。你用的CPU架构比较特殊仓库里没有预编译好的包。你想装私有软件人家只发布源码。3.1 configure阶段它在干什么为什么总报缺东西以经典的nginx为例源码编译三连是这个./configure --prefix/usr/local/nginx --with-http_ssl_module make sudo make install./configure这一步劝退了无数新手。很多人看到“configure: error: the HTTP rewrite module requires the PCRE library”就崩溃了其实这个报错非常好理解——你在告诉nginx编译时我要带rewrite模块而这个模块依赖PCRE库configure检查发现系统里没有PCRE的开发文件于是拒绝继续。看到这类报错思路就一条缺哪个库就去装哪个库的“开发版”。比如报错说缺PCREDebian系就装libpcre3-devRed Hat系就装pcre-devel。为什么必须是“开发版”因为编译时需要的是头文件.h文件和对应的.so链接文件而不仅仅是运行时的库。这个知识点很关键——很多新手看到报错缺库去apt install pcre装完再 configure 还是报同一个错就是因为装的是运行时库开发头文件没装上。如果拿捏不准到底要装什么包一个实用技巧是把报错里的大写库名截出来去包管理器里搜比如apt search pcre看列表里带-dev后缀的那个装它。Debian系和Red Hat系的开发包命名规律很统一——多数就是“库名-dev/-devel”。configure还有一个用途是定制安装路径。--prefix/usr/local/nginx表示装到/usr/local/nginx不填的话默认装在/usr/local好处是你的软件和系统包管理器的文件互不冲突。我在生产环境里编译装软件几乎都会指定--prefix这样卸载的时候直接把整个目录删掉基本不会误伤其他程序。3.2 make 和 make install编译到底在忙什么configure通过之后目录里会生成一份Makefile—— 这就是编译的“施工图纸”。它记录了哪些源文件要一起编译、用哪些编译参数、最终生成什么二进制、安装时文件分别复制到哪里。接下来执行make它干的活是把源码变成可运行的二进制。这个过程可能很快也可能旷日持久我编译过一次Chromium整整跑了三小时。如果中途报错通常是你缺了某个库的头文件或者编译器版本太老不支持新的语法。前者用上一步的方法补齐依赖再重新make后者多半得查一下软件要求的编译器版本。make成功之后生成的可执行文件还躺在源码目录里你可以在当前目录用./nginx这种方式临时跑。真正把文件安装到系统目录是sudo make install这一步。它严格按照Makefile里的规则把编译好的二进制复制到--prefix指定目录把配置文件放到对应位置可能还会创建日志目录和数据目录。我一直建议新手把编译安装的三步拆开执行而不是教程里常见的./configure make make install一把梭。为什么因为如果 install 阶段因为权限问题报错了前面已编译好的二进制文件其实已经生成了你只需要sudo make install重新装一遍就行完全不用从头编译。一条命令连起来跑每次报错都得从头定位没必要。3.3 编译装完程序却敲不了命令PATH和动态库的坑源码编译安装最容易出的一个“装完但用不了”的坑——指令找不到。比如我上面指定--prefix/usr/local/nginxnginx装好后你在任何目录敲nginx系统都提示command not found。原因在于Shell是依靠环境变量PATH里的目录列表去找命令的。nginx装在了/usr/local/nginx/sbin/nginx而PATH里默认只包含/usr/local/bin、/usr/bin、/bin这类目录自然找不到它。解决方式有三种一是用完整路径执行/usr/local/nginx/sbin/nginx二是把它的目录加入PATH改/etc/profile或~/.bashrc三是在/usr/bin下做一个软链接ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx。我个人推荐第三种简单直接而且对系统影响最小。比PATH更隐蔽的是动态库路径问题。有些软件装到自定义lib目录后运行时报错找不到.so文件比如./app: error while loading shared libraries: libfoo.so.1: cannot open shared object file解决办法是让系统知道去哪找这个库。把自定义目录写进/etc/ld.so.conf.d/下新建的一个.conf文件里然后执行sudo ldconfig刷新动态链接器缓存就好。如果你只是给当前用户临时测试也可以设置export LD_LIBRARY_PATH/path/to/lib:$LD_LIBRARY_PATH但注意这只对当前Shell生效重启就没了。4. 三种高频业务软件安装实战MySQL、Docker、Python多版本理论讲了一堆总得落到具体软件上才有体感。我挑三个最有代表性的场景说说各自安装时真正的坑在哪。4.1 MySQLapt直装未必是最优选很多人装MySQL上来就sudo apt install mysql-server装完还很顺手。但如果你的业务要跑很多年我建议慎重用这种默认源版本。原因有两个第一是版本落后。Ubuntu默认源里的MySQL版本往往不是最新甚至不是长期支持版后面出问题查资料时版本对不上会非常痛苦。第二是默认配置和你要的往往不一样。比如默认的datadir在系统盘你数据盘在/data后面迁数据库又得折腾。生产环境我更推荐用MySQL官方提供的APT仓库。步骤大概是去官网下载对应的仓库配置文件.deb包安装它它会往/etc/apt/sources.list.d/写入官方源然后sudo apt update sudo apt install mysql-server这样得到的MySQL版本新、更新及时而且官方仓库里自带一堆mysql-server-8.0这类指定版本包选择余地大。装完后有个高频坑MySQL 8.0 默认的认证插件是caching_sha2_password有些老客户端比如PHP 7.2之前的mysqlnd不认这个连不上库。解决方式是在MySQL里给对应账号指定mysql_native_passwordALTER USER appuser% IDENTIFIED WITH mysql_native_password BY your_password;这个坑90%的“装好了但程序连不上”都是它引起的。4.2 Docker安装脚本里的隐藏知识点Docker的安装算是比较省心的官方提供了安装脚本curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh但我并不建议你直接跑这个脚本就跑路原因在于脚本默认安装的是Docker CE的stable版本但它不会帮你做用户组授权、也不会帮你配置镜像加速。装完之后最重要的一件事是把当前用户加进docker组否则每次执行docker命令都要加sudo烦得你想摔键盘sudo usermod -aG docker $USER改完用户组需要重新登录或执行newgrp docker才生效。另外如果你在国内服务器上装了Docker不做镜像加速的话拉个镜像能等到天荒地老还时不时超时中断。配置方式是在/etc/docker/daemon.json里写镜像源地址{ registry-mirrors: [https://docker.mirrors.ustc.edu.cn] }然后执行sudo systemctl restart docker重启服务。这里要注意daemon.json用了JSON格式逗号、引号缺一不可写错了Docker服务直接起不来。改完可以用sudo dockerd --validate-config检查语法新版本才支持或者daemon.json改完后马上systemctl start docker看是否报错。4.3 Python多版本为什么直接改系统Python是个坏主意“用Linux装Python”如果你指的是想装最新的Python我强烈建议你不要去动系统自带的那个Python。Ubuntu/Debian系统里的python3是系统底层的“基础设施”很多系统工具比如apt的某些插件、gnome-terminal都依赖它。你要是手贱把它升级到新版本或者卸载了某些系统组件会直接罢工那种“所有东西看起来都正常但是有个小工具突然不好使了”的灵异事件十有八九是这个引起的。正确的多版本方案是pyenv。它的核心思路是把不同版本的Python编译好放在~/.pyenv/versions/目录下然后通过环境变量和shims机制让你当前Shell里python指向你指定的那个版本。安装pyenv的方式官方仓库有脚本一条命令搞定。常用命令pyenv install 3.12.1 # 安装指定版本 pyenv global 3.12.1 # 设置全局默认版本 pyenv local 3.12.1 # 在当前目录固定一个版本使用pyenv期间我唯一要提醒你的坑是编译Python时系统必须装了对应版本的开发依赖否则一半概率编译到中途直接失败。Debian系先装这些sudo apt install build-essential libssl-dev zlib1g-dev libncurses5-dev libreadline-dev libsqlite3-dev libffi-dev libbz2-dev至于那些说“装Python很简单不就是下载源码./configure make”的人他们没告诉你的是./configure --enable-optimizations这个参数如果不开编译出来的Python性能会差一截而开了它编译时间会翻倍。取舍看你自己我通常是开着的。5. 装完之后的事服务注册、版本切换与依赖冲突处理安装只是“万里长征第一步”。程序装上之后马上要面对的是“怎么让它以后一直跑、开机自启、出错自动拉起”。这就是Linux的“程序管理”里最绕不开的一块——systemd。5.1 systemd服务文件把程序交给系统托管现在几乎所有主流的Linux发行版都在用systemd管理服务和进程。你写一个.service文件放到/etc/systemd/system/系统就能把你的程序当成一个“服务”来管支持开机自启、异常重启、查看日志。一个最简单的服务文件长这样[Unit] DescriptionMy Custom Service Afternetwork.target [Service] ExecStart/usr/local/bin/myapp Restartalways Userwww-data Groupwww-data [Install] WantedBymulti-user.target每个字段的含义不复杂但有几个点得注意ExecStart必须写绝对路径写相对路径系统也接受但绝对路径更稳。User和Group指定以哪个用户身份运行这一栏非常关键——如果你写死root那服务一旦被人利用就是root权限在系统里胡作非为多数服务用专门的低权限用户跑才是安全的。Restartalways是让进程崩了自动拉起来生产环境基本必配。写好文件后依次执行sudo systemctl daemon-reload # 重新加载服务文件 sudo systemctl enable myapp # 设置开机自启 sudo systemctl start myapp # 立即启动daemon-reload很多人会漏掉。你新增或修改了.service文件后不执行这一句systemd用的还是旧的内容哪怕你改了文件它也不认。改了配置文件反复重启都无效别忘先reload。还有一个调试时的实用技巧systemctl status myapp输出内容比较少想看完整日志用journalctl -u myapp -f它会把服务的所有stdout/stderr输出都给你实时打印出来。定位“服务起来了但没起来”这种问题时这个命令比什么都管用。5.2 多版本共存与优先级切换update-alternatives“程序管理”里另一个高频需求是版本切换。比如你的服务器上可能要在一段时间内同时支持OpenJDK 11和OpenJDK 17或者你装了多个Python版本。这时候就要用到update-alternatives—— 它是Debian系的一套“符号链接管理器”。它的工作原理其实很朴素系统里维护一套多版本注册表每个程序比如java在不同版本间切换时/usr/bin/java这个链接指向谁由update-alternatives来管理。注册一个版本sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-11/bin/java 1100 sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-17/bin/java 1700后面那个数字是优先级越大表示默认越优先。切换版本sudo update-alternatives --config java执行后面这条系统会列出所有已注册的java版本你输入数字回车就切换好了。同样道理也适用于python、vim这类有多版本共存的命令。5.3 依赖冲突的排查思路别再“暴力重装”装了管理程序之后最让人头大的是依赖冲突。典型场景程序A依赖libssl1.1程序B依赖libssl3你的系统里只有一个libssl包装B的时候把libssl1.1升级成libssl3A就罢工了。碰到这类问题第一反应不要是“那我强行装回去”那样大概率会把系统的包数据库搞坏。正确排查链路是这样的先看报错确认到底是哪个包、哪个文件冲突apt install xxx报错时它通常会告诉你“将安装以下软件包”或者“以下软件包有未满足的依赖关系”。问系统这个依赖现在是什么状态dpkg -l | grep libssl看看装的版本是哪个。查这个包被哪些包依赖apt rdepends libssl3它会列出所有依赖libssl3的包。判断冲突范围如果只是A和B不能共存看有没有替代方案——比如A的某个新版本也兼容libssl3那升级A就行如果没有可能得考虑用“定期单独维护”的容器来运行A。在Debian系上如果你已经破坏了包数据库还有一个“后悔药”sudo apt --fix-broken install它尝试把处于异常状态的依赖关系修复到能继续工作的状态。很多乱七八糟的问题这一条命令能兜住底。6. 卸载与清理把安装留下的痕迹打扫干净聊完了装和管理最后聊卸载。别小看这一步多少人的服务器卡、磁盘满、服务冲突追溯下来都是“以前卸载程序没卸干净”的债。6.1 三种卸载方式的区别卸载方式效果适用场景sudo apt remove 包名删程序文件保留配置文件暂时不用以后可能重装sudo apt purge 包名删程序文件和配置文件彻底不想要了手动删--prefix目录连整个自定义安装目录一起删源码编译安装的程序我第一次在服务器上卸MySQL用的是apt remove而后重装时发现数据库配置还是旧的跑都跑不起来排障了半天才意识到是配置文件残留。踩过这次坑之后我养成了一个习惯凡是准备彻底告别一个应用一律用purge凡是用源码--prefix方式装的程序直接删目录再清掉/usr/local/bin里对应的软链接。6.2 源码编译程序的卸载为什么没有make uninstall前面提到源码编译安装的程序我推荐指定--prefix卸载时“删目录就行”。但你可能听说过还有一个sudo make uninstall的说法。现实中这个命令很多时候是失效的——因为部分软件的Makefile压根没有实现uninstall目标你敲了会看到make: *** No rule to make target uninstall. Stop.。所以我的经验是编译安装前把源码包留着源码目录里会有一个install_manifest.txt文件部分软件会生成里面记录了安装时复制了哪些文件到哪些路径。卸载的时候照着这个清单一个一个删才算真正干净。如果没有这个文件那就只能按--prefix目录整个删掉再手动清理链接和PATH了。6.3 残留检查没有卸载干净的三步自查清理完一个程序后别急着走花两分钟自查用which 程序名看命令还在不在如果还在说明有软链接或者某处残留的可执行文件。用dpkg -l | grep 程序名或者rpm -qa | grep 程序名看包管理器数据库里还有没有记录。检查/etc下有没有以它命名的配置文件残留数据目录比如/var/lib/mysql是否清理。最后分享我踩过最深的一个坑有一次服务器磁盘满了排查半天发现是一个已经被我“卸载”的应用在/var/log下留下了巨型日志文件进程被终止了但日志文件还在疯狂增长——原来卸载程序并不会自动帮你停掉还在跑的进程。所以卸载前一定先systemctl stop 服务名或者pkill 进程名否则你自己以为它没了其实它在后台还活着一直往磁盘里写东西。管理Linux程序这件事说到底是一条“文件放置——依赖登记——运行托管——卸载清理”的完整链路。你顺着这条链路把每个环节都摸透了再去看网上任何安装教程都不会再是你抄我抄的盲人摸象而是能自己判断哪个步骤是合理的、哪个步骤藏着坑。遇到问题时的排查速度也会远快于那些只会背命令的人。
返回列表