56

Is there a way of retrieving list of "menuentries" titles of GRUB?

I need a results in a format that grub-set-default will accept. eg.

"Ubuntu"
"Memory test (memtest86+)"
etc.
2

8 Answers 8

77

Using awk

awk -F\' '/menuentry / {print $2}' /boot/grub/grub.cfg 

gives you a full list of all menu entries in grub.cfg.

1
  • This answer, though accepted, doesn't answer the question anymore. If submenus are used, which is standard for all but the default choice in the grub menu, you should not use the output as input to grub-set-default. (You get a warning from update-grub.)
    – hansfn
    Apr 19, 2023 at 12:34
22

Color Screen shot (short version)

Use mouse scroll wheel, Home, End, PgUp, PgDn, and keys to navigate the menu.

grub-menu.sh

Text Screen shot (long version)

The bash script uses whiptail instead of dialog to display the menu. One advantage is you can copy the terminal image to the clipboard as text and paste it into this website as text. Other advantages include:

  • Mouse scroll wheel support
  • Faster performance
  • dialog is not installed by default in Ubuntu Server or Lubuntu. whiptail is included by default.

Here's a text screen shot:

Grub Version: 2.02~beta2-36ubuntu3.15


        ┌─────────┤ Use arrow, page, home & end keys. Tab toggle option ├──────────┐
        │ Menu No. --------------- Menu Name ---------------                       │ 
        │                                                                          │ 
        │     0    Ubuntu                                                     ↑    │ 
        │     1    Advanced options for Ubuntu                                ▮    │ 
        │     1>0  Ubuntu, with Linux 4.14.31-041431-generic                  ▒    │ 
        │     1>1  Ubuntu, with Linux 4.14.31-041431-generic (upstart)        ▒    │ 
        │     1>2  Ubuntu, with Linux 4.14.31-041431-generic (recovery mode)  ▒    │ 
        │     1>3  Ubuntu, with Linux 4.14.30-041430-generic                  ▒    │ 
        │     1>4  Ubuntu, with Linux 4.14.30-041430-generic (upstart)        ▒    │ 
        │     1>5  Ubuntu, with Linux 4.14.30-041430-generic (recovery mode)  ▒    │ 
        │     1>6  Ubuntu, with Linux 4.14.27-041427-generic                  ▒    │ 
        │     1>7  Ubuntu, with Linux 4.14.27-041427-generic (upstart)        ▒    │ 
        │     1>8  Ubuntu, with Linux 4.14.27-041427-generic (recovery mode)  ▒    │ 
        │     1>9  Ubuntu, with Linux 4.14.24-041424-generic                  ▒    │ 
        │     1>10 Ubuntu, with Linux 4.14.24-041424-generic (upstart)        ▒    │ 
        │     1>11 Ubuntu, with Linux 4.14.24-041424-generic (recovery mode)  ▒    │ 
        │     1>12 Ubuntu, with Linux 4.14.23-041423-generic                  ▒    │ 
        │     1>13 Ubuntu, with Linux 4.14.23-041423-generic (upstart)        ↓    │ 
        │                                                                          │ 
        │                                                                          │ 
        │                   <Display Grub Boot>        <Exit>                      │ 
        │                                                                          │ 
        └──────────────────────────────────────────────────────────────────────────┘ 

Highlight entry and press Enter

Use the navigation keys to highlight an option and press Enter to see the pre-kernel drivers loaded by grub and the boot parameters passed by grub to the kernel when booting it:

menuentry 'Ubuntu, with Linux 4.14.27-041427-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-4.14.27-041427-generic-advanced-f3f8e7bc-b337-4194-88b8-3a513f6be55b' {
recordfail
savedefault
load_video
gfxmode $linux_gfx_mode
insmod gzio
if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
insmod part_gpt
insmod ext2
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root f3f8e7bc-b337-4194-88b8-3a513f6be55b
else
search --no-floppy --fs-uuid --set=root f3f8e7bc-b337-4194-88b8-3a513f6be55b
fi
echo 'Loading Linux 4.14.27-041427-generic ...'
linux /boot/vmlinuz-4.14.27-041427-generic root=UUID=f3f8e7bc-b337-4194-88b8-3a513f6be55b ro quiet splash loglevel=0 vga=current udev.log-priority=3 fastboot kaslr acpiphp.disable=1 crashkernel=384M-2G:128M,2G-:256M $vt_handoff
echo 'Loading initial ramdisk ...'
initrd /boot/initrd.img-4.14.27-041427-generic
}
Press <Enter> to continue

