Ubuntu – Bash one-liner to delete only old kernels

bashboot-partitionkernel

I've seen lots of threads on how to free space on the /boot partition and that is my objective as well. However, I'm only interested in deleting old kernels and not each one of them but the current one.

I need the solution to be a one-liner since I'll be running the script from Puppet and I don't want to have extra files lying around. So far I got the following:

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

To be more precise, what it does at the moment is the following:

  • List all the linux-* packages and print their names.
  • Only list the ones that have numbers and sort them, returning the reverse result. This way, older kernels are listed last.
  • Print only the results that go after the current kernel
  • Since there are some linux-{image,headers} results, make sure I won't purge anything related to my current kernel
  • Call apt to purge

This works, but I'm sure the solution can be more elegant and that it's safe for a production environment, since at least 20 of our servers run Ubuntu.

Thanks for your time,
Alejandro.

Best Answer

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! :)

  1. 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}-*
    
  2. 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]+'
    
  3. 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 ;)

  4. 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.

Related Question