![从 Windows 重新启动 grub](https://linux22.com/image/1633323/%E4%BB%8E%20Windows%20%E9%87%8D%E6%96%B0%E5%90%AF%E5%8A%A8%20grub.png)
我的台式机上有一个双启动设置,目前 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 now
0 是我系统上 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 中的服务器(通过任何协议)获取完整配置或至少获取“下次启动”和/或“默认启动”的配置。
对于较大的池来说,这将具有清晰度和更容易管理的额外优势......