Due to boredom and needing something to do to keep my brain from atrophying, I’ve decided to polish some of the rough edges off some of my scripts so that they’re suitable for publication.

here’s the first of them, a perl script to list kernels and other grub menu entries, with numeric indexing suitable for use with grub-set-default and grub-reboot

output looks like this:

0 Debian GNU/Linux
1 Advanced options for Debian GNU/Linux
1>0 Debian GNU/Linux, with Linux 3.9-1-amd64
 1>1 Debian GNU/Linux, with Linux 3.9-1-amd64 (recovery mode)
 1>2 Debian GNU/Linux, with Linux 3.8-2-amd64
 1>3 Debian GNU/Linux, with Linux 3.8-2-amd64 (recovery mode)
 1>4 Debian GNU/Linux, with Linux 3.8-1-amd64
 1>5 Debian GNU/Linux, with Linux 3.8-1-amd64 (recovery mode)
 1>6 Debian GNU/Linux, with Linux 3.7-trunk-amd64
 1>7 Debian GNU/Linux, with Linux 3.7-trunk-amd64 (recovery mode)
 1>8 Debian GNU/Linux, with Linux 3.2.0-4-amd64
 1>9 Debian GNU/Linux, with Linux 3.2.0-4-amd64 (recovery mode)
 2 Network boot (iPXE)
 3 Bootable floppy: LSI
 4 Bootable floppy: freedos-bare
 Default: 0 "Debian GNU/Linux"

(this example tells me i need to uninstall some old kernels)

and here’s the script:

#! /usr/bin/perl 

# parse and list the boot entries in the grub boot menu (grub.cfg)
# $Id:,v 1.8 2013/06/20 10:36:47 cas Exp $

# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Craig Sanders <>
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.

# Partial Revision History:
# 2013-06-20 - better handling of non-numeric default saved entries, code still uglyish but better commented.
# 2013-05-13 - first stab at support for but uglified the code
# 2010-11-03 - rewrote for grub2 (grub.cfg rather than menu.lst)

use strict;

my $cfg = shift || '/boot/grub/grub.cfg';

my $grubenv='/boot/grub/grubenv';

# hash of kernels (menu entry index -> menu entry description) 
# e.g. '4' > 'Debian GNU/Linux, with Linux 3.8-1-amd64'
my %K = ();

# hash of menuentry_id_option -> menuindex
# e.g. 'gnulinux-3.8-1-amd64-advanced-6bb6d228-0581-49ae-9d49-dd148c273ecc' => 4
my %M = ();

# read in the menu entries.

open(MENU,"<", $cfg) || die "couldn't open $cfg for read: $!\n" ;

my $insubmenu = 0;
my $menuitem=0;
my $submenuitem=0;
my $prefix='';
my $menuindex='';
my $kernel='';
my $menuidoption='';

