後半パーティションにUbuntuのISOイメージを保存しておき、そこからブート出来るようにしておくと便利です。その際の、GRUBメニューの現在の状況を確認したり、エントリー追加や削除を簡単に出来るようにスクリプトを作成してみました。

ISOと同じパーティションに保存
スクリプトの実行はどこでも構いませんが、ISOイメージのあるパーティションに保存しておけば、クリーンインストールした際にもすぐに実行出来て便利だと思います。
nano grub-manage.sh
sudo bash grub-manage.sh
#!/usr/bin/env bash
# ============================================================
# grub-manage.sh — GRUB エントリー管理スクリプト (Ubuntu)
#
# 機能:
# 1. ISO ループブートエントリーの追加
# 2. 不要エントリーの削除 (40_custom 由来のみ)
# 3. 何もせず終了
#
# 注意:
# バックアップは /root/grub-backups/ に保存する。
# /etc/grub.d/ に置くと update-grub が実行してエントリーが復活する。
# ============================================================
set -euo pipefail
CUSTOM_FILE="${CUSTOM_FILE:-/etc/grub.d/40_custom}"
BACKUP_DIR="/root/grub-backups"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
info() { echo -e "${CYAN}[INFO]${RESET} $*"; }
success() { echo -e "${GREEN}[OK]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
error() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
die() { error "$*"; exit 1; }
hr() { echo -e "${CYAN}$(printf '=%.0s' {1..64})${RESET}"; }
hr_thin() { echo -e "${CYAN}$(printf -- '-%.0s' {1..64})${RESET}"; }
confirm() {
local ans
while true; do
echo -en "${YELLOW}[確認]${RESET} $1 [y/N]: "
read -r ans
case "$ans" in
[yY]*) return 0 ;;
[nN]*|"") return 1 ;;
*) echo " y または n を入力してください。" ;;
esac
done
}
check_root() {
[[ $EUID -eq 0 ]] || die "sudo で実行してください。\n 例: sudo bash $0"
}
backup_file() {
local src="$1" base
base=$(basename "$src")
mkdir -p "$BACKUP_DIR"
local dst="${BACKUP_DIR}/${base}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$src" "$dst"
echo "$dst"
}
# ============================================================
# grub.cfg のパスを返す
# ============================================================
find_grub_cfg() {
local f
for f in /boot/grub/grub.cfg /boot/grub2/grub.cfg /boot/efi/EFI/ubuntu/grub.cfg; do
[[ -f "$f" ]] && echo "$f" && return
done
echo ""
}
# ============================================================
# menuentry/submenu の表示名を1行から抽出
# ============================================================
_SQ="'"
_DQ='"'
extract_entry_name() {
local line="$1"
local pat="(menuentry|submenu)[[:space:]]+[${_SQ}${_DQ}]([^${_SQ}${_DQ}]*)[${_SQ}${_DQ}]"
if [[ "$line" =~ $pat ]]; then
echo "${BASH_REMATCH[2]}"
else
echo ""
fi
}
# ============================================================
# grub.cfg をパースしてエントリー情報を配列に格納
#
# ENTRY_NAMES[] 表示名
# ENTRY_CLASSES[] toplevel | submenu_header | sub_entry
# ENTRY_GRUB_IDS[] grub ID (0, 1, 1>0 など)
# ENTRY_SOURCES[] custom | auto
# ============================================================
ENTRY_NAMES=()
ENTRY_CLASSES=()
ENTRY_GRUB_IDS=()
ENTRY_SOURCES=()
parse_grub_cfg() {
local cfg_file="$1"
# grub.cfg の ### BEGIN /etc/grub.d/40_custom ### セクション内の
# エントリー名を収集する。
# バックアップファイル (40_custom.bak.*) 由来のセクションは
# ファイル名が一致しないため自動的に除外される。
declare -gA CUSTOM_ENTRY_MAP=()
local in_cs=0
while IFS= read -r line; do
if [[ "$line" =~ ^###\ BEGIN\ (.+)\ ###$ ]]; then
[[ "${BASH_REMATCH[1]}" == "$CUSTOM_FILE" ]] && in_cs=1 || in_cs=0
continue
fi
[[ "$line" =~ ^###\ END\ (.+)\ ###$ ]] && { in_cs=0; continue; }
if [[ $in_cs -eq 1 ]]; then
local cn; cn=$(extract_entry_name "$line")
[[ -n "$cn" ]] && CUSTOM_ENTRY_MAP["$cn"]=1
fi
done < "$cfg_file"
ENTRY_NAMES=()
ENTRY_CLASSES=()
ENTRY_GRUB_IDS=()
ENTRY_SOURCES=()
local top_index=-1 in_submenu=0 sub_index=-1 sub_top_index=-1
while IFS= read -r line; do
if [[ $in_submenu -eq 0 ]] && [[ "$line" =~ ^submenu[[:space:]] ]]; then
((top_index++)) || true
sub_top_index=$top_index; in_submenu=1; sub_index=-1
local sname; sname=$(extract_entry_name "$line")
local src="auto"; [[ -n "${CUSTOM_ENTRY_MAP[$sname]+_}" ]] && src="custom"
ENTRY_NAMES+=("$sname"); ENTRY_CLASSES+=("submenu_header")
ENTRY_GRUB_IDS+=("$top_index"); ENTRY_SOURCES+=("$src")
continue
fi
if [[ $in_submenu -eq 1 ]] && [[ "$line" =~ ^'}'[[:space:]]*$ ]]; then
in_submenu=0; sub_top_index=-1; continue
fi
if [[ $in_submenu -eq 1 ]] && [[ "$line" =~ ^[[:space:]]+menuentry[[:space:]] ]]; then
((sub_index++)) || true
local ename; ename=$(extract_entry_name "$line")
local src="auto"; [[ -n "${CUSTOM_ENTRY_MAP[$ename]+_}" ]] && src="custom"
ENTRY_NAMES+=("$ename"); ENTRY_CLASSES+=("sub_entry")
ENTRY_GRUB_IDS+=("${sub_top_index}>${sub_index}"); ENTRY_SOURCES+=("$src")
continue
fi
if [[ $in_submenu -eq 0 ]] && [[ "$line" =~ ^menuentry[[:space:]] ]]; then
((top_index++)) || true
local ename; ename=$(extract_entry_name "$line")
local src="auto"; [[ -n "${CUSTOM_ENTRY_MAP[$ename]+_}" ]] && src="custom"
ENTRY_NAMES+=("$ename"); ENTRY_CLASSES+=("toplevel")
ENTRY_GRUB_IDS+=("$top_index"); ENTRY_SOURCES+=("$src")
continue
fi
done < "$cfg_file"
}
# ============================================================
# エントリー一覧を表示
# ============================================================
print_entries() {
if [[ ${#ENTRY_NAMES[@]} -eq 0 ]]; then
warn "menuentry が見つかりませんでした。"
return
fi
printf "\n ${BOLD}%-4s %-14s %-12s %-8s %s${RESET}\n" \
"No." "GRUB ID" "種別" "出所" "エントリー名"
hr_thin
for i in "${!ENTRY_NAMES[@]}"; do
local class="${ENTRY_CLASSES[$i]}" src="${ENTRY_SOURCES[$i]}"
local grub_id="${ENTRY_GRUB_IDS[$i]}" name="${ENTRY_NAMES[$i]}"
local type_label
case "$class" in
toplevel) type_label="${GREEN}エントリー${RESET}" ;;
submenu_header) type_label="${YELLOW}サブメニュー${RESET}" ;;
sub_entry) type_label="${CYAN} └子エントリ${RESET}" ;;
esac
local src_label
[[ "$src" == "custom" ]] \
&& src_label="${GREEN}custom${RESET}" \
|| src_label="${CYAN}auto${RESET} "
local display_name="$name"
[[ "$class" == "sub_entry" ]] && display_name=" ${name}"
printf " ${BOLD}%3d${RESET} %-14s %-22b %-16b %s\n" \
"$i" "$grub_id" "$type_label" "$src_label" "$display_name"
done
echo ""
info "出所: ${GREEN}custom${RESET} = 40_custom(削除可能) ${CYAN}auto${RESET} = 自動生成(削除不可)"
}
# ============================================================
# 現在の GRUB 設定を表示
# ============================================================
print_current_settings() {
hr
echo -e "${BOLD}■ 現在の GRUB 設定${RESET}"
hr_thin
local default_grub="/etc/default/grub"
if [[ -f "$default_grub" ]]; then
local dv tv
dv=$(grep '^GRUB_DEFAULT=' "$default_grub" | cut -d= -f2 | tr -d '"' || echo "未設定")
tv=$(grep '^GRUB_TIMEOUT=' "$default_grub" | cut -d= -f2 | tr -d '"' || echo "未設定")
echo -e " GRUB_DEFAULT : ${BOLD}${dv}${RESET}"
echo -e " GRUB_TIMEOUT : ${BOLD}${tv}${RESET} 秒"
fi
local env_file
for env_file in /boot/grub/grubenv /boot/grub2/grubenv; do
[[ -f "$env_file" ]] || continue
local saved next rf
saved=$(grub-editenv "$env_file" list 2>/dev/null | grep '^saved_entry=' | cut -d= -f2 || true)
next=$(grub-editenv "$env_file" list 2>/dev/null | grep '^next_entry=' | cut -d= -f2 || true)
rf=$(grub-editenv "$env_file" list 2>/dev/null | grep '^recordfail=' | cut -d= -f2 || true)
echo -e " saved_entry : ${BOLD}${saved:-(なし)}${RESET}"
echo -e " next_entry : ${BOLD}${next:-(なし)}${RESET}"
if [[ "${rf}" == "1" ]]; then
echo -e " recordfail : ${RED}${BOLD}1(残存)${RESET}"
warn "recordfail=1 が残っています。次回起動時に影響する場合があります。"
else
echo -e " recordfail : ${GREEN}なし${RESET}"
fi
break
done
echo ""
}
# ============================================================
# 1: ISO ループブートエントリーの追加
# ============================================================
action_add_iso() {
hr
echo -e "${BOLD}■ ISO ループブートエントリーの追加${RESET}"
hr_thin
echo ""
# パーティション選択
info "パーティション一覧:"
echo ""
mapfile -t part_list < <(
lsblk -lno NAME,SIZE,FSTYPE,LABEL,MOUNTPOINTS \
| grep -v '^loop' \
| awk 'NF && $1 ~ /[0-9]$/ { print $0 }'
)
[[ ${#part_list[@]} -eq 0 ]] && die "パーティションが見つかりませんでした。"
printf " ${BOLD}%-4s %s${RESET}\n" "No." "NAME SIZE FSTYPE LABEL MOUNTPOINTS"
hr_thin
for i in "${!part_list[@]}"; do
printf " ${CYAN}[%2d]${RESET} %s\n" "$((i+1))" "${part_list[$i]}"
done
echo ""
local part_dev part_uuid
while true; do
echo -en "${BOLD}番号を入力してください${RESET} [1-${#part_list[@]}]: "
read -r sel
if ! [[ "$sel" =~ ^[0-9]+$ ]] || (( sel < 1 || sel > ${#part_list[@]} )); then
error "1〜${#part_list[@]} の番号を入力してください。"; continue
fi
local pname; pname=$(echo "${part_list[$((sel-1))]}" | awk '{print $1}')
part_dev="/dev/${pname}"
[[ -b "$part_dev" ]] || { error "${part_dev} はブロックデバイスではありません。"; continue; }
local fstype; fstype=$(lsblk -no FSTYPE "$part_dev" 2>/dev/null || true)
if [[ "$fstype" != "ext4" ]]; then
warn "${part_dev} は ${fstype:-不明} です(ext4 を想定)。"
confirm "このまま続けますか?" || continue
fi
echo ""
lsblk -o NAME,SIZE,FSTYPE,UUID,MOUNTPOINTS "$part_dev" 2>/dev/null || true
echo ""
confirm "${part_dev} を使用しますか?" && break
done
part_uuid=$(blkid -s UUID -o value "$part_dev")
[[ -n "$part_uuid" ]] || die "UUID を取得できませんでした: ${part_dev}"
success "パーティション: ${part_dev} UUID: ${part_uuid}"
echo ""
# ISO ファイルの検出と選択
local mount_point="/mnt/_isoboot_tmp_$$"
mkdir -p "$mount_point"
mount "$part_dev" "$mount_point"
info "${part_dev} を ${mount_point} にマウントしました"
echo ""
mapfile -t iso_candidates < <(find "$mount_point" -maxdepth 2 -name "*.iso" 2>/dev/null)
if [[ ${#iso_candidates[@]} -eq 0 ]]; then
umount "$mount_point"; rmdir "$mount_point"
die "ISO ファイルが見つかりませんでした。"
fi
info "見つかった ISO ファイル:"
for i in "${!iso_candidates[@]}"; do
local sz; sz=$(du -sh "${iso_candidates[$i]}" 2>/dev/null | cut -f1)
printf " ${CYAN}[%d]${RESET} %s (%s)\n" "$((i+1))" "${iso_candidates[$i]##"$mount_point"}" "$sz"
done
printf " ${CYAN}[%d]${RESET} 何もせず終了\n" "$((${#iso_candidates[@]}+1))"
echo ""
local iso_entries=()
local sel_input sel_nums=()
while true; do
echo -en "${BOLD}追加する番号を入力${RESET}(スペース区切り複数可): "
read -r sel_input
local cancel_num=$(( ${#iso_candidates[@]} + 1 ))
local do_cancel=0
for n in $sel_input; do
[[ "$n" == "$cancel_num" ]] && do_cancel=1 && break
done
if [[ $do_cancel -eq 1 ]] || [[ -z "$sel_input" ]]; then
umount "$mount_point"; rmdir "$mount_point"
info "キャンセルしました。"; return
fi
local valid=1
sel_nums=()
for n in $sel_input; do
if ! [[ "$n" =~ ^[0-9]+$ ]] || (( n < 1 || n > ${#iso_candidates[@]} )); then
error "1〜${#iso_candidates[@]} の番号を入力してください。"
valid=0; break
fi
sel_nums+=("$n")
done
[[ $valid -eq 1 ]] && break
done
for n in "${sel_nums[@]}"; do
local iso_full="${iso_candidates[$((n-1))]}"
local iso_rel="${iso_full##"$mount_point"}"
local iso_name; iso_name=$(basename "$iso_full")
echo ""
local paths; paths=$(_detect_boot_paths "$iso_full")
local vmlinuz="${paths%%:*}"
local rest="${paths#*:}"; local initrd="${rest%%:*}"; local boot_type="${rest##*:}"
if [[ "$vmlinuz" == "UNKNOWN" ]]; then
warn "カーネルパスを自動検出できませんでした。手動入力してください。"
echo -en " vmlinuz のパス (例: /casper/vmlinuz): "; read -r vmlinuz
echo -en " initrd のパス (例: /casper/initrd): "; read -r initrd
boot_type="custom"
else
success "カーネル : ${vmlinuz}"
success "initrd : ${initrd}"
success "起動方式 : ${boot_type}"
fi
iso_entries+=("${iso_rel}:${vmlinuz}:${initrd}:${boot_type}")
done
umount "$mount_point"; rmdir "$mount_point"
success "アンマウント完了"
[[ ${#iso_entries[@]} -eq 0 ]] && { warn "追加するエントリがありません。"; return; }
echo ""
local bak; bak=$(backup_file "$CUSTOM_FILE")
success "バックアップ: ${bak}"
echo ""
for entry in "${iso_entries[@]}"; do
local iso_rel vmlinuz initrd boot_type
IFS=':' read -r iso_rel vmlinuz initrd boot_type <<< "$entry"
local menu_label; menu_label=$(basename "$iso_rel" .iso)
local params
case "$boot_type" in
casper) params="boot=casper iso-scan/filename=\$isofile quiet splash ---" ;;
live) params="boot=live iso-scan/filename=\$isofile quiet splash" ;;
*) params="iso-scan/filename=\$isofile quiet splash" ;;
esac
local grub_entry
grub_entry=$(cat <<EOF
menuentry "${menu_label} (ISO Loop Boot)" {
insmod part_gpt
insmod ext2
insmod loopback
insmod iso9660
search --no-floppy --fs-uuid --set=isodev ${part_uuid}
set isofile="${iso_rel}"
loopback loop (\$isodev)\$isofile
linux (loop)${vmlinuz} ${params}
initrd (loop)${initrd}
}
EOF
)
echo -e "${CYAN}追加予定エントリ:${RESET}"
echo "$grub_entry"
echo ""
if confirm "このエントリを追加しますか?"; then
echo "$grub_entry" >> "$CUSTOM_FILE"
success "追加しました: ${menu_label}"
else
warn "スキップ: ${menu_label}"
fi
done
_run_update_grub
}
# ISO をループマウントして vmlinuz/initrd パスを自動検出
_detect_boot_paths() {
local iso_path="$1"
local tmp="/mnt/_iso_inspect_$$"
mkdir -p "$tmp"
if ! mount -o loop,ro "$iso_path" "$tmp" 2>/dev/null; then
echo "UNKNOWN:UNKNOWN:custom"; return
fi
local vmlinuz="" initrd="" boot_type=""
if [[ -f "$tmp/casper/vmlinuz" ]]; then vmlinuz="/casper/vmlinuz"; initrd="/casper/initrd"; boot_type="casper"
elif [[ -f "$tmp/casper/vmlinuz.efi" ]]; then vmlinuz="/casper/vmlinuz.efi"; initrd="/casper/initrd.lz"; boot_type="casper"
elif [[ -f "$tmp/live/vmlinuz" ]]; then vmlinuz="/live/vmlinuz"; initrd="/live/initrd.img"; boot_type="live"
elif [[ -f "$tmp/live/vmlinuz.efi" ]]; then vmlinuz="/live/vmlinuz.efi"; initrd="/live/initrd.img"; boot_type="live"
else
local found; found=$(find "$tmp" -name "vmlinuz*" | head -1 || true)
if [[ -n "$found" ]]; then
vmlinuz="${found##"$tmp"}"
local dir; dir=$(dirname "$found")
local ird; ird=$(find "$dir" -name "initrd*" | head -1 || true)
initrd="${ird##"$tmp"}"
boot_type="custom"
else
vmlinuz="UNKNOWN"; initrd="UNKNOWN"; boot_type="custom"
fi
fi
umount "$tmp"; rmdir "$tmp"
echo "${vmlinuz}:${initrd}:${boot_type}"
}
# ============================================================
# 2: 不要エントリーの削除
# ============================================================
action_delete() {
hr
echo -e "${BOLD}■ 不要エントリーの削除${RESET}"
hr_thin
echo ""
local deletable_indices=()
for i in "${!ENTRY_SOURCES[@]}"; do
[[ "${ENTRY_SOURCES[$i]}" == "custom" ]] \
&& [[ "${ENTRY_CLASSES[$i]}" != "submenu_header" ]] \
&& deletable_indices+=("$i")
done
if [[ ${#deletable_indices[@]} -eq 0 ]]; then
warn "削除可能なエントリー(40_custom 由来)が見つかりませんでした。"
return
fi
echo -e " ${BOLD}削除可能なエントリー一覧:${RESET}"
echo ""
printf " ${BOLD}%-4s %-14s %s${RESET}\n" "No." "GRUB ID" "エントリー名"
hr_thin
for i in "${deletable_indices[@]}"; do
printf " ${BOLD}%3d${RESET} %-14s %s\n" \
"$i" "${ENTRY_GRUB_IDS[$i]}" "${ENTRY_NAMES[$i]}"
done
echo ""
local sel_input to_delete=() valid
while true; do
echo -en "${BOLD}削除する番号を入力${RESET}(スペース区切り複数可、Enter でキャンセル): "
read -r sel_input
[[ -z "$sel_input" ]] && { info "キャンセルしました。"; return; }
valid=1; to_delete=()
for num in $sel_input; do
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
error "無効な入力: ${num}"; valid=0; break
fi
local found=0
for di in "${deletable_indices[@]}"; do
[[ "$di" -eq "$num" ]] && found=1 && break
done
if [[ $found -eq 0 ]]; then
error "番号 ${num} は削除可能なエントリーではありません。"; valid=0; break
fi
to_delete+=("$num")
done
[[ $valid -eq 1 ]] && break
done
echo ""
echo -e " ${BOLD}${RED}以下のエントリーを削除します:${RESET}"
for num in "${to_delete[@]}"; do
echo -e " ${RED}・${ENTRY_NAMES[$num]}${RESET}"
done
echo ""
confirm "本当に削除しますか?" || { info "キャンセルしました。"; return; }
local bak; bak=$(backup_file "$CUSTOM_FILE")
success "バックアップ: ${bak}"
# /etc/grub.d/ 内の残存バックアップを検出・削除提案
local stale_baks=()
while IFS= read -r -d '' f; do
stale_baks+=("$f")
done < <(find /etc/grub.d/ -maxdepth 1 -name '40_custom.bak.*' -print0 2>/dev/null)
if [[ ${#stale_baks[@]} -gt 0 ]]; then
warn "/etc/grub.d/ に古いバックアップが残っています(エントリー復活の原因):"
for f in "${stale_baks[@]}"; do warn " ${f}"; done
echo ""
if confirm "古いバックアップを /etc/grub.d/ から削除しますか?"; then
for f in "${stale_baks[@]}"; do rm -f "$f" && success "削除: $f"; done
fi
echo ""
fi
# 同名エントリーが複数ある場合に備え、出現順(N番目)で削除する。
# 降順処理することで後ろから削除してもカウントがずれない。
declare -A name_delete_occurrences=()
for num in "${to_delete[@]}"; do
local tname="${ENTRY_NAMES[$num]}"
local occ=0
for i in "${!ENTRY_NAMES[@]}"; do
[[ "${ENTRY_SOURCES[$i]}" != "custom" ]] && continue
[[ "${ENTRY_CLASSES[$i]}" == "submenu_header" ]] && continue
[[ "${ENTRY_NAMES[$i]}" == "$tname" ]] && ((occ++)) || true
[[ "$i" -eq "$num" ]] && break
done
name_delete_occurrences["$tname"]+="${occ} "
done
local tmp; tmp=$(mktemp)
cp "$CUSTOM_FILE" "$tmp"
for tname in "${!name_delete_occurrences[@]}"; do
local sorted_occs
sorted_occs=$(echo "${name_delete_occurrences[$tname]}" | tr ' ' '\n' | grep -v '^$' | sort -rn)
for occ in $sorted_occs; do
awk -v name="$tname" -v target_occ="$occ" '
BEGIN { skip=0; depth=0; match_count=0 }
{
if (!skip && /menuentry/ && index($0, name) > 0) {
match_count++
if (match_count == target_occ) { skip=1; depth=0 }
}
if (skip) {
n = split($0, chars, "")
for (i=1; i<=n; i++) {
if (chars[i] == "{") depth++
else if (chars[i] == "}") {
depth--
if (depth <= 0) { skip=0; break }
}
}
next
}
print
}' "$tmp" > "${tmp}.new"
mv "${tmp}.new" "$tmp"
success "削除: ${tname}(${occ}番目)"
done
done
cat "$tmp" > "$CUSTOM_FILE"
rm -f "$tmp"
_run_update_grub
}
# ============================================================
# update-grub を実行してエントリー一覧を再表示
# ============================================================
_run_update_grub() {
echo ""
info "GRUB を更新中..."
if update-grub 2>&1; then
success "update-grub 完了"
local grub_cfg; grub_cfg=$(find_grub_cfg)
if [[ -n "$grub_cfg" ]]; then
echo ""
info "エントリー一覧を更新しました:"
parse_grub_cfg "$grub_cfg"
print_entries
fi
else
warn "update-grub でエラーが発生しました。手動で確認してください: sudo update-grub"
fi
}
# ============================================================
# 操作メニュー
# ============================================================
show_action_menu() {
hr
echo -e "${BOLD}■ 操作を選択してください${RESET}"
hr_thin
echo ""
echo -e " ${BOLD}[1]${RESET} ISO ループブートエントリーを追加"
echo -e " ${BOLD}[2]${RESET} 不要エントリーを削除 ${CYAN}(40_custom 由来のみ)${RESET}"
echo -e " ${BOLD}[3]${RESET} 何もせず終了"
echo ""
local choice
while true; do
echo -en "${BOLD}選択${RESET} [1/2/3]: "
read -r choice
case "$choice" in
1) action_add_iso; break ;;
2) action_delete; break ;;
3) echo ""; info "変更なしで終了します。"; exit 0 ;;
*) error "1、2、または 3 を入力してください。" ;;
esac
done
}
# ============================================================
# メイン
# ============================================================
main() {
clear
hr
echo -e "${BOLD} manage-grub.sh — GRUB エントリー管理ツール${RESET}"
hr
echo -e " 実行日時: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
check_root
local grub_cfg; grub_cfg=$(find_grub_cfg)
[[ -n "$grub_cfg" ]] || die "grub.cfg が見つかりませんでした。"
info "設定ファイル: ${BOLD}${grub_cfg}${RESET}"
parse_grub_cfg "$grub_cfg"
print_current_settings
hr
echo -e "${BOLD}■ GRUB エントリー一覧${RESET}"
hr_thin
print_entries
if command -v efibootmgr &>/dev/null; then
hr
echo -e "${BOLD}■ EFI ブートエントリー(参考)${RESET}"
hr_thin
echo ""
efibootmgr 2>/dev/null | while IFS= read -r line; do
[[ "$line" =~ ^\* ]] \
&& echo -e " ${GREEN}${line}${RESET} ${CYAN}← アクティブ${RESET}" \
|| echo -e " ${line}"
done
echo ""
fi
show_action_menu
echo ""
hr
success "完了しました。"
hr
echo ""
}
main "$@"

GRUBメニューを表示するには
ちなみに、GRUBメニューを表示するには、UEFI環境ならEscキーを、BIOS環境ならShiftキーをタイミングよく押します。押し過ぎて、grub>のプロンプトになった場合はnormalと入力することでメニューが表示されるはずです。


既存の残存バックアップが残っている場合
GRUBメニューに変な項目が残っている場合は下記で解決するかも。
sudo rm /etc/grub.d/40_custom.bak.*
sudo update-grub
ISOイメージのパーティションを/isoに永続マウントするには
こちらのスクリプトを使用します。

