很多人对 Linux 的第一个记忆,都是从终端里敲下sudo apt-get update开始的。教程让你敲,装软件前让你敲,出了问题还是让你敲,可几乎没人告诉你敲完之后屏幕上滚过的那几十行Hit、Get、Ign到底是什么,也没人说过为什么少敲这一步后面就会报一堆莫名其妙的错。它看起来只是个"刷新一下"的动作,实际上是整个 Debian 系包管理体系的入口——索引在这里建立,信任链在这里校验,后面所有 install、upgrade、autoremove 的判断依据都来自这一步的结果。这篇就把这个命令从权限、文件落点、源配置、输出解读到自动化场景完整拆一遍,不管你是刚装完虚拟机的新手,还是天天在服务器上跑定时任务的老手,都能从里面找到几个能直接用的点。
1. 敲下回车之后:apt-get update 到底动了哪些文件
1.1 它一个软件包都不会装
先纠正一个特别普遍的误解:apt-get update和"更新系统"没有半点关系,它连一个.deb文件都不会下载。它只做一件事——把远端软件仓库的包索引拉回本地。
你可以把包索引理解成超市门口的货架清单:上面写着这家"仓库"里有哪些商品(软件包)、每个商品当前是什么版本、它依赖哪些别的商品、真正的商品文件该去哪个地址取。而apt-get install是拿着清单去货架上拿货,apt-get upgrade是对比清单决定哪些货要换新。
所以update完成后,你执行apt-cache search或者apt list --upgradable能看到最新的东西,并不是因为包变了,而是因为你手里的清单变新了。理解这一点之后,很多"为什么我 update 了还是装不上"的问题就迎刃而解——清单更新了,但货架地址本身可能就是错的。
1.2 /var/lib/apt/lists 里躺着什么
索引真正的落盘位置是/var/lib/apt/lists/。这个目录初看会让人一脸懵,里面全是这种超长文件名:
mirrors.aliyun.com_ubuntu_dists_jammy_main_binary-amd64_Packages mirrors.aliyun.com_ubuntu_dists_jammy-updates_main_binary-amd64_Packages mirrors.aliyun.com_ubuntu_dists_jammy_InRelease命名规则其实很朴素:把源地址里的斜杠/全部换成下划线_,再拼上仓库路径。看文件名就能反推出它来自哪个源的哪个部分,排错的时候特别好用——比如你发现某个文件来自一个早就废弃的域名,那说明sources.list里有残留配置没清干净。
同目录下还有一个partial/子目录,这是关键设计。APT 下载索引时先在partial里建临时文件,等整个文件下载完、校验和也对上了,才原子性地移到正式位置。这样做的目的是防止断网或中途被杀进程时,把一个半截的索引覆盖到可用索引上。代价就是断网后partial里会留下一堆垃圾文件,时间久了可能占掉几百兆空间,这是很多人df -h发现根分区莫名其妙变小却找不到原因的元凶之一。
1.3 为什么这一步非 sudo 不可
/var/lib/apt/lists的属主是 root,普通用户没有写权限,而update的核心动作就是往这个目录里写文件,所以必须提权。除此之外,update还会去碰/var/lib/apt/lists/lock这个锁文件,它同样在 root 名下。
这里有个很多人不知道的用法:如果你只想看看这个命令到底会去访问哪些地址,可以用
apt-get update --print-uris它只把待下载的 URI 列表打印出来,不实际写文件,在排查"到底连的是哪个源"时非常直观,比翻配置文件快得多。
还有一种情况是你在容器或者受限环境里,不想动系统目录,可以自己指定索引落点:
apt-get -o Dir::State::Lists=/tmp/mylists update这样索引全写到/tmp/mylists,不影响系统里的那份。虽然日常用得少,但做批量检查或者对比不同源的内容时挺方便。
2. sources.list 才是 update 的真正输入
2.1 一行软件源配置怎么读
update从哪里知道要去哪拉索引?答案在/etc/apt/sources.list和/etc/apt/sources.list.d/里。前者是传统格式,一行一个源,长这样:
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse一行拆成四段来看:开头的deb表示这是一个二进制软件包仓库,如果写deb-src就表示源码仓库;第二段是仓库的根地址;第三段jammy是发行版代号(suite),必须和你系统版本严格对应;第四段开始是仓库分区(component),可以写一个也可以写多个。
方括号里还能塞选项,常见的有三种。arch=amd64限定只取某个架构的包,多架构环境里很有用;signed-by=/usr/share/keyrings/xxx.gpg指定用哪个密钥来验签,现在加第三方源都用这个,apt-key那套已经废弃了;trusted=yes是直接跳过验签,只建议在完全掌控的内网源上使用。
2.2 四个 component 到底有什么区别
新手最常问的是main、restricted、universe、multiverse到底该留哪几个。简单说,main是官方完全支持的自由软件,系统能跑起来靠的就是它;restricted是官方支持的专有软件,主要是显卡驱动、无线网卡固件这类,缺了它可能装不上闭源驱动;universe是社区维护的自由软件,数量巨大但官方不提供安全更新承诺;multiverse是社区维护的非自由软件,因为授权原因不能放进前两个区。
我的习惯是四个全留着。少写一个看起来能让update快几秒,但代价是某天你要装的东西搜不到,然后花半小时怀疑人生。真嫌慢的话,从镜像源的带宽上找原因,比砍 component 划算得多。
除了这四个,jammy-updates、jammy-security、jammy-backports这些也是 suite,它们和基础 suite 是并列关系,各自对应一个独立的索引文件。-security千万别删,那是安全补丁的来源。
2.3 sources.list.d 与加载顺序
除了主配置文件,/etc/apt/sources.list.d/目录下的所有.list文件也会被读取。现在很多教程让你"新建一个文件放进去",原因就是这个目录里的文件是独立管理的,删除某个源只要删掉对应文件,不用去主配置里小心翼翼地注释行。
较新的系统还引入了 deb822 格式,文件后缀是.sources,内容从"一行"变成了"一段":
Types: deb URIs: http://mirrors.aliyun.com/ubuntu/ Suites: jammy jammy-updates jammy-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg可读性确实更好,但两种格式并存的时候容易看漏。我就遇到过主配置里是旧的、.d目录里是新的,同一个源配了两遍,update时同一个地址被访问两次,日志里一堆重复行,排查了半天。
换源之后一定要重新 update,这是硬规则。因为本地索引里记的是旧源的文件名和路径,你以为改完配置就完事了,其实 APT 还在用旧索引做判断,直到下一次 update 才会真正换成新源的数据。
3. 把 update 的输出当成日志来读
3.1 Hit、Get、Ign、Err 四种前缀
update跑完那几十行输出不是装饰,每一行都有明确含义。掌握这四个前缀,八成的问题你能自己看出来。
| 前缀 | 含义 | 是否需要关注 |
|---|---|---|
Hit | 本地索引与远端一致,直接用缓存,没有下载 | 正常,不用管 |
Get | 索引有变化,正在下载,后面跟着大小 | 正常 |
Ign | 主动跳过,通常是翻译文件、增量补丁或架构不匹配 | 大多数情况正常,大量出现要看看 |
Err | 出现错误,这一行必须处理 | 必须关注 |
Hit和Get的区别很能说明问题。你今天刚 update 过,再跑一次满屏Hit,说明缓存生效了,没有浪费流量;而换源之后的第一次 update 基本全是Get,因为本地一个索引文件都没有。
Ign最容易被误当成错误。常见来源是Translation-zh_CN这类翻译文件——你系统语言设成中文,APT 会尝试去拉中文翻译,但很多镜像站没同步这部分,于是就直接跳过。这不影响任何功能,无视即可。另一种Ign是 PDiff 增量更新,镜像站不支持时会回退到全量下载,也是正常行为。
3.2 高频报错的分层排查思路
Err行出现后,先把报错按层次分类,比一条条瞎试效率高得多。我把常见错误归成四类。
第一类是网络解析层。关键字是Could not resolve或Temporary failure in name resolution,意思是域名都解析不出来。先ping一下镜像站域名,再cat /etc/resolv.conf看 DNS 配置。虚拟机刚装完最常见的就是 DNS 没配好,宿主机能上网但虚拟机不行。
第二类是连接层。关键字是Connection timed out、Connection refused。能解析但连不上,可能是端口不通、公司网络有出口限制,或者你设了代理但代理配置写在别处没生效。检查/etc/apt/apt.conf.d/下有没有代理配置。
第三类是仓库内容层。关键字是404 Not Found、Release file ... does not have a Release file。这基本是配置写错了——发行版代号对不上(把jammy写成focal),或者镜像站根本没同步这个 suite。
第四类是校验层。关键字是Hash Sum mismatch、NO_PUBKEY、signatures couldn't be verified、Release file is not valid yet。这类最麻烦,但原因往往很固定:校验和不匹配多半是中间有缓存代理改写了内容,或者本地索引是半截的;NO_PUBKEY是缺第三方源的公钥;is not valid yet是系统时钟不对,索引文件的时间戳落在了"未来"。
3.3 一次真实的 404 与 GPG 报错排查复盘
讲个我实际踩过的完整链路。给一台新机器换源,改完配置跑update,报404 Not Found,提示找不到dists/focal/Release。
第一步没有急着改配置,而是先确认系统版本:
lsb_release -a输出是 22.04,代号jammy。这就定位到问题了——配置文件里写的 suite 是focal。原因是我从一篇老教程里复制了源配置,教程对应的是 20.04。改过来之后重新 update,又报了一部分404,但这次只有一行。
第二步用grep把配置文件全扫了一遍:
grep -r "^deb" /etc/apt/sources.list /etc/apt/sources.list.d/发现sources.list.d/里还躺着一个早期加的第三方源文件,指向focal。删掉这个文件,404彻底消失。
第三步冒出NO_PUBKEY,提示缺少某个源的签名密钥。处理方式不是用apt-key add(已废弃),而是:
curl -fsSL https://example.com/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/example.gpg然后在源配置里加上signed-by=/usr/share/keyrings/example.gpg。重新 update,全部Hit和Get,没有任何Err。
这条链路值得记住的地方在于:报错要一条一条消,不要一次改三处。如果第一步就把源全删了重写,你永远不知道真正的原因是 suite 写错,还是残留文件没清。
4. update、upgrade、dist-upgrade 与 autoremove 的职责边界
4.1 四个命令各管什么
这几个命令名字长得像,职责差别其实很大,混用会出事故。
| 命令 | 操作对象 | 会不会装/删包 |
|---|---|---|
apt-get update | 本地索引 | 不装不删,只刷新清单 |
apt-get upgrade | 已安装的包 | 只升级,不删包,装不上就跳过 |
apt-get dist-upgrade | 已安装的包 | 可以删包来满足依赖变化 |
apt-get autoremove | 自动安装的孤儿包 | 会删除包 |
upgrade有个重要特性:如果升级某个包需要卸载另一个包,它会直接放弃升级那个包,然后告诉你"这些包被保留"。这是保守设计,适合生产环境。dist-upgrade(现在叫full-upgrade)则会为了完成升级真的去卸载包,更适合跟着发行版走的时候用。
4.2 为什么必须先 update 再 upgrade
upgrade的判断依据完全来自本地索引。索引是三天前的,它就只能看到三天前的版本信息,于是给你升级到三天前的版本,然后你奇怪为什么别人都升到新版了你还在旧版。
更隐蔽的问题是依赖计算。索引陈旧时,新包和新依赖的关系对不上,upgrade可能得出一个错误的结论,比如报一堆"无法满足的依赖"。这时候你要做的不是去手动装依赖,而是先 update 一次。
所以流程永远是:update→ 看apt list --upgradable有什么 → 决定要不要upgrade。跳过第一步直接升级,属于盲操作。
4.3 autoremove 会带走什么
autoremove删的是"被标记为自动安装、且当前没有任何包依赖它"的包。听起来很安全,但有个坑:如果你当初是手动装了一个包,后来它被别的包依赖上了,再后来那个包被卸了,它就可能被标记成 auto。
动手之前务必先看一遍:
apt-get autoremove --dry-run或者
apt-get autoremove -s它会列出将要删除的包而不实际执行。看到不认识的名字就去搜一下它是干什么的,特别是带lib前缀的库文件,删错了可能让某个软件直接启动不了。
另外可以用apt-mark showmanual查看当前被标记为手动安装的包列表,用apt-mark showauto看自动安装的。如果你发现某个重要工具被标成了 auto,可以手动改回来:
sudo apt-mark manual 包名这个小技巧救过我好几次,尤其是那些通过脚本批量装上去、标记状态不明确的包。
5. 没有终端的时候:sudo、定时任务与自动化场景
5.1 sudo 为什么非要一个终端
不少人在 IDE 插件、CI 流水线或者某些自动化工具里调用sudo apt-get update时会撞上这个报错:
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper原因很清楚:sudo默认从/dev/tty读取密码,而自动化环境里根本没有分配终端,于是它拿不到输入,只能报错退出。这不是权限不够,是"没有地方输密码"。
处理方式有三种,按场景选。最简单的是用-S让 sudo 从标准输入读密码:
echo "$PASSWORD" | sudo -S apt-get update但密码会出现在命令历史和进程列表里,只适合临时调试。第二种是配置 askpass 助手,通过环境变量指定一个能提供密码的程序:
export SUDO_ASKPASS=/path/to/askpass.sh sudo -A apt-get update第三种最干净,用visudo配置免密,但一定要把范围限死:
youruser ALL=(root) NOPASSWD: /usr/bin/apt-get update, /usr/bin/apt-get upgrade注意:千万不要图省事写成
NOPASSWD: ALL。那等于把 root 权限敞开了给,一旦这个账号被盗用,后果不可控。
5.2 定时任务里跑 update 的注意点
定时自动刷新索引是个常见需求,但直接往 crontab 里塞一行会踩几个坑。
第一,cron 环境的PATH非常简陋,很多命令找不到。要么写全路径/usr/bin/apt-get,要么在脚本开头自己设PATH。第二,如果后续还要跑upgrade,一定要设DEBIAN_FRONTEND=noninteractive,否则某些包会弹出配置界面,在无终端环境里直接卡死。第三,输出要重定向到日志文件,不然出错了你根本不知道。
一个我用了很久的写法:
#!/bin/bash export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin export DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get update -qq >> /var/log/apt-auto-update.log 2>&1放在每天的凌晨执行。-qq是静默模式,只在出错时输出,日志不会无限膨胀。更新索引本身不占什么资源,但记得别和其他的 apt 操作撞时间。
5.3 内网离线源的基本思路
机器多了之后,每台都去公网镜像站拉索引是很浪费的。常见做法是在内网搭一个镜像,所有机器的sources.list指向内网地址,update走局域网,速度和稳定性都上一个大台阶。
同步工具有apt-mirror、debmirror,也可以直接对镜像站做rsync。核心是只同步你实际用到的 suite 和 component,不然全量同步能吃掉几百 GB。做完之后记得定期同步,否则内网源会越来越旧,机器上的包版本会和外网脱节。
搭内网源还有个附带好处:可以给源加上trusted=yes(前提是内网完全可控),省掉 GPG 密钥分发的麻烦。公网源千万别这么干。
6. 缓存、锁和镜像源:把 update 跑顺的几条实操
6.1 三把锁分别在哪儿
APT 相关操作失败时最常见的一句报错是:
Could not get lock /var/lib/dpkg/lock-frontend涉及的锁文件主要有几个:/var/lib/dpkg/lock-frontend、/var/lib/dpkg/lock、/var/lib/apt/lists/lock、/var/cache/apt/archives/lock。看到这个报错的正确顺序是:先确认是不是真的还有别的 apt 进程在跑。
ps aux | grep -E "apt|dpkg"如果确实有,等它跑完就行。如果确认没有残留进程,才考虑手动删锁文件。直接rm锁文件是最后手段,因为它掩盖了真正的问题,进程还没死透的时候删锁,两个 apt 同时写索引,索引文件就废了。
6.2 缓存的清理尺度
/var/cache/apt/archives/存的是下载下来的.deb包,装完就没用了。apt-get clean全清,apt-get autoclean只清已经过期的版本。这两个都安全。
/var/lib/apt/lists/就不一样了,它是索引,删了之后必须重新update才能恢复。但恰恰有些场景需要这么干——比如遇到Hash Sum mismatch,怎么重试都不行,这时候:
sudo rm -rf /var/lib/apt/lists/* sudo apt-get update强制从零重建索引,十次有九次能解决。原理是旧的索引文件可能已经损坏,而 APT 因为校验逻辑的问题没法自动识别并修复。
6.3 镜像源的选择与测速
国内常用的几个镜像站速度都不错,但不同地区、不同运营商差异明显,最好实测一下。用curl对同一个文件分别测响应时间:
curl -o /dev/null -s -w '%{time_total}\n' http://mirrors.aliyun.com/ubuntu/dists/jammy/Release把几个源的地址换成同一个路径,各跑三五次取平均,选最快的那个。别只测一次,网络抖动很常见。
| 镜像源 | 特点 |
|---|---|
| 阿里云 | 覆盖全,更新及时,华东地区表现好 |
| 清华 TUNA | 同步频率高,教育资源网络访问快 |
| 中科大 | 华中、华东地区延迟低 |
| 华为云 | 华南地区表现稳定 |
测速之外还有个判断标准:看update时Get行的大小和速度。有些源响应快但带宽小,索引文件多了之后反而慢。真正体感差的是那种卡在某个大文件上不动的,遇到就果断换。
7. 换到 yum/dnf 时,哪些概念是通的、哪些完全不同
7.1 索引缓存的落点不一样
从 Debian 系换到 Red Hat 系,第一个不适应的是"刷新索引"这件事没了独立命令。apt-get update只刷索引,而yum update或dnf update是刷新索引加升级包一步到位,这是最容易被搞混的地方。
如果你只想刷新索引不动包,要用:
sudo yum makecache sudo dnf makecache对应地,索引的存放位置也不同。apt 是/var/lib/apt/lists/,yum/dnf 是/var/cache/dnf/(老版本在/var/cache/yum/)。清缓存用yum clean all或dnf clean all,注意clean all会把索引也清掉,下次任何操作都要重新下载。
7.2 源配置的结构差异
apt 的源配置是"一行一个源",yum 是"一段一个源",写在/etc/yum.repos.d/*.repo里:
[base] name=CentOS-$releasever - Base baseurl=http://mirrors.aliyun.com/centos/$releasever/os/$basearch/ enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7方括号里是仓库 ID,baseurl对应 apt 的 URI,gpgcheck相当于 apt 的签名校验开关。加第三方源同样是新建一个.repo文件,和 apt 在.d目录里加文件是一个思路。
7.3 别把 apt 的习惯直接搬过去
最容易出事故的一点:习惯了apt-get update只刷索引,跑到 CentOS 上顺手敲了个yum update,结果直接把系统里所有能升的包全升了。生产环境上这么来一下,够你加班一整晚。
稳妥的做法是升级前先看:
sudo yum check-update它只列出可升级的包,不实际执行。确认没问题再动手。这个命令在 apt 里对应的是apt list --upgradable,两边概念是通的,只是命令名字不一样。
跨发行版切换时,我建议先把这几个对应关系记在本子上:刷新索引 apt 是update、yum 是makecache;列出可升级 apt 是list --upgradable、yum 是check-update;清缓存 apt 是clean、yum 是clean all。这几组搞清楚了,日常操作基本不会翻车。
我个人用下来的体会是,apt-get update这个命令真正的价值不在于它做了什么,而在于它把"信任"这件事显式地摆在了你面前——索引从哪来、签名对不对、校验和能不能对上,每一步失败都会明确告诉你原因。真正让人头疼的从来不是命令本身,而是那些被忽略的输出行和没搞清楚的源配置。养成一个习惯:每次update之后扫一眼有没有Err,看到Ign多想一秒它为什么被跳过,这份索引就是干净的。