公司动态

Ubuntu按安装时间查看软件包:APT/DPKG日志解析与脚本实现

📅 2026/8/24 7:03:57
Ubuntu按安装时间查看软件包:APT/DPKG日志解析与脚本实现
1. 项目概述为什么需要按安装时间查看软件在Ubuntu或者其他基于Debian的Linux发行版上我们通常用apt list --installed或者dpkg -l来查看已安装的软件包。但这两个命令默认给出的列表要么是按字母顺序排列要么是按包名状态排列唯独没有按我们最关心的“安装时间”来排序。这就像你有一个塞满各种工具和文件的抽屉你知道里面有什么但完全记不清哪件东西是什么时候放进去的。这个需求在几个实际场景下会变得非常迫切。比如你的系统最近变得有点慢或者出现了一些奇怪的错误你怀疑是最近安装的某个软件包导致的。这时候如果能快速拉出一个“最近安装”的软件清单排查范围就能瞬间缩小。再比如你接手了一台别人用过的服务器想了解一下这台机器的“软件安装史”看看前任管理员都部署了哪些服务和应用按时间线查看无疑是最直观的方式。对于有洁癖或者追求系统整洁度的用户定期回顾并清理那些一时兴起安装、后来再也没用过的“陈年旧包”按安装时间排序也是最有效的梳理方法。然而Ubuntu系统本身并没有提供一个像apt list --installed-by-date这样开箱即用的命令。这并非系统设计的疏忽而是因为软件包管理本身是一个复杂的状态系统一个软件包的“安装时间”并不是一个单一、明确的属性。它可能涉及软件包首次被解压配置的时间、最后一次升级的时间、甚至是被重新配置的时间。我们的目标就是从系统日志和数据库的蛛丝马迹中准确地提取并还原出这个“安装时间线”。2. 核心思路与数据源分析要实现按安装时间排序我们首先得搞清楚Ubuntu把软件包的安装记录藏在了哪里答案主要在两个地方APT的历史日志和DPKG的详细日志。2.1 APT历史日志/var/log/apt/history.log这是最常用、最直观的数据源。每当你使用apt或apt-get命令安装、升级或删除软件包时APT都会在这个日志文件里追加一条记录。记录里包含了操作时间、执行的操作Install, Upgrade, Remove、受影响的软件包列表以及执行该操作的命令。查看这个日志很简单cat /var/log/apt/history.log或者用less、tail来查看# 查看最近10条记录 tail -n 10 /var/log/apt/history.log # 实时查看最新记录需要另开终端执行apt操作 tail -f /var/log/apt/history.log一条典型的记录长这样Start-Date: 2023-10-27 14:30:15 Commandline: apt install nginx curl Requested-By: user (1000) Install: nginx:amd64 (1.18.0-0ubuntu1.4), curl:amd64 (7.68.0-1ubuntu2.8) End-Date: 2023-10-27 14:30:48优点时间精确到秒记录了完整的命令行清晰明了。缺点只记录通过APT进行的操作。如果你用dpkg -i直接安装了一个.deb文件或者通过snap、flatpak安装的软件这里不会有记录。此外日志文件是轮转的比如history.log.1.gz,history.log.2.gz太旧的操作可能不在当前的主日志文件里。2.2 DPKG详细日志/var/log/dpkg.log这是更低层、更全面的日志。DPKG是Debian包管理系统的底层工具几乎所有对软件包数据库的修改安装、配置、升级、删除都会在这里留下痕迹。它的记录粒度更细。查看命令cat /var/log/dpkg.log一条典型的DPKG日志记录2023-10-27 14:30:20 status installed nginx:amd64 1.18.0-0ubuntu1.4 2023-10-27 14:30:22 status installed curl:amd64 7.68.0-1ubuntu2.8优点覆盖所有通过dpkg的操作包括dpkg -i直接安装。记录非常详细包含状态变化status、configure等。缺点信息比较原始和繁杂需要从中筛选出“安装”事件。同样存在日志轮转问题。2.3 方案选型为什么选择解析日志你可能会想软件包管理器应该有个数据库字段记录安装时间吧很遗憾主流的包管理数据库如/var/lib/dpkg/status并没有直接存储这个信息。安装时间是一个“事件”而不是软件包的静态属性它更适合被记录在日志中。因此我们的核心思路就是编写一个脚本从上述日志文件中提取出每个软件包的安装记录解析出时间戳然后与我们当前已安装的软件包列表进行关联和排序输出。这里有一个关键决策点以哪个日志为准追求高可读性和命令关联性优先使用/var/log/apt/history.log。它能告诉你“是谁在什么时间通过什么命令安装的”对于问题回溯尤其有用。追求最全的安装记录使用/var/log/dpkg.log。它能抓到所有漏网之鱼比如直接dpkg安装的包。最稳妥的方案两者结合。先尝试从history.log中获取信息如果找不到比如是dpkg直接安装的再回退到dpkg.log中查找。我们下面的实操将采用这种结合的方式。注意无论是APT还是DPKG日志默认都有日志轮转和大小限制。/var/log/apt/history.log和/var/log/dpkg.log通常只保存最近一段时间如一个月的详细记录。更早的日志会被压缩成.gz文件如history.log.1.gz或直接删除。如果你需要查询很久以前的安装记录可能需要解压并查询这些归档日志。我们的脚本示例会包含处理压缩日志的功能。3. 实操构建按安装时间排序的查看脚本纸上谈兵终觉浅我们直接动手写一个实用的Bash脚本。这个脚本将实现以下功能自动查找并解析/var/log/apt/history.log及其压缩归档文件。自动查找并解析/var/log/dpkg.log及其压缩归档文件。合并两者信息为每个已安装的软件包找到最早的“安装”时间。按安装时间从新到旧排序输出。将以下脚本保存为一个文件例如list-installed-by-date.sh并赋予执行权限。#!/bin/bash # 脚本list-installed-by-date.sh # 功能按安装时间从新到旧列出已安装的软件包 # 用法./list-installed-by-date.sh [包名过滤关键字] # 设置一个临时文件来存储包名和时间戳 temp_file$(mktemp) # 函数从APT历史日志中提取安装记录 parse_apt_history() { local log_file$1 # 判断是否是.gz压缩文件 if [[ $log_file *.gz ]]; then local cat_cmdzcat else local cat_cmdcat fi # 使用awk解析日志。原理查找包含“Install: ”的行其前面最近的Start-Date就是安装时间。 $cat_cmd $log_file 2/dev/null | awk /^Start-Date:/ { # 提取日期时间格式如2023-10-27 14:30:15 # 注意日期和时间之间可能有多个空格 gsub(/^Start-Date: */, ); start_date $0; } /^Install:/ { # 这一行列出了所有安装的包格式如Install: package1:arch (version), package2:arch (version) # 移除“Install: ”前缀 gsub(/^Install: */, ); # 按逗号分割包列表 split($0, packages, , ); for (i in packages) { # 从每个包字符串中提取纯包名例如从“nginx:amd64 (1.18.0)”提取“nginx” # 匹配第一个冒号或空格之前的部分 if (match(packages[i], /^[^:[:space:]]/)) { pkg_name substr(packages[i], RSTART, RLENGTH); # 打印 时间戳 包名 print start_date, pkg_name; } } } } # 函数从DPKG日志中提取安装记录 parse_dpkg_log() { local log_file$1 if [[ $log_file *.gz ]]; then local cat_cmdzcat else local cat_cmdcat fi # 解析dpkg.log寻找状态为“installed”的记录 $cat_cmd $log_file 2/dev/null | awk / status installed / { # 日志格式2023-10-27 14:30:20 status installed package:arch version # $1是日期$2是时间$5是包名包含架构 # 从包名中剥离架构部分例如“nginx:amd64” - “nginx” pkg_full $5; sub(/:.*$/, , pkg_full); # 移除第一个冒号及之后的所有内容 # 组合日期和时间 install_time $1 $2; print install_time, pkg_full; } } # 主逻辑开始 echo 正在收集软件包安装记录这可能需要几秒钟... # 1. 收集所有相关的日志文件包括当前的和轮转压缩的 apt_logs( /var/log/apt/history.log /var/log/apt/history.log.* ) dpkg_logs( /var/log/dpkg.log /var/log/dpkg.log.* ) # 2. 解析APT历史日志 for log in ${apt_logs[]}; do if [[ -f $log ]] || [[ $log *.gz -f $log ]]; then parse_apt_history $log $temp_file fi done # 3. 解析DPKG日志 for log in ${dpkg_logs[]}; do if [[ -f $log ]] || [[ $log *.gz -f $log ]]; then parse_dpkg_log $log $temp_file fi done # 4. 获取当前所有已安装的软件包列表 # 使用dpkg-query列出所有状态为“installed”的包只取包名 installed_pkgs$(dpkg-query -W -f${Package}\n 2/dev/null) # 5. 关联、去重、排序 # 思路遍历每个已安装的包从临时文件中找到它最早的一条记录 while IFS read -r pkg; do # 从临时文件中grep出这个包的所有记录取时间最早的一条因为日志是按时间顺序写的第一条就是最早的 # 使用sort按时间排序取最旧的一条 first_record$(grep -E ^[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]][0-9]{2}:[0-9]{2}:[0-9]{2}[[:space:]]$pkg$ $temp_file | sort | head -n 1) if [[ -n $first_record ]]; then echo $first_record else # 如果没有找到记录可能是通过snap或其他方式安装的或者日志已丢失。 # 我们可以给它一个非常早的默认时间或者跳过。这里选择赋予一个极早的时间戳使其排在最后。 echo 1970-01-01 00:00:00 $pkg fi done $installed_pkgs | sort -r -k1,2 | awk {printf %-30s %s\n, $1 $2, $3} | head -n 50 # 6. 清理临时文件 rm -f $temp_file脚本使用说明将上面的代码复制到一个文本编辑器保存为list-installed-by-date.sh。在终端中切换到脚本所在目录运行chmod x list-installed-by-date.sh赋予执行权限。运行脚本./list-installed-by-date.sh。如果你想过滤特定的包可以传入一个参数例如./list-installed-by-date.sh nginx脚本会列出包含“nginx”的包。你需要稍微修改脚本末尾的grep部分来实现过滤为了清晰起见基础脚本未包含此功能但原理是在echo $first_record前加一个判断。脚本核心原理解读parse_apt_history函数使用awk逐行处理日志。它维护一个start_date变量当遇到Start-Date:行时更新。当遇到Install:行时就知道接下来列出的所有包都是在这个start_date被安装的。然后它拆分包列表并提取纯包名去掉版本和架构信息与时间戳一起输出。parse_dpkg_log函数更简单直接查找包含status installed的行提取时间戳和包名。时间关联脚本将所有日志记录收集到一个临时文件中。然后对于当前系统里每一个已安装的包通过dpkg-query获取去这个临时文件里寻找它的第一条记录即最早安装时间。这里用grep和sort | head -n 1实现。排序输出sort -r -k1,2是关键。-r表示反向排序最新的在前-k1,2表示按第一列和第二列即日期和时间作为排序键。最后用awk美化一下输出格式并默认只显示前50条。重要注意事项与避坑指南权限问题读取/var/log/下的日志需要root权限。如果你以普通用户运行脚本可能会因为权限不足而读不到日志导致输出大量“1970-01-01”的记录。解决方法使用sudo运行脚本即sudo ./list-installed-by-date.sh。日志轮转脚本已经考虑了.gz压缩文件和通配符*可以自动处理history.log.1.gz这类归档文件。这是脚本比单纯用grep命令更健壮的地方。性能如果系统使用时间很长日志文件非常多脚本遍历可能会稍慢。这是为了获取准确数据的必要开销。你可以通过修改apt_logs和dpkg_logs数组只包含你关心的日志文件范围来加速。非APT/DPKG安装的软件通过Snap、Flatpak、AppImage、源码编译make install等方式安装的软件不会出现在这些日志中。脚本会将其标记为“1970-01-01”Unix纪元起点排在列表最后。要查看这些软件需要使用其各自的命令如snap list、flatpak list。4. 进阶技巧与替代方案上面的脚本已经是一个功能完整的工具了。但如果你喜欢更快捷的方式或者想了解其他思路这里有几个“一招鲜”的命令和替代方案。4.1 使用grep与sort的快速组合命令如果你只是想快速瞥一眼最近通过APT安装了什么一条命令就能搞定# 查看最近10次APT安装操作从history.log grep -A2 ^Start-Date: /var/log/apt/history.log | grep -E Start-Date:|Install: | head -20这个命令会提取最近的安装操作记录但输出是原始的日志格式可读性一般。如果想看最近安装的具体包名从dpkg.log# 查看最近20个被标记为‘installed’的包从dpkg.log grep status installed /var/log/dpkg.log | tail -204.2 利用apt命令的--installed与日志关联一个更精巧的思路是利用apt命令本身的查询功能虽然它不直接支持按时间排序但我们可以结合日志来丰富信息# 列出所有已安装的包并尝试从APT日志中查找其安装时间仅显示能找到的 for pkg in $(apt list --installed 2/dev/null | awk -F/ NR1{print $1}); do install_time$(zgrep -h Install:.*$pkg /var/log/apt/history.log* 2/dev/null | head -1 | awk {print $1, $2}) if [[ -n $install_time ]]; then printf %-30s %s\n $install_time $pkg fi done | sort -r这个命令循环遍历每个已安装的包去所有APT历史日志包括压缩的里搜索包含该包名的“Install:”行并提取时间。最后排序输出。它的优点是命令简短缺点是循环遍历效率较低且对于一次安装多个包的情况zgrep可能匹配到多条记录我们只取第一条head -1。4.3 图形化工具Synaptic包管理器的历史记录对于桌面用户其实有更简单的方法。安装Synaptic包管理器sudo apt install synaptic打开Synaptic点击菜单栏的“文件” - “历史记录”。这里会以图形化、按时间顺序列出所有的APT操作历史包括安装、升级、删除。你可以点击每一条记录查看详情。这可能是对普通用户最友好的方式了。4.4 第三方工具apt-history有一些社区维护的脚本提供了更强大的功能。例如你可以从GitHub等地方找到名为apt-history的脚本。通常安装和使用步骤如下# 示例下载一个流行的apt-history脚本 sudo wget -O /usr/local/bin/apt-history https://gist.githubusercontent.com/example/raw/apt-history.sh sudo chmod x /usr/local/bin/apt-history # 使用 apt-history install # 查看所有安装记录 apt-history upgrade # 查看所有升级记录 apt-history remove # 查看所有删除记录使用第三方工具前请务必查看其源代码确保理解其行为并从可信来源获取。5. 常见问题与排查实录在实际使用脚本或命令的过程中你可能会遇到以下问题。这里记录了我的排查经验和解决方案。5.1 问题运行脚本输出全是“1970-01-01”现象无论运行自己的脚本还是上面的示例命令输出的时间戳都是1970年或者根本找不到安装记录。原因与排查权限不足这是最常见的原因。/var/log/apt/和/var/log/dpkg.log的默认权限是root:root且日志文件通常只有root可读。用普通用户运行脚本自然读不到内容。解决在命令前加sudo。例如sudo ./list-installed-by-date.sh。日志文件不存在或已被清理有些最小化安装的系统或者经过深度清理的服务器可能关闭了日志或清空了日志文件。排查运行ls -la /var/log/apt/history.log /var/log/dpkg.log查看文件是否存在及大小。解决如果文件存在但为空那历史记录已丢失无法追溯。可以检查压缩归档文件ls -la /var/log/apt/history.log.*.gz。软件包并非通过APT/DPKG安装如前所述Snap、Flatpak、源码编译安装的软件不会出现在这些日志里。排查用which 包名或dpkg -l | grep 包名确认软件是否通过dpkg管理。5.2 问题脚本运行速度很慢现象系统用了好几年日志文件巨大几十MB甚至上百MB脚本运行需要十几秒甚至更久。优化方案限制查询范围如果你只关心最近几个月或一年的安装可以修改脚本中的日志文件数组只包含最近的几个文件。# 在原脚本中修改这两行例如只查询最近3个日志文件 apt_logs( /var/log/apt/history.log /var/log/apt/history.log.1.gz /var/log/apt/history.log.2.gz ) dpkg_logs( /var/log/dpkg.log /var/log/dpkg.log.1.gz /var/log/dpkg.log.2.gz )使用更高效的工具awk本身很快但脚本中用了双层循环外层遍历所有已安装包内层用grep搜索。对于包数量很多5000的系统可以优化算法。一个思路是先提取所有日志记录到一个按包名索引的哈希结构如关联数组中然后再遍历已安装包列表去这个哈希里查找。但这需要更复杂的awk或Python脚本实现。5.3 问题时间显示不准确或时区不对现象脚本显示的时间与本地时间对不上。原因系统日志/var/log/下的日志默认使用UTC时间记录而不是本地时区时间。解决在脚本中进行时区转换。可以在输出前使用date命令进行转换。但更简单的方法是在脚本的排序和输出部分告诉系统将这些时间戳按本地时间理解。一个修改输出行的例子# 将原脚本中输出行的awk部分修改如下 done $installed_pkgs | sort -r -k1,2 | while read date time pkg; do # 将UTC时间转换为本地时间显示 local_time$(date -d $date $time %Y-%m-%d %H:%M:%S 2/dev/null || echo $date $time) printf %-30s %s\n $local_time $pkg done | head -n 50这样显示的时间就是你本地时区的时间了。5.4 问题如何查看特定时间段的安装记录需求我想查看2023年10月期间安装的所有软件。解决方案结合grep和日期过滤。使用我们之前写的脚本可能有点重可以直接查询日志。# 查看APT日志中2023年10月的安装记录 grep ^Start-Date: 2023-10 /var/log/apt/history.log | grep -A2 ^Start-Date: # 查看DPKG日志中2023年10月的安装记录 grep ^2023-10-.*status installed /var/log/dpkg.log如果需要更复杂的查询可以将日志导出用文本处理工具如awk, sed或导入到SQLite等数据库中进行查询这适合高级用户或自动化审计需求。经过以上步骤你不仅得到了一个即拿即用的脚本更重要的是理解了Ubuntu/Debian系统下软件包安装时间记录的来龙去脉。下次当系统出现异常或者你只是单纯地想清理一下“软件仓库”时就知道该如何快速定位那些“新来的”或者“被遗忘的”软件包了。管理系统的乐趣往往就藏在这些能让你对机器了如指掌的小技巧里。