从 Windows 重新启动 grub

从 Windows 重新启动 grub

我的台式机上有一个双启动设置,目前 Linux 是默认操作系统,因此除非我在启动过程中进行干预,否则它将启动到 Linux。如果我在 Linux 上登录,我有一个小脚本可以通过运行 重新启动到 Windows,sudo grub-reboot 2 && reboot我想知道我是否可以从 Windows 执行类似操作。这将允许我将 Windows 设置为我的默认操作系统,而不会牺牲使用简单脚本轻松切换到辅助操作系统的能力。

我意识到这可能是不可能或不可取的,因为我知道 Windows 在多重启动情况下有不合作的习惯,所以请随时指出我哪些事情无法完成或尚无法完成。

概括:是否有一个脚本可以在 Windows 上运行,类似于grub reboot 2在 Linux 上,它只会更改下次重启的启动优先级,以便快速切换到我的辅助操作系统?


附录:用例

希望您能原谅我的重复,我想说得彻底一些。我所缺少的只是“重新启动到Linux“ 脚本。

现在的情况

Linux 是默认操作系统,我有一个可以切换到 Windows 的脚本。

  • 我目前正在使用 Linux,想切换到 Windows。我在桌面上运行标有“重新启动到 Windows”的脚本。我的计算机重新启动并选择了 Windows。我没有干预。它加载到 Windows。下次我重新启动后,Linux 仍然是默认选择。
  • 我想运行 Windows。我打开电脑,在启动屏幕上选择了 Linux。我没有及时干预,它就加载了 Linux。我在桌面上运行标有“重新启动到 Windows”的脚本。我的电脑重新启动,Windows 被选中。我没有干预。它加载到 Windows。下次我重新启动时,Linux 仍然是默认选择。
  • 我目前正在使用 Windows,想切换到 Linux。我重启电脑。电脑重启后,Linux 被选中。我没有干预。它加载到 Linux 中。
  • 我想运行 Linux。我打开电脑,在启动屏幕上选择了 Linux。我没有干预。它加载到 Linux。
  • 我想运行 Windows。我打开电脑,在启动屏幕上选择了 Linux。我介入并选择 Windows。它加载到 Windows 中。

首选情况

Windows 是默认操作系统,我有一个脚本可以切换到 Linux。

  • 我目前正在使用 Windows,想切换到 Linux。我在桌面上运行标有“重新启动到Linux“。我的电脑重新启动,并且选择了 Linux。我没有干预。它加载到 Linux。下次我重新启动后,Windows 仍然是默认选择。
  • 我想运行 Linux。我打开电脑,在启动屏幕上选择了 Windows。我没有及时干预,它加载了 Windows。我在桌面上运行标有“重新启动到Linux“。我的电脑重新启动,并且选择了 Linux。我没有干预。它加载到 Linux。下次我重新启动后,Windows 仍然是默认选择。
  • 我目前正在使用 Linux,想切换到 Windows。我重启了电脑。电脑重启后,Windows 被选中。我没有干预。它加载到 Windows 中。
  • 我想运行 Windows。我打开电脑,在启动屏幕上选择了 Windows。我没有干预。它加载到 Windows。
  • 我想运行 Linux。我打开电脑,在启动屏幕上选择了 Windows。我介入并选择 Linux。它加载到 Linux。

答案1

我意识到这是一个老问题,但我在寻找同样的东西时偶然发现了它。我双启动 Ubuntu 22.04 和 Windows 10。Ubuntu 被配置为 grub 中的默认选择。必须手动从 grub 菜单中进行选择很烦人,因为我的笔记本电脑通常连接到底座,盖子关闭。如果我想启动到 Windows,我会运行sudo grub-reboot 0 && sudo reboot now0 是我系统上 Windows 的 grub 菜单项编号。问题是如果我在 Windows 中并想重新启动到 Windows。我必须打开笔记本电脑并在 grub 菜单上手动选择,或者启动到 Ubuntu 并grub-reboot再次使用它进行启动。

经过一些研究和实验,我现在有了一个可行的解决方案。请注意,这仅在我的系统上进行了测试,我已正确配置 grub 以允许其grub-reboot按所述工作。请参阅这里进行正确的设置。我的 Linux 分区是 ext4。

/boot在我的 Linux 系统中,我在主目录中递归复制了。然后我运行grub-reboot 0并递归比较了/boot~/boot。我发现唯一更改的文件是/boot/grub/grubenv。这是 grub环境块文件,它包含正好 1024 字节的文本,并且next_entry=在所有内容末尾都有一个填充行,#以使其达到 1024 字节。grub-reboot 0只需将其更改为next_entry=0并调整填充行的长度以保持文件为 1024 字节。

