删除旧内核的 Bash 一行命令

我看到很多关于如何释放/boot分区空间的帖子,这也是我的目标。然而,我只对删除内核感兴趣,而不是每一个内核,只删除当前的内核。
我需要解决方案是一行命令,因为我将从Puppet运行脚本,我不想有额外的文件存在。到目前为止,我得到了以下内容:
dpkg -l linux-* | awk '/^ii/{print $2}' | egrep [0-9] | sort -t- -k3,4 --version-sort -r | sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"` | xargs sudo apt-get -y purge

更准确地说,它目前的功能如下:
- 列出所有的linux-*软件包并打印它们的名称。 - 只列出那些带有数字的软件包,并对它们进行排序,返回逆序结果。这样,较旧的内核会被列在最后。 - 仅打印当前内核之后的结果。 - 由于存在一些linux-{image,headers}的结果,请确保不会清除与当前内核相关的任何内容。 - 调用apt进行清理。
这个方法是可行的,但我相信解决方案可以更加优雅,并且对于生产环境是安全的,因为我们至少有20台服务器运行着Ubuntu。
感谢您的时间, Alejandro.

从网络上找到了一些旧东西:http://tuxtweaks.com/2010/10/remove-old-kernels-in-ubuntu-with-one-command/ - user68186
如果你的一行代码没有保存在脚本文件中,你该如何使用它呢? - jarno
8个回答