grub-menu.sh bash script

grub-menu.sh only has one option to tweak:

# Default for hide duplicate and triplicate options with (upstart) and (recovery mode)?
HideUpstartRecovery=false

Set the value to true (hide the extra entries) or false (list all entries).

The default format can be overridden when calling the script using:

grub-menu.sh short

or:

grub-menu.sh long

The code:

#!/bin/bash

# NAME: grub-menu.sh
# PATH: $HOME/bin
# DESC: Written for AU Q&A: https://askubuntu.com/q/1019213/307523
# DATE: Apr 5, 2018. Modified: July 27, 2019
# UPDT: Scroll bar was outside of dialog box. Move windo border line.

# $TERM variable may be missing when called via desktop shortcut
CurrentTERM=$(env | grep TERM)
if [[ $CurrentTERM == "" ]] ; then
    notify-send --urgency=critical "$0 cannot be run from GUI without TERM environment variable."
    exit 1
fi

# Send output to secondary terminal such that previous history isn't cleared on exit
tput smcup

AllMenusArr=()      # All menu options.
# Default for hide duplicate and triplicate options with (upstart) and (recovery mode)?
HideUpstartRecovery=false
if [[ $1 == short ]] ; then
    HideUpstartRecovery=true    # override default with first passed parameter "short"
elif [[ $1 == long ]] ; then
    HideUpstartRecovery=false   # override default with first passed parameter "long"
fi
SkippedMenuEntry=false  # Don't change this value, automatically maintained
InSubMenu=false     # Within a line beginning with `submenu`?
InMenuEntry=false   # Within a line beginning with `menuentry` and ending in `{`?
NextMenuEntryNo=0   # Next grub internal menu entry number to assign
# Major / Minor internal grub submenu numbers, ie `1>0`, `1>1`, `1>2`, etc.
ThisSubMenuMajorNo=0
NextSubMenuMinorNo=0
CurrTag=""          # Current grub internal menu number, zero based
CurrText=""         # Current grub menu option text, ie "Ubuntu", "Windows...", etc.
SubMenuList=""      # Only supports 10 submenus! Numbered 0 to 9. Future use.

while read -r line; do
    # Example: "           }"
    BlackLine="${line//[[:blank:]]/}" # Remove all whitespace
    if [[ $BlackLine == "}" ]] ; then
        # Add menu option in buffer
        if [[ $SkippedMenuEntry == true ]] ; then
            NextSubMenuMinorNo=$(( $NextSubMenuMinorNo + 1 ))
            SkippedMenuEntry=false
            continue
        fi
        if [[ $InMenuEntry == true ]] ; then
            InMenuEntry=false
            if [[ $InSubMenu == true ]] ; then
                NextSubMenuMinorNo=$(( $NextSubMenuMinorNo + 1 ))
            else
                NextMenuEntryNo=$(( $NextMenuEntryNo + 1 ))
            fi
        elif [[ $InSubMenu == true ]] ; then
            InSubMenu=false
            NextMenuEntryNo=$(( $NextMenuEntryNo + 1 ))
        else
            continue # Future error message?
        fi
        # Set maximum CurrText size to 68 characters.
        CurrText="${CurrText:0:67}"
        AllMenusArr+=($CurrTag "$CurrText")
    fi

    # Example: "menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu" ...
    #          "submenu 'Advanced options for Ubuntu' $menuentry_id_option" ...
    if [[ $line == submenu* ]] ; then
        # line starts with `submenu`
        InSubMenu=true
        ThisSubMenuMajorNo=$NextMenuEntryNo
        NextSubMenuMinorNo=0
        SubMenuList=$SubMenuList$ThisSubMenuMajorNo
        CurrTag=$NextMenuEntryNo
        CurrText="${line#*\'}"
        CurrText="${CurrText%%\'*}"
        AllMenusArr+=($CurrTag "$CurrText") # ie "1 Advanced options for Ubuntu"

    elif [[ $line == menuentry* ]] && [[ $line == *"{"* ]] ; then
        # line starts with `menuentry` and ends with `{`
        if [[ $HideUpstartRecovery == true ]] ; then
            if [[ $line == *"(upstart)"* ]] || [[ $line == *"(recovery mode)"* ]] ; then
                SkippedMenuEntry=true
                continue
            fi
        fi
        InMenuEntry=true
        if [[ $InSubMenu == true ]] ; then
            : # In a submenu, increment minor instead of major which is "sticky" now.
            CurrTag=$ThisSubMenuMajorNo">"$NextSubMenuMinorNo
        else
            CurrTag=$NextMenuEntryNo
        fi
        CurrText="${line#*\'}"
        CurrText="${CurrText%%\'*}"

    else
        continue    # Other stuff - Ignore it.
    fi