while (<MENU>) {
  #next unless /^\s*menuentry\s/io;
  if ($insubmenu ne 0) {
    next unless /^\s*(menuentry|submenu)\s|^}$/io;
    if (m/^}$/) { $insubmenu = 0 ; $prefix='' ; next };

    $menuindex = $prefix . $submenuitem++;

  } else {
    next unless /^\s*(menuentry|submenu)\s/io;
    if (m/submenu/) {
        $prefix = $menuitem . '>';
        $submenuitem = 0;
        $menuindex = $submenuitem;

    $menuindex = $menuitem++;

  # sometimes the default entry saved in grubenv is the menu_id_option
  # rather than numeric.  The %M hash links menu_id_option to menuindex
  # numbers.
  # e.g. 'gnulinux-3.8-1-amd64-advanced-6bb6d228-0581-49ae-9d49-dd148c273ecc' => 4
  my $line = $_;
  if ($line =~ m/menuentry_id_option/) { 
      $line =~ s/.*menuentry_id_option '//;
      $line =~ s/'.*//;
      $M{$menuidoption} = $menuindex;

  $kernel = $_;

  $K{$menuindex} = $kernel;
  #printf "%4s\t%s\t%s\n", $menuindex, $_, $menuidoption;
  printf "%4s\t%s\t\n", $menuindex, $_;
} ;

# now get the default kernel and boot-once kernel if any

my $saved_entry='';
my $prev_saved_entry='';
my $defk = 0;
my $once = 0;

open(GRUBENV, "<", $grubenv) || die "couldn't open $grubenv for read: $!\n" ;
while (<GRUBENV>) {
  next unless /saved_entry/;
  if (/^saved_entry/) {
    (undef,$saved_entry) = split /=/;
  } elsif (/^prev_saved_entry/) {
    (undef,$prev_saved_entry) = split /=/;

$saved_entry = 0 if ($saved_entry eq '');

if ($prev_saved_entry ne '') {
  $defk = $prev_saved_entry;
  $once = $saved_entry;
} else {
  $defk = $saved_entry;

# if $defk is non-numeric, look up index in %M
if ($defk !~ /^[0-9]+$/) {
  $defk = $M{$defk};

# if $once is non-numeric, look up index in %M
if ($once !~ /^[0-9]+$/) {
  $once = $M{$once};

print "\n  Default: $defk";
print "  \"", $K{$defk}, "\"\n";

if ($prev_saved_entry ne '') {
  print "Boot Once: $once";
  print "  \"", $K{$once}, "\"\n";


  1. mirabilos

    Oh yes, I noticed this “Advanced options for Debian GNU/Linux” too… where does it come from, can we please get rid of it again? It’s absolutely disturbing, especially as the start screen is now almost empty…

    1. cas

      I first encountered it on Ubuntu Natty on some machines and VMs at work. Didn’t like it there or then, either….mostly because it broke my script and I was too lazy to add submenu support and made using grub-set-default and grub-reboot a PITA.

      On those Ubuntu machines, I just disabled it by commenting out the submenu stuff in /etc/grub.d/10_linux.

      It turned up in debian about a month ago, with grub 2.00. There was already a bug report about it (Bug #690538 on I repatched 10_linux to make submenus optional rather than just disable them and added the patch to the bug report. The patch I submitted is not quite complete – you also have to edit /usr/sbin/grub-mkconfig and add GRUB_ENABLE_SUBMENUS to the list of exported variables.

      e.g. at or near line 231 of grub-mkconfig, add GRUB_ENABLE_SUBMENUS \ (the trailing \ is important, it continues the export command)….without this, my patch just disables submenus without making them optional.

      I see you’ve already found that bug report and replied to it – I can only suggest you apply the patch yourself. it’s pretty easy to do, just save the patch from my bug report in /etc/grub.d and run cat 10_linux.patch | patch as root. 10_linux is listed as a conffile in the grub-common package, so the change won’t be overwriten when grub is upgraded….but if you wanted the option of enabling submenus again, you would have to edit /usr/sbin/grub-mkconfig whenever grub-common is upgraded.

      Then I added submenu support to and, apart from not being pleased at how it made the script harder to read and understand, found I just don’t care about submenus any more. I reboot so infrequently that I don’t care what the menu looks like, but I did care about being able to use grub-set-default and grub-reboot easily.

      One of these days™ I’ll get around to registering an account on Savannah and submitting the patch there….the debian maintainer doesn’t seem interested in forwarding the patch upstream.

        1. mirabilos

          Oh great, thanks!

          Sure I can rebuild it myself… but it’d get lost with every new grub upload, that’s why I’m a bit reluctant to do that. (I’ve got a local APT repo alright, but…)

          1. cas

            You don’t need to rebuild the package yourself.

            Just patch the /etc/grub.d/10_linux file in place. It’s a listed conffile for the grub-common package so it won’t be overwritten automatically on upgrade.

            You’ll get the usual Keep, Replace, Show Diff, etc prompt from dpkg instead.

          2. mirabilos

            I guess I could do that for a single machine. But I’d rather have it properly installed everywhere and upgraded automatically correctly. (Think sysadmin scale. Not end user. My machines at home all run MS-DOS and MirBSD ☺)

          3. cas

            you are, of course, entitled to your opinion.

            pragmatically speaking, however, since the patch is likely to be ignored in the grub bug tracker and the debian maintainer doesn’t seem to care about it, your choices are to 1. learn to accept that you’ll be having grub submenus whether you like them or not, 2. maintain a local package yourself in your own repository, or 3. just patch the 10_linux file and push the patched version to multiple servers with scp or pdcp or puppet or similar.

Comments are closed.