看起来还不错,只有几点评论。前两个评论使命令更安全,而第三和第四个则使其变得更短。随意选择是否采纳其中任何一个。但我强烈建议遵循前两个。你要确保尽可能安全。我是说真的。你正在对一些自动生成的软件包列表执行sudo apt-get -y purge命令。那真是太邪恶了!:)
列出所有的`linux-*`会得到很多误报,比如(来自我的输出示例)`linux-sound-base`。即使这些可能会在后面的命令中被过滤掉,但我个人觉得最好一开始就不要列出它们。更好地控制你想要删除的软件包。不要做可能产生意外结果的事情。所以我会从以下命令开始:
``` dpkg -l linux-{image,headers}-* ```
我认为你的正则表达式“只列出带有数字的”稍微太简单了。例如,在64位系统上有一个名为`linux-libc-dev:amd64`的软件包。你的正则表达式会匹配到它,但你不希望它匹配到。诚然,如果你遵循了我的第一个建议,那么`linux-libc-dev:amd64`也不会被列出,但还是有必要改进一下。我们对版本号的结构了解得比“只有一个数字”更多。此外,引用正则表达式通常是一个好主意,以防止shell对其进行错误解释。所以我会将`egrep`命令改为:
``` egrep '[0-9]+\.[0-9]+\.[0-9]+' ```
然后是排序的问题。为什么要排序呢?既然你打算删除所有内核(除了当前的内核),是否很重要让你在删除较新内核之前先删除较旧的内核?我认为这没有任何区别。或者你只是为了能够使用`sed`来“仅打印当前内核之后的结果”?但在我看来,这样做太复杂了。为什么不简单地过滤掉与当前内核对应的结果,就像你已经用`grep -v`做的那样,然后结束呢?老实说,如果我采用你命令的前半部分(包括我的两个前面的建议),在我的机器上得到的结果是:
``` $ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | sort -t- -k3,4 --version-sort -r | sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"` linux-image-3.8.0-34-generic linux-image-3.5.0-44-generic ```
去掉排序和`sed`部分,我得到的结果是:
``` $ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | grep -v -e `uname -r | cut -f1,2 -d"-"` linux-image-3.5.0-44-generic linux-image-3.8.0-34-generic linux-image-extra-3.5.0-44-generic linux-image-extra-3.8.0-34-generic ```
所以你更复杂的命令实际上会在我的机器上漏掉两个我想要删除的软件包(现在可能这些`linux-image-extra-*`依赖于`linux-image-*`,因此它们也会被删除,但明确指定一下不会有坏处)。无论如何,我不明白你排序的意义;一个简单的`grep -v`而不是繁琐的预处理应该就可以了,可能甚至更好。我是KISS原则的支持者。这将使你以后更容易理解或调试。而且,没有排序的话稍微更高效一点 ;)
这只是纯粹的
因此,我最终选择了更简单和更安全的命令。
$ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | grep -v $(uname -r | cut -d- -f-2) | xargs sudo apt-get -y purge

既然你真的想清理你的 /boot 分区,一个完全不同的方法是列出 /boot 的内容,使用 dpkg -S 来确定这些文件属于哪些软件包,过滤掉属于当前内核的文件,并移除结果中的软件包。但我更喜欢你的方法,因为它还会找到过时的软件包,比如 linux-headers-*,它们并未安装到 /boot,而是安装到了 /usr/src


感谢您的回复,@Malte,谢谢您的建议。我发现您的前两个步骤非常明确,确实使得一行命令更加安全。然而,我认为您的后两个步骤忽略了较新的内核并将它们清除。至少,在我的服务器上尝试了您的解决方案后,会卸载掉一些不需要的东西。 - Alejandro
很有趣。你能私信给我两个输出吗?一个包含我的第三和第四个建议的命令,另一个不包含它们。还有uname -r的输出。对我来说,它运行得很好。很有意思看看为什么在你的情况下不起作用。 - Malte Skoruppa
1我试图给您发送私信,但是未找到任何电子邮件地址,因此我将在此处发布我的输出:http://hastebin.com/raleyigowe.vhdluname -r 显示我的服务器正在使用 linux-image-3.8.0-34。问题是有时候服务器已经下载了一个更新的内核,但还没有安装它。这就是为什么我正在寻找一种只删除旧内核的方法。 - Alejandro
这个命令选择了当前内核的头文件,这似乎是不可取的。 - Mark Stosberg
@MarkStosberg 你能把 dpkg -l linux-{image,headers}-*uname -r 的输出发出来吗?你可以把输出放在 pastebin 上,然后在这里贴一个链接。 - Malte Skoruppa
1@MalteSkoruppa,uname -r生成的是4.8.0-36-generic,但无法从列表中排除linux-headers-4.8.0-36 - Mark Stosberg
@MarkStosberg 你是对的,我已经相应地编辑了我的帖子。谢谢! - Malte Skoruppa
我的启动盘已满,并且新的内核由于空间不足而未能完全安装,这给我带来了一个进退两难的困境:“无启动空间/未满足的依赖关系”。为了成功删除旧的内核,我将最后一部分从“apt -y purge”改为“dpkg --purge”,效果非常好。否则,这篇文章非常棒,非常清楚地解释了每个步骤,谢谢。 - Sruli

我写了一个脚本,可以删除版本低于当前启动版本的“linux-*”软件包。我认为测试软件包状态是不必要的。该命令在清除软件包之前会要求确认。如果您不想这样做,请在apt-get命令中添加-y选项。
sudo apt-get purge $(dpkg-query -W -f'${Package}\n' 'linux-*' |
sed -nr 's/.*-([0-9]+(\.[0-9]+){2}-[^-]+).*/\1 &/p' | linux-version sort | 
awk '($1==c){exit} {print $2}' c=$(uname -r | cut -f1,2 -d-))

然而,为了能够留下可配置数量的备用内核,我建议使用我的linux-purge脚本,并加上--keep选项。有关该脚本的更多信息,请参阅此处

@jamo 我喜欢你的 linux-purge 脚本。不过,有一个小问题 - SebMa

太长不看:直接跳到底部。

虽然有点长,但我会为你分解:

  1. dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}'就像Malte建议的那样。列出相关的内核文件。
  2. egrep '[0-9]+\.[0-9]+\.[0-9]+'也是Malte建议的更安全的方法,通过查找版本号来选择只有内核文件。
  3. 由于我们可能同时列出镜像和头文件包,包命名可能会有所不同,因此我们需要这个awk解决方法,对于排序是必要的:awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}'的结果是在原始包名称之前加上版本号的新列,如下所示:
  4. 现在我们必须按顺序对列表进行排序,以防止卸载任何比当前运行的内核文件更新的内核文件:sort -k1,1 --version-sort -r给出了以下结果:
  5. 现在去除当前和较新的内核文件:sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"`给出了以下结果:
  6. 现在去掉我们添加的第一列,使用awk '{print $2}'得到我们想要的:
  7. 现在我们可以将其提供给软件包管理器以自动删除所有内容并重新配置grub:
  8. 我建议先进行试运行(尽管对于您的脚本目的而言,如果环境很大,这可能不太实用)

    现在,如果一切看起来都没问题,请使用以下命令实际删除:

再次强调,这个“一行代码”的目的是只删除比当前正在运行的内核旧的内核(这样任何新安装的内核仍然可用)。
谢谢,请告诉我这对您是否有效,以及您是否能够改进它!

请查看我的答案 - jarno

我真的厌倦了所有这些不必要的复杂性,所以我创建了一个Python包,使得一行代码变得微不足道。
ubuntu-old-kernel-cleanup | xargs sudo apt-get -y purge

使用以下方式进行安装

sudo pip install git+http://github.com/mrts/ubuntu-old-kernel-cleanup.git

请访问https://github.com/mrts/ubuntu-old-kernel-cleanup了解更多信息。

希望这对其他人也有所帮助。


尝试了这个,但是出现错误:"ubuntu-old-kernel-cleanup: 命令未找到"。 - Grant
那么它就不在你的可执行搜索路径中。你是按照上面描述的方式使用sudo pip install ...来安装的吗?实际上,sudo是很重要的,否则pip会将其安装在某个用户目录中,而shell可能不会在该目录中搜索可执行文件。 - mrts
@mrts 我刚刚为你的解决方案点了赞,因为它适用于Python2。然而,你的Python模块目前还不支持Python3:https://github.com/mrts/ubuntu-old-kernel-cleanup/issues/3 - SebMa
1@SebMa,谢谢你提醒!我刚刚将它移植到Python 3,请测试并在GitHub问题中告诉我它是否对你有效。 - mrts
1@mrts 运行良好,恭喜 :) - SebMa

你可以简单地使用'ls'命令列出/boot目录,以查看你拥有的内核版本。然后使用'sudo apt-get -y purge "xxx"'命令,将"xxx"替换为你想要删除的版本号。请注意,这不应该是你当前正在运行的版本!

1他们想要一个一行命令。你的解决方案需要超过1行。 - Anwar

安装bikeshedapt install bikeshed)并以root身份调用purge-old-kernels
$ sudo purge-old-kernels

purge-old-kernels已被弃用,推荐使用"apt autoremove"。如果您遇到问题,请向apt提交错误报告。请参阅https://bugs.launchpad.net/bikeshed/+bug/1569228/comments/7。 - Amedee Van Gasse

一个快速的答案,有需要时可以解释。
dpkg -l 'linux-image-[0-9]*' | 
awk -v current="$(uname -r)" '!/^i/ || $2~current {next} {print $2}' |
sed '$d' | 
xargs echo sudo apt-get autoremove

2我建议(或者,如果你愿意的话,请求)将这个扩展到包括解释。 :) - Eliah Kagan
@EliahKagan,我觉得这个脚本没有意义。为什么要跳过不想安装的软件包?这个脚本可能会删除一些比当前版本更新的内核,或者通过sed '$d'命令保留一些旧的内核。除此之外,这个脚本并不会删除任何与要删除的内核软件包相关的linux-header软件包或其他软件包,也不会删除软件包的配置文件。我建议使用我的答案。 - jarno
@EliahKagan 实际上,这个脚本并不会删除任何软件包,而是通过echo命令打印出你可以运行的apt-get命令。 - jarno

这个一行命令很好用。
如果你想的话,你可以随时去掉-y选项,在应用更改之前更好地检查结果。
# purge old kernels in one-line command

sudo apt purge -y $(sudo apt list linux-image* 2>/dev/null|grep instal|head -n -2|cut -d/ -f1) $(sudo apt list linux-headers* 2>/dev/null|grep instal|head -n -3|cut -d/ -f1)

为了获得所需的结果,我将搜索分为linux-imagelinux-headers两部分。
命令2>/dev/null可以去除警告信息。
grep中使用的instal一词至少适用于3种不同的语言:意大利语(installato),英语(installed)和西班牙语(instalado)。
我使用head来排除当前的内核。
通过cut -d/ -f1我们可以得到完整的软件包名称。
希望这些信息能帮助许多正在使用Linux的人。