好的,这似乎很简单。唯一可能棘手的部分是从 Windows 内部获取对 Linux 分区的读/写访问权限/boot。在 Windows 11 上,WSL2 允许安装 ext4(可能还有其他)分区。对于 Windows 10,一些选项是这里。我正在使用 Paragon 的适用于 Windows 的 Linux 文件系统(不是免费,但也不贵。)

解决这个问题后,我编写了一个 bash 脚本来执行基本上该操作grub-reboot。是的,我意识到像 Powershell 这样的东西对于 Windows 工具来说可能更有意义;我将把它留给读者作为练习。Bash 对我来说更容易,而且无论如何我一直在 Windows 上使用 cygwin。(我无法从 WSL 读取使用 linuxfs-windows 驱动程序安装在 D 上的 linux 分区。)此外,它允许我从 Windows(cygwin bash)或 ubuntu 使用相同的驱动程序脚本。请注意,脚本包含在提交任何内容之前进行测试的选项;--dry-run将保留新的 grubenv 文件/tmp并且不会更改您的实际副本。--diff将比较新旧(--diff本身会输出差异并更新您的实际文件;--diff--dry-run输出差异和更新的内容,而不会更改任何内容。)

#!/usr/bin/env bash
#
# win-grub-reboot
#
# Update the linux grubenv file, typically in /boot/grub/grubenv, to set the 'next_entry' to the
# specified value. This does essentially what 'grub-reboot' does - set a one time entry for the
# next boot. See https://manpages.ubuntu.com/manpages/xenial/man8/grub-reboot.8.html.
#
# While it could be used from linux, it would be better to use grub-reboot in that case, of course.
# This is intended to be used in windows, from a bash shell such as cygwin or WSL. It requires
# that the linux partition is mounted and writable in windows.
#
# The grubenv file is the GRUB Environment block. See https://www.gnu.org/software/grub/manual/grub/html_node/Environment-block.html.
# This is a preallocated 1024 byte file, padded with '#' on the last line, and with no trailing
# newline. If your system is properly set up to use 'grub-reboot', this file will have a line like
#   next_entry=
# grub-reboot, and this script, simply puts the desired entry here and adjusts the file size back to 1024 by
# truncating or extending the filler line.
#
# This script performs many sanity checks, and will not update your grubenv file is there is anything
# unexpected. But use at your own risk. Its been tested on a dual boot Ubuntu 22.04 and Windows 10
# system, configured such that 'grub-reboot' from Ubuntu will change the default on the next boot only.
# It has only been tried with boot entries as zero based grub menu item numbers. It could easily be
# extended to support menu item names or identifiers, as grub-reboot does, with some testing and by
# removing the check which requires a positive integer for the --menu argument.
#

PROG_NAME=${0##*/}

# grubenv is expected to be 1024 bytes
FILE_SIZE=1024

function Die
{
    echo $*
    exit 1
}

function Usage
{
    IFS=%

    while read line
    do
        echo $line
    done <<ENDUSAGE

Usage: $PROG_NAME [OPTIONS]

Set the default boot entry for GRUB, for the next boot only

Options:
  -e, --entry       Desired grub menu entry for next boot only. Must be a 0 based 
                    grub menu item number. grub-reboot supports additional options,
                    but these are untested at this time and therefore disabled.
                    See 'man grub-reboot' for more details.
  -f, --file        Full path and name for the grubenv file. On the linux system, this
                    is typically /boot/grub/grubenv. On windows, this should be the windows
                    path to that file (the linux partition must be mounted and writable).
  -d, --diff        Display a diff of the original grubenv and the new one
  -r, --dry-run     Writes the new grubenv file to a temp file and displays its content.
                    No changes are made to the actual grubenv file.
  -h, --help        Display this usage message

Example: Assuming that you are running under cygwin and your linux partition containing /boot
is mounted on drive D, and that 0 is the grub menu item number for the desired next boot, then

    $PROG_NAME --file /cygdrive/D/boot/grub/grubenv --entry 0

will cause grub to choose the first menu item for the next boot only.

ENDUSAGE
}

function Main
{
    set +e

    declare grubEnvFile
    declare newEntry
    declare dryRun=false
    declare showDiff=false

    while [ $# -ne 0 ]
    do
        case ${1} in
        -e|--entry)
            shift
            newEntry=$1
            ;;
        -f|--file)
            shift
            grubEnvFile=$1
            ;;
        -d|--diff)
            showDiff=true
            ;;
        -r|--dry-run)
            dryRun=true
            ;;
        -h|--help)
            Usage
            exit 0
            ;;
        "")
            ;;
        *)
            Die "Unsupported argument $1"
            ;;
        esac
        shift
    done

    [ ! -z $grubEnvFile ] || Die "Path to grubenv is required"
    [ -f $grubEnvFile ] || Die "File $grubEnvFile does not exist"

    # remove this check to allow menu item names/identifiers
    if ! [[ $newEntry =~ ^[0-9]+$ ]] ; then
        Die "entry [$newEntry] must be a 0 based grub menu item"
    fi

    # Sanity check the current file size
    currentFileSize=$(stat -c %s "$grubEnvFile")
    [ $currentFileSize -eq $FILE_SIZE ] || Die "$grubEnvFile has unexpected size $currentFileSize"

    # Make sure the current file contains the next_entry= line
    cat $grubEnvFile | egrep "^next_entry=.*$" >/dev/null 2>&1 || Die "Did not find expected 'next_entry' in $grubEnvFile"

    # Get the old entry form the file
    oldEntry=$(cat $grubEnvFile | sed -E -n "s/^next_entry=(.*$)/\1/p")

    # Get lengths of old and new so we know how to adjust the filler line
    newEntryLen=$(echo $newEntry | awk '{print length}')
    oldEntryLen=$(echo $oldEntry | awk '{print length}')
    diffLen=$((newEntryLen-oldEntryLen))

    # Make sure the last line is the expected filler '######...'
    tail -n 1 $grubEnvFile | egrep "^#+$" >/dev/null 2>&1 || Die "Did not find expected filler line in $grubEnvFile"

    # Write the new file out to a temp file with the new next_entry value. 
    # We are operating on the temp file till all sanity checks pass, and
    # then we'll overwrite the existing file with the temp file.
    tmpfile=$(mktemp /tmp/grubenv.XXXXXX)
    cat $grubEnvFile | sed -E  "s/^next_entry=.*$/next_entry=${newEntry}/" > $tmpfile

    if [ $diffLen -gt 0 ]; then
        # new file will be too large. truncate it. we'll ensure that we still have a filler line
        # below, to make sure we did not truncate anything important
        truncate -s $FILE_SIZE $tmpfile
    elif [ $diffLen -lt 0 ]; then
        # we need to add chars to the filler line to get to 1024
        diffLen=$((-diffLen))
        extraFiller=$(printf "%${diffLen}s" '#' | tr ' ' '#')
        echo -n $extraFiller >> $tmpfile
    fi

    # make sure the new file has the expected next_entry
    cat $tmpfile | egrep "^next_entry=${newEntry}$" >/dev/null 2>&1 || Die "Unexpected error: replacement error"

    # make sure the new file is the correct size
    newFileSize=$(stat -c %s $tmpfile)
    [ $newFileSize -eq $FILE_SIZE ] || Die "Unexpected file $newFileSize size in the result"

    # Make sure the last line is the expected filler '######...'
    tail -n 1 $tmpfile | egrep "^#+$" >/dev/null 2>&1 || Die "Unexpected error: no filler line in result"

    if [ $showDiff = true ]; then
        echo "diff $grubEnvFile $tmpfile:"
        diff $grubEnvFile $tmpfile
    fi

    if [ $dryRun = true ]; then
        echo
        echo "Result in $tmpfile:"
        echo
        cat $tmpfile
    else
        mv $tmpfile $grubEnvFile
    fi
}

Main "$@"

我从一个可以在 Linux 或 Windows(Cygwin)上运行的驱动程序脚本中调用它winboot

#!/usr/bin/env bash

function IsWindows
{
    case `uname` in
        CYGWIN_NT-*)
            true
            ;;
        *)
            false
            ;;
    esac
}

set -e

if IsWindows; then
    win-grub-reboot --file /cygdrive/D/boot/grub/grubenv --entry 0
    shutdown --reboot now
else
    sudo grub-reboot 0
    sudo reboot now
fi

答案2

这正是我们在远程管理双启动计算机的 CIP 中长期面临的问题。

不幸的是,我无法想出一个完整的解决方案,只是另一个可能有助于找到解决方案的想法:Grub 可以从 LAN 中的服务器(通过任何协议)获取完整配置或至少获取“下次启动”和/或“默认启动”的配置。

对于较大的池来说,这将具有清晰度和更容易管理的额外优势......

相关内容