Looks nice enough, just a few comments. The two first comments make the command safer, while the third and fourth make it a bit shorter. Feel free to follow or ignore any one of them. Though I will strongly advise to follow the first two. You want to make sure it's as safe as possible. I mean seriously. You're throwing a sudo apt-get -y purge
at some automatically generated package list. That is so evil! :)
Listing all linux-*
will get you many false positives, such as (example from my output) linux-sound-base
. Even though these may be filtered out later by the rest your command, I would personally feel safer not listing them in the first place. Better control what packages you want to remove. Don't do things that may have unexpected results. So I would start out with
dpkg -l linux-{image,headers}-*
Your regex to "list only the ones that have numbers" is slightly too simple in my opinion. For instance, there is the package linux-libc-dev:amd64
when you're on a 64-bit system. Your regex will match. You don't want it to match. Admittedly, if you followed my first advice, then linux-libc-dev:amd64
won't get listed anyway, but still. We know more about the structure of a version number than the simple fact "there's a number". Additionally, it's generally a good idea to quote regexes, just to prevent potential misinterpretations by the shell. So I would make that egrep command
egrep '[0-9]+\.[0-9]+\.[0-9]+'
Then there is this sorting thing. Why do you sort? Since you're going to remove all kernels (except the current one) anyway, is it important for you to remove older ones before newer ones? I don't think it makes any difference. Or are you only doing that so you can then use sed
to "Print only the results that go after the current kernel"? But IMO this feels much too complicated. Why not simply filter out the results corresponding to your current kernel, as you are already doing with grep -v
anyway, and be done? Honestly, if I take the first part of your command (with my two previous suggestions integrated), on my machine I get
$ 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
Removing that sorting/sed stuff, I get
$ 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
So your more complicated command would actually miss two packages on my machine, that I would want to remove (now it's possible that those linux-image-extra-*
thingys depend on the linux-image-*
thingys and therefore would get removed anyway, but it can't hurt to make it explicit). At any rate, I don't see the point of your sorting; a simple grep -v
without fancy preprocessing should be fine, presumably even better. I am a proponent of the KISS principle. It will make it easier for you to understand or debug later. Also, without the sorting it's slightly more efficient ;)
This is purely aestethic but you will get the same output with this slightly shorter variant. :-)
$ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | grep -v $(uname -r | cut -d- -f-2)
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
Consequently, I end up with the simpler and safer command
$ 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
Since you actually want to clean up your /boot
partition, a completely different approach would be to list the contents of /boot
, use dpkg -S
to determine the packages that the individual files belong to, filter out those that belong to the current kernel, and remove the resulting packages. But I like your approach better, because it will also find outdated packages such as linux-headers-*
, which do not get installed to /boot
, but to /usr/src
.
The accumulation of old kernels until /boot is full, thereby breaking apt, is a bug: LP #1357093, with a fix implemented in all flavors of Ubuntu 16.04 and newer.
Most users who notice this problem installed 'whole-disk encryption', which creates a tiny unencrypted /boot partition. Since it's tiny, the partition fills quickly, and these users notice the problem much sooner than others.
When a new kernel is installed, the /etc/kernel/postinst.d/apt-auto-removal script marks older kernels as eligible for autoremoval...but doesn't run autoremove. The lack of autoremove was originally intended to allow a human to review the list of removed packages.
The bugfix to LP #1357093 works for most users - if it doesn't work for you, then there is another underlying cause preventing old kernel packages from being apt-marked as eligible for autoremove. Seek one-on-one help in the Ubuntu support channels.
In older versions of Ubuntu, you must fix weed old kernels to preserve space manually. How you fix it manually depends upon whether your /boot is full or not. Most users don't notice the problem until /boot is full, and they are getting apt and dpkg no-space-left-on-device errors.
If /boot is not full, and apt works properly, a simple
sudo apt-get autoremove ## Ubuntu 14.04 and older
sudo apt autoremove ## Ubuntu 16.04 and newer
should remove all kernel packages that are eligible for autoremoval.
If /boot is full, and apt actions fail with the dpkg error 'no space left on device', then it is too late to use autoremove.
It's too late because Aptdaemon queues package actions. Autoremove is at the back of the queue, and apt aborts the entire remaining queue when any action fails...including running out of space. (This is arguably a bug in apt/aptdaemon)
The best practice here is to use 'uname' and 'dpkg' to remove one or two old kernels, freeing space for apt to complete it's queued actions. Then autoremove will work.
Example:
$ uname -r
3.16.0-36-generic ## This is kernel you MUST NOT remove.
$ dpkg -l | grep linux-image
rc linux-image-3.16.0-23-generic ## 'rc' means already removed
rc linux-image-3.16.0-28-generic ## 'rc' can be safely ignored
rc linux-image-3.16.0-29-generic
ii linux-image-3.16.0-30-generic ## 'ii' means installed. Removable
ii linux-image-3.16.0-31-generic ## Removable
ii linux-image-3.16.0-33-generic ## Removable
ii linux-image-3.16.0-34-generic ## Backup working kernel. Don't remove
ii linux-image-3.16.0-36-generic ## Current kernel. DO NOT REMOVE
## Use dpkg to remove one older kernel, freeing enough space for apt to work
$ sudo dpkg --remove linux-image-3.16.0-30-generic
$ sudo apt-get autoremove ## Ubuntu 14.04 and older
$ sudo apt autoremove ## Ubuntu 16.04 and newer
If you have been ignoring the problem for a long time, then there are second-order effects, like linux-image-generic pointing to the wrong kernel version, and apt dependency errors. There is no single way of fixing all of these at once.
Generally, the easy way to clean up most of these problems is to clean the old packages out of your local package cache, update your package database, and reinstall the offending packages from the Ubuntu repositories (instead of your local cache).
$ sudo apt-get update ## Refresh the package database (14.04 and older)
$ sudo apt update ## (16.04 and newer)
$ sudo apt-get autoclean ## Delete the obsolete packages from your local cache (14.04 and older)
$ sudo apt autoclean ## (16.04 and newer)
$ sudo apt-get install --reinstall <packagename> ## Reinstall the offending package with the latest version in the Ubuntu repositories (14.04 and older)
$ sudo apt install --reinstall <packagename> ## (16.04 and newer)
If you still encounter apt and/or dpkg errors, seek one-on-one help in the Ubuntu support channels.
Best Answer
The advantage of this answer is native Ubuntu Bash is used without installing third-party applications. Users of custom kernels who didn't use
apt
ordpkg
can change this bash script to suit their needs.Zenity based solution
Zenity provides a GUI interface to the terminal. Here it's used to process a list of kernels and select individual ones:
The dialog title reports the number of kernels, their total size and the current kernel version booted. The current kernel is excluded from the title's totals and does not appear the kernel list.
The Modified Date is normally the date the kernel was released. On my system that date is "touched" every time the kernel is booted using a cron reboot script (How do you find out when a specific kernel version was last booted?).
For each kernel its size within the
/boot
directory is reported. Then the kernel's total size is summed for the three directories; /boot, /usr/src/kernel_version and /lib/modules/kernel_version.If no parameter is passed to
rm-kernels
the total size is estimated and the title shows "Est. Total". This saves time running thedu
command which can take 30 seconds to 90 minutes depending on how many kernels you have and whether you have an SSD or an HDD. If you pass any parameter at all thendu
is used to obtain kernel sizes and the title shows "Real Total" as the sample screen above illustrates.apt-get purge
gives you chance to abortYou get to view everything that will be purged by
apt purge
and are given the option to proceed or abort:apt purge
reports 1,784 MB will be freed but the real total 2,379 MB. I don't know why it is different.Rather than purging kernels one at a time and
update-grub
being repetitively called in time-consuming loop, the selections are purged all at once.The Code
Copy this code to a file named
rm-kernels
in/usr/local/bin
:NOTE: You need to use
sudo
powers to save the file with your favorite editor.To make file executable use:
Server Version
rm-kernels-server
is the server version to selectively delete kernels all at once. Instead of a GUI (graphical) dialog box a text-based dialog box is used to select kernels to purge.Before running the script you need to install the dialog function using:
sudo apt install dialog
Dialog is in the default Ubuntu Desktop installation but not in Ubuntu Server.
Sample screen
rm-kernels-server
bash codeNOTE: In the call to
dialog
the directive--ascii-lines
is passed to replace line-draw extended character set (whichssh
doesn't like) with "+-----+" for drawing boxes. If you do not like this appearance you can use the--no-lines
directive for no box at all. If you aren't usingssh
you can delete the--ascii-lines
and your display will be formated with line-draw characters:July 28, 2017 Updates
The calculated size of each kernel was taken from
/boot/*kernel_version*
which were 5 files totaling ~50 MB. The formula has changed to include the files in/usr/src/*kernel_version*
and/lib/modules/*kernel_version*
. The calculated size for each kernel is now ~400 MB.The default is to estimate the size of files for linux-headers at 125 MB and linux-image at 220 MB because
du
can be painfully slow unless files are in cached in memory. To get the real size usingdu
pass any parameter to the script.The total of all kernel sizes (excluding the current running version which cannot be removed) is now show in the title bar.
The dialog box used to display each Kernel's Last Access Date. This date can get mass overwritten for all kernels during backup or similar operations. The dialog box now shows the Modified Date instead.
August 5, 2017 Updates
The kernel list is now sorted by Kernel Version rather than alpha-numerically.
An additional column has been added for
/boot size
. In the graphical Zenity version the last column changes between "Real Total" and "Est. Total" (Estimated) depending on parameter 1 being passed or not.