done < /boot/grub/grub.cfg

LongVersion=$(grub-install --version)
ShortVersion=$(echo "${LongVersion:20}")
DefaultItem=0

if [[ $HideUpstartRecovery == true ]] ; then
    MenuText="Menu No.     ----------- Menu Name -----------"
else
    MenuText="Menu No. --------------- Menu Name ---------------"
fi

while true ; do

    Choice=$(whiptail --clear \
        --title "Use arrow, page, home & end keys. Tab toggle option" \
        --backtitle "Grub Version: $ShortVersion" \
        --ok-button "Display Grub Boot" \
        --cancel-button "Exit" \
        --default-item "$DefaultItem" \
        --menu "$MenuText" 24 80 16 \
        "${AllMenusArr[@]}" \
        2>&1 >/dev/tty)

    clear
    if [[ $Choice == "" ]]; then break ; fi
    DefaultItem=$Choice

    for (( i=0; i < ${#AllMenusArr[@]}; i=i+2 )) ; do
        if [[ "${AllMenusArr[i]}" == $Choice ]] ; then
            i=$i+1
            MenuEntry="menuentry '"${AllMenusArr[i]}"'"
            break
        fi
    done

    TheGameIsAfoot=false
    while read -r line ; do
        if [[ $line = *"$MenuEntry"* ]]; then TheGameIsAfoot=true ; fi
        if [[ $TheGameIsAfoot == true ]]; then
            echo $line
            if [[ $line = *"}"* ]]; then break ; fi
        fi
    done < /boot/grub/grub.cfg

    read -p "Press <Enter> to continue"

done

# Restore output to primary terminal
tput rmcup

exit 0

15

Exactly this has been annoying me for over a year - so I did a quick and dirty script to do it. Hope this helps others?

This stacks the names in a push/pop queue, but not the menu indexing so it could be better but I have gone as far as I could be bothered.

gawk  'BEGIN {                                                                                                                       
  l=0                                                                                                                                
  menuindex= 0                                                                                                                       
  stack[t=0] = 0                                                                                                                     
}                                                                                                                                    

function push(x) { stack[t++] = x }                                                                                                  

function pop() { if (t > 0) { return stack[--t] } else { return "" }  }                                                              

{                                                                                                                                    

if( $0 ~ /.*menu.*{.*/ )                                                                                                             
{                                                                                                                                    
  push( $0 )                                                                                                                         
  l++;                                                                                                                               

} else if( $0 ~ /.*{.*/ )                                                                                                            
{                                                                                                                                    
  push( $0 )                                                                                                                         

} else if( $0 ~ /.*}.*/ )                                                                                                            
{                                                                                                                                    
  X = pop()                                                                                                                          
  if( X ~ /.*menu.*{.*/ )                                                                                                            
  {                                                                                                                                  
     l--;                                                                                                                            
     match( X, /^[^'\'']*'\''([^'\'']*)'\''.*$/, arr )                                                                               

     if( l == 0 )                                                                                                                    
     {                                                                                                                               
       print menuindex ": " arr[1]                                                                                                   
       menuindex++                                                                                                                   
       submenu=0                                                                                                                     
     } else                                                                                                                          
     {                                                                                                                               
       print "  " (menuindex-1) ">" submenu " " arr[1]                                                                               
       submenu++                                                                                                                     
     }                                                                                                                               
  }                                                                                                                                  
}                                                                                                                                    

}' /boot/grub/grub.cfg

Here you can see a screen grab from my box showing it running enter image description here

As has been asked for in a comment from 2019/8 I mentioned above doing a "temp boot". This idea was what started originally my script, and the temp boot method came from another post and it goes something like this

  1. Set GRUB_DEFAULT to GRUB_DEFAULT=saved # vi /etc/default/grub
  2. Update the grub config in /boot # sudo update-grub
  3. Set default OS to load (this will load every time you reboot machine) # sudo grub-set-default 0
  4. When need to load other OS (number is a menu number of OS as in /boot/grub/grub.cfg, this will load other OS only once during next reboot - reboot to be started manually): # sudo grub-reboot 4

Ref: https://ubuntuforums.org/showthread.php?t=1310463

4
  • +1 I like the push and pop commands in your answer. Wish I had known them before I wrote my answer. Apr 6, 2018 at 23:55
  • Can you also share grub-tempboot.sh?
    – Autodidact
    Sep 5, 2019 at 11:56
  • test 1. Set GRUB_DEFAULT to GRUB_DEFAULT=saved ( /etc/default/grub ) 2. Update the grub config in /boot #sudo update-grub 3. Set default OS to load (this will load every time you reboot machine) # sudo grub-set-default 0 4. When need to load other OS (number is a menu number of OS as in /boot/grub/grub.cfg, this will load other OS only once during next reboot - reboot to be started manually): sudo grub-reboot 4 Sep 9, 2019 at 7:09
  • The awk script didn't work for me on Ubuntu 22.02 (with grub 2). I made a simpler version without the pushing and poping that you can find at gist.github.com/hansfn/c15e700eff2994c8f14b52492f210321 However, I think the best answer is Max's one-liner (currently below) - askubuntu.com/a/1121712/1689804
    – hansfn
    Apr 19, 2023 at 13:04
14

I just found out that you can just use auto-complete (pressing tab twice) on commands like grub-reboot or grub-set-default and it'll give you a list of the available boot menu entries. You can then just copy the title you want literally, you don't need to use a number.

Unfortunately, for some reason my Windows 7 installation doesn't show up that way.

5
  • 1
    You can see what it does in /usr/share/bash-completion/completions/grub-reboot and /usr/share/bash-completion/completions/grub-set-default scripts. May 3, 2020 at 21:20
  • in fact it just makes awk -F "[\"']" '/menuentry/ { print $2 }' /boot/grub/grub.cfg but why it doesn't see Windows is unclear for me yet May 3, 2020 at 21:26
  • 1
    careful with submenus though, one may still need something like grub-reboot "Advanced options for Ubuntu>Ubuntu, with Linux 4.15.0-108-generic"
    – ricab
    Jul 3, 2020 at 16:00
  • yeah! tab completion plus the comment about the sub menus worked perfectly. Aug 20, 2022 at 3:37
  • Yes, it's unfortunate that the default tab completion coming with Grub doesn't handle sub menus. You have to manually glue together the strings. The awk one-liner from Max (below) is better, but can't be used directly (in the tab completion) as it prints both the "n>m" identifier and the menu entry title.
    – hansfn
    Apr 19, 2023 at 13:17
12

An other awk one-liner which handles submenus :

 awk -F\' '$1=="menuentry " || $1=="submenu " {print i++ " : " $2}; /\smenuentry / {print "\t" i-1">"j++ " : " $2};' /boot/grub/grub.cfg

This might be fallible thought, because \s (any whitespace character) before the menuentry keyword is used to identify submenu entries.

1
  • This is the best, quick solution - thanks. Upvoting so other people might find it ;-) PS! Assuming your awk supports \s, you can make it slightly more robust by replace \t with \s - accepting indents with spaces too. We still require root menu entries (and sub menues) to have no indents, though.
    – hansfn
    Apr 19, 2023 at 13:01
9

This should be the right "string"...

awk -F\' '/^menuentry / {print $2}' /boot/grub/grub.cfg|cat -n|awk '{print $1-1,$1="",$0}'

The last pipe delete white space at the beginning of each row and correct the number sequence in accordance with grub number entry. Sample output:

0   Ubuntu
1   Memory test (memtest86+)
2   Memory test (memtest86+, serial console 115200)
3   Windows 10 (su /dev/sdc1)

If you like to see all entries, also the advanced, you could use

awk -F\' '/(^|| )nuentry / {print $2}' /boot/grub/grub.cfg|cat -n|awk '{print $1-1,$1="",$0}'

but the numbers sequence isn't suitable for use with grub-set-default.

3
  • 7
    This only shows the top level menu. The Advanced Options sub-menu is missing. Jan 24, 2018 at 23:52
  • 1
    Could you explain what the piped commands do and how it is better than the accepted answer?
    – Melebius
    Jan 25, 2018 at 10:00
  • 1
    For me, this worked better as it prints also the entry numbers
    – Zac
    Jan 29, 2018 at 16:08
1

Solution 1 - Using awk command:

terminal input:

awk -F\' '/^menuentry/ || /^submenu/ {print "  " $2};/[[:space:]]menuentry[[:space:]]/ {print "       " $2}' /boot/grub/grub.cfg

terminal output:

  Ubuntu
  Advanced options for Ubuntu
       Ubuntu, with Linux 6.4.10
       Ubuntu, with Linux 6.4.10 (recovery mode)
       Ubuntu, with Linux 6.2.0-26-generic
       Ubuntu, with Linux 6.2.0-26-generic (recovery mode)
       Ubuntu, with Linux 5.19.0-50-generic
       Ubuntu, with Linux 5.19.0-50-generic (recovery mode)
  Windows Boot Manager (on /dev/nvme0n1p2)
  UEFI Firmware Settings



Solution 2 - Using awk command with script:

function show_grub_menu {
    echo ""
    grub-install --version | awk '{print $2,$3}'
    echo ""
    awk -F\' '
        /^menuentry/ || /^submenu/ {print "  " $2};
        /[[:space:]]menuentry[[:space:]]/ {print "       " $2}
    ' /boot/grub/grub.cfg
}


source the script and run in teminal:
$ show_grub_menu 

(GRUB) 2.06-2ubuntu7.2

  Ubuntu
  Advanced options for Ubuntu
       Ubuntu, with Linux 6.4.10
       Ubuntu, with Linux 6.4.10 (recovery mode)
       Ubuntu, with Linux 6.2.0-26-generic
       Ubuntu, with Linux 6.2.0-26-generic (recovery mode)
       Ubuntu, with Linux 5.19.0-50-generic
       Ubuntu, with Linux 5.19.0-50-generic (recovery mode)
  Windows Boot Manager (on /dev/nvme0n1p2)
  UEFI Firmware Settings
0

On EL7:

grub2-mkconfig 2> /dev/null | grep ^menu 

produces

menuentry 'CentOS Linux (6.2.9-1.el7.elrepo.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-6.2.9-1.el7.elrepo.x86_64-advanced-c68ae2c5-d205-43f6-9a7e-77ac25a0561f' {
menuentry 'CentOS Linux (5.15.0-105.119.1.el9uek-LXG.x86_64+) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-5.15.0-105.119.1.el9uek-LXG.x86_64+-advanced-c68ae2c5-d205-43f6-9a7e-77ac25a0561f' {
menuentry 'CentOS Linux (5.15.0-7.86.6.1.el9uek.x86_64-TEST+) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-5.15.0-7.86.6.1.el9uek.x86_64-TEST+-advanced-c68ae2c5-d205-43f6-9a7e-77ac25a0561f' {
menuentry 'CentOS Linux (5.15.0-7.86.6.1.el9uek.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-5.15.0-7.86.6.1.el9uek.x86_64-advanced-c68ae2c5-d205-43f6-9a7e-77ac25a0561f' {
menuentry 'CentOS Linux (4.19.93) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-4.19.93-advanced-c68ae2c5-d205-43f6-9a7e-77ac25a0561f' {
menuentry 'CentOS Linux (4.1.42-5.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-4.1.42-5.el7.x86_64-advanced-c68ae2c5-d205-43f6-9a7e-77ac25a0561f' {
menuentry 'CentOS Linux (0-rescue-6eeaf0d9e7104fc8a1077a05e2554ace) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-0-rescue-6eeaf0d9e7104fc8a1077a05e2554ace-advanced-c68ae2c5-d205-43f6-9a7e-77ac25a0561f' {
1
  • This only works as root. Aug 4, 2023 at 9:18

You must log in to answer this question.

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