27

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.

2

8 Answers 8

26

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.

8
  • Thanks for your response @Malte, thanks for your input. I find your two first steps very clarifying and they make the oneliner safer indeed. However, I believe your two last steps ignore the newer kernels and purge them as well. At least, I tried your solution on a server of mine and it'd have uninstalled something unwanted.
    – Alejandro
    Jan 9, 2014 at 19:59
  • That's interesting. Can you PM me the two outputs, one with the command with my third and fourth suggestion included, one without them? Plus the output of uname -r. For me it works fine. It would be interesting to see why it doesn't work in your case. Jan 9, 2014 at 22:32
  • 1
    I tried to PM you but I didn't find any email adress so I'll post my output here: hastebin.com/raleyigowe.vhdl uname -r shows my server is using linux-image-3.8.0-34 Thing is that sometimes a server has downloaded a newer kernel but it hasn't installed it yet. That's why I was looking for a way to remove only old kernels.
    – Alejandro
    Jan 11, 2014 at 11:08
  • This command selected the header file for my current kernel, which seems undesirable. Mar 6, 2017 at 20:36
  • 1
    @MalteSkoruppa, uname -r produces 4.8.0-36-generic , which fails to exclude linux-headers-4.8.0-36 from the list. Mar 8, 2017 at 14:09
11

I wrote this script that removes "linux-*" packages that have lesser version than the currently booted one. I think it is not necessary to test package status. The command asks for confirmation before purging packages. If you don't want that, add -y option to the apt-get command.

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

However, to be able to leave configurable amount of spare kernels, I recommend to use my linux-purge script with --keep option. See here for more information about the script.

1
  • @jamo I love your linux-purge script. However, there's one little bug
    – SebMa
    Jun 16, 2020 at 18:00
3

TL;DR: skip to the bottom.

It IS a little bit longer though. I'll break it down for you:

  1. dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' Just like Malte suggested. Lists the relevant kernel files.
  2. egrep '[0-9]+\.[0-9]+\.[0-9]+' Also suggested by Malte as the safer way to pick out only the kernel files by looking for a version number.
  3. Since we now are possibly listing both the image and the header packages, the package naming can vary so we have this awk workaround which is necessary for the sort awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}' The result is a new column with the version number before the original package name like below:

    $ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}'
    3.11.0-23 linux-headers-3.11.0-23
    3.11.0-23 linux-headers-3.11.0-23-generic
    3.11.0-24 linux-headers-3.11.0-24
    3.11.0-24 linux-headers-3.11.0-24-generic
    3.11.0-26 linux-headers-3.11.0-26
    3.11.0-26 linux-headers-3.11.0-26-generic
    3.11.0-23 linux-image-3.11.0-23-generic
    3.11.0-24 linux-image-3.11.0-24-generic
    3.11.0-26 linux-image-3.11.0-26-generic
    3.8.0-35 linux-image-3.8.0-35-generic
    3.11.0-23 linux-image-extra-3.11.0-23-generic
    3.11.0-24 linux-image-extra-3.11.0-24-generic
    3.11.0-26 linux-image-extra-3.11.0-26-generic
    3.8.0-35 linux-image-extra-3.8.0-35-generic
    
  4. Now we must sort the list in order to prevent uninstalling any newer images than the one that is currently running. sort -k1,1 --version-sort -r giving us this:

    $ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}' | sort -k1,1 --version-sort -r
    3.11.0-26 linux-image-extra-3.11.0-26-generic
    3.11.0-26 linux-image-3.11.0-26-generic
    3.11.0-26 linux-headers-3.11.0-26-generic
    3.11.0-26 linux-headers-3.11.0-26
    3.11.0-24 linux-image-extra-3.11.0-24-generic
    3.11.0-24 linux-image-3.11.0-24-generic
    3.11.0-24 linux-headers-3.11.0-24-generic
    3.11.0-24 linux-headers-3.11.0-24
    3.11.0-23 linux-image-extra-3.11.0-23-generic
    3.11.0-23 linux-image-3.11.0-23-generic
    3.11.0-23 linux-headers-3.11.0-23-generic
    3.11.0-23 linux-headers-3.11.0-23
    3.8.0-35 linux-image-extra-3.8.0-35-generic
    3.8.0-35 linux-image-3.8.0-35-generic
    
  5. Now strip out the current and newer kernel files sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"` giving us this:

    $ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}' | sort -k1,1 --version-sort -r | sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"`
    3.11.0-23 linux-image-extra-3.11.0-23-generic
    3.11.0-23 linux-image-3.11.0-23-generic
    3.11.0-23 linux-headers-3.11.0-23-generic
    3.11.0-23 linux-headers-3.11.0-23
    3.8.0-35 linux-image-extra-3.8.0-35-generic
    3.8.0-35 linux-image-3.8.0-35-generic
    
  6. Now strip off the first column we added with awk '{print $2}' to get exactly what we want:

    $ dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}' | sort -k1,1 --version-sort -r | sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"` | awk '{print $2}'
    linux-image-extra-3.11.0-23-generic
    linux-image-3.11.0-23-generic
    linux-headers-3.11.0-23-generic
    linux-headers-3.11.0-23
    linux-image-extra-3.8.0-35-generic
    linux-image-3.8.0-35-generic
    
  7. Now we can feed that to the package manager to automatically remove everything and reconfigure grub:

    I recommend doing a dry run first (though for your scripting purposes this might not be practical if you have a large environment)

    dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}' | sort -k1,1 --version-sort -r | sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"` | awk '{print $2}' | xargs sudo apt-get --dry-run remove
    

    Now if everything looks good go ahead and actually remove it with:

    dpkg -l linux-{image,headers}-* | awk '/^ii/{print $2}' | egrep '[0-9]+\.[0-9]+\.[0-9]+' | awk 'BEGIN{FS="-"}; {if ($3 ~ /[0-9]+/) print $3"-"$4,$0; else if ($4 ~ /[0-9]+/) print $4"-"$5,$0}' | sort -k1,1 --version-sort -r | sed -e "1,/$(uname -r | cut -f1,2 -d"-")/d" | grep -v -e `uname -r | cut -f1,2 -d"-"` | awk '{print $2}' | xargs sudo apt-get -y purge
    

Once again the whole point of this "one-liner" is to remove only the kernels OLDER than the currently running kernel (which leaves any newly installed kernels still available)

Thanks let me know how this works for you and if you could improve it!

1
1

I got really tired of all this unnecessary complexity and created a Python package that makes the one-liner trivial:

ubuntu-old-kernel-cleanup | xargs sudo apt-get -y purge

Install it with

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

See more at https://github.com/mrts/ubuntu-old-kernel-cleanup.

Hope this helps others too.

5
  • Tried this but I get error: "ubuntu-old-kernel-cleanup: command not found"
    – Grant
    Apr 27, 2016 at 12:42
  • Then it is not in your executable search path. Did you install it with sudo pip install ... as described above? sudo is actually important, otherwise pip will install it in some user directory and shell may not search this directory for executable files.
    – mrts
    Apr 28, 2016 at 10:59
  • @mrts I've just upvoted your solution because it works with python2. However your python module does not support python3 yet : github.com/mrts/ubuntu-old-kernel-cleanup/issues/3
    – SebMa
    Jun 16, 2020 at 18:57
  • 1
    @SebMa, thanks for the heads-up! I just ported it to Python 3, please test and tell in GitHub issue if it works for you.
    – mrts
    Jun 24, 2020 at 12:44
  • 1
    @mrts Works fine, congratulations :)
    – SebMa
    Jun 24, 2020 at 16:17
1

You can simply list the /boot directory to see the kernel versions you have using 'ls' command. Then use 'sudo apt-get -y purge "xxx"' where "xxx" is replaced with the version number you want to remove. Take care that it is not the version you are currently running!!.

1
  • 1
    They wanted a one-liner command. You solution needs more than 1 line
    – Anwar
    Sep 11, 2016 at 15:14
1

Install bikeshed (apt install bikeshed) and call purge-old-kernels as root.

$ sudo purge-old-kernels
1
0

A quick answer, explanation upon request:

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
3
  • 2
    I suggest (or, if you prefer, request) expanding this to include the explanation. :) May 13, 2015 at 11:53
  • @EliahKagan, I think the script does not make sense. Why skip packages that are not desired to be installed? The script might remove some newer kernels than the current one, or save some older one by sed '$d'command. The script does not remove any linux-header packages or other packages related to the kernel packages to be removed, besides it does not delete configuration files of the packages. I would recommend using my answer, instead.
    – jarno
    May 15, 2015 at 22:56
  • @EliahKagan Actally the script does not remove any packages, but prints (by echo) the apt-get command that you could run.
    – jarno
    May 15, 2015 at 23:04
0

This one-line command works fine.

If you want you can always remove the -y option to better check the results before appling the changes.

# 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)

For obtain the desired result, I separed the search for linux-image and linux-headers.

The command 2>/dev/null removed the warnings.

The word instal used within grep is good for at least 3 different languages: italian (installato), english (installed) and spanish (instalado).

I used head for exclude the current kernel.

With cut -d/ -f1 we have the complete package name.

I hope this information will help many people that are using Linux.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .