如何防止Windows命令解释器在用户输入错误时退出批处理文件执行?

3
如果我有以下示例代码:
@echo off
:menu
cls
echo 1. win
echo 2. lose
set /p menu=
goto %menu%
pause>nul

:win
cls
echo yay lolz
pause>nul

:lose
cls
echo really?
pause>nul

我该如何防止批处理在我输入“test”而不是有效响应时退出?

2
当您只期望定义好的响应时,请使用Choice而不是Set /P - Compo
首先,你的菜单看起来像是我应该输入1或2。你应该考虑使用CHOICE命令来获取用户输入。 - Squashman
1个回答

4

1. Windows命令文档

我建议在浏览器中收藏以下内容:

可以通过在命令提示符窗口中带有/?参数来运行每个Windows命令以获取帮助,例如if /?set /?等。执行help命令会输出一个不完整的Windows命令列表及其简要说明。


2. 使用SET /P进行用户提示

如果用户需要从多个选项中选择一个,则建议不使用set /p。在提示用户输入字符串并将其分配给环境变量时,必须考虑多个因素:

  1. 如果用户故意或错误地仅按下RETURNENTER键,则在使用set /P "MyVar=Your choice: "时并不会修改环境变量MyVar。这意味着如果环境变量MyVar在用户提示之前未定义,则在用户按下RETURN键后仍未定义。如果在用户提示之前已经定义了MyVar,则在用户仅按下RETURNENTER键的情况下保持其值不变。根据内部cmd.exe命令设置的ERRORLEVEL值是什么?文档,命令SET在用户没有输入字符串时将以错误值1退出。

  2. 用户可以自由地在使用set /P进行提示时键入任何字符串。批处理文件作者无法控制用户实际输入的内容。因此,批处理文件作者必须考虑到用户出于错误或故意目的而输入会导致语法错误的字符串,或者执行与预期完全不同的操作的可能性。

一个简单的例子:

@echo on
:MainMenu
@set /P "menu=Your choice: "
if %menu% == 1 goto Label1
if %menu% == 2 goto Label2
goto MainMenu

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

这个批处理文件在顶部使用echo on而不是echo off,是为了调试目的而从命令提示符窗口启动。

第一次运行时,只需按下用户提示上的RETURN。 Windows命令解释器在执行IF命令之前对第一个IF条件进行预处理,然后退出批处理文件处理:

if  == 1 goto Label1

很明显缺少第一个参数。cmd.exe因为此语法错误而退出批处理,并输出适当的错误信息。原因是环境变量menu在用户提示之前没有定义,且在用户提示后仍未定义。

在命令提示符窗口内第二次运行批处理文件时输入了字符串2,批处理文件按预期工作。

在同一命令提示符窗口内再次运行批处理文件的第三次,只需按下RETURN键即可。批处理文件再次输出第二条消息。为什么?因为环境变量menu仍然被定义为具有该字符串的第二个批处理文件执行,并且该变量在按下RETURN键时未被修改。

好的,让我们修改示例批处理文件:

@echo on
:MainMenu
@set "menu=2"
@set /P "menu=Your choice: "
if "%menu%" == "1" goto Label1
if "%menu%" == "2" goto Label2
goto MainMenu

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

现在已经更好了,因为环境变量menu始终预定义为值2。所以如果用户没有输入任何内容,则会跳转到Label2。此外,先前运行的变量menu的值对批处理文件的执行不再产生影响。
另一种解决方案是在用户根本没有输入字符串时使用退出代码1,并仅在这种情况下使用默认值定义环境变量,方法如下:
@set /P "menu=Your choice: " || set "menu=2"

使用Windows批处理文件的单行多个命令描述了条件执行运算符||,只有在第一个执行提示命令set /P "menu=Your choice: "以不等于0的退出代码退出时才运行第二个命令set "menu=2",这通常发生在用户没有输入任何内容时。

感谢aschipfl做出的贡献。

但是现在它真的安全可靠吗?

不,它并不安全可靠。用户仍然可能会错误地输入错误的字符串。

例如,用户误输入"而不是2,在德语键盘上很容易按下CapsLock+2Shift+2就会输入"。预处理后的第一个IF命令行现在是:

if """ == "1" goto Label1

这是一条无效的命令行,由于语法错误导致批处理文件处理退出。请假设用户在提示符上输入以下字符串:
" == "" call dir "%USERPROFILE%\Desktop" & rem 

注意:末尾有一个空格。

第一个IF条件是由Windows命令解释器预处理的:

if "" == "" call dir "%USERPROFILE%\Desktop"   & rem " == "1" goto Label1

可以看到批处理文件现在会执行两个IF条件中都没有写的命令。 如何使用户提示安全可靠? 可以通过使用延迟变量扩展来使用户提示失败安全且可靠,至少对于评估用户输入的字符串的代码。
@echo on
:MainMenu
@setlocal EnableDelayedExpansion
@set "Label=MainMenu"
@set /P "menu=Your choice: " || set "menu=2"
if "!menu!" == "1" set "Label=Label1"
if "!menu!" == "2" set "Label=Label2"
endlocal & goto %Label%

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

现在用户输入的字符串不再修改Windows命令处理器执行的命令行。因此,由于用户输入引起的语法错误而导致批处理文件处理退出不再可能(故障安全)。此外,批处理文件永远不会执行未在批处理文件中编写的命令(安全)。

3. 使用CHOICE进行选择提示

对于简单的选择菜单,有一个比set /P更好的命令 - CHOICE
@echo off
:MainMenu
cls
echo/
echo    1 ... Option 1
echo    2 ... Option 2
echo    E ... Exit
echo/
%SystemRoot%\System32\choice.exe /C 12E /N /M "Your choice: "
if errorlevel 3 exit /B
if errorlevel 2 goto Label2
if not errorlevel 1 goto MainMenu

@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

用户不再有自由地输入未被批处理文件作者定义的任何内容。在用户按下12EShift+E后,批处理文件立即继续执行。除了Ctrl+C之外,choice会忽略其他所有内容。
动态变量ERRORLEVELchoice终止时几乎总是有三个选项的值,范围为1到3,并将1到3作为退出代码返回给调用cmd.exe。例外情况是批处理文件的用户在提示符上按下Ctrl+C并用N回答cmd.exe的下一个提示符Terminate batch job (Y/N)?。在这种情况下,动态变量ERRORLEVEL的值为0,这就是if not errorlevel 1 goto MainMenu处理这个非常特殊的用例的原因。
注意:if errorlevel X表示如果大于或等于X。因此,始终需要从命令choice的可能最高的退出代码开始。
由于分配给ERRORLEVEL的退出代码是众所周知的,因此在更大的菜单中,可以通过使用适当的标签进一步优化代码。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ERRORLEVEL="

:MainMenu
cls
echo/
echo    1 ... Option 1
echo    2 ... Option 2
echo    E ... Exit
echo/
%SystemRoot%\System32\choice.exe /C 12E /N /M "Your choice: "
goto Label%ERRORLEVEL%

:Label0
rem The user pressed Ctrl+C and on next prompt N and
rem so made no choice. Prompt the user once again.
goto MainMenu

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.
exit /B

:Label3

使用命令CHOICE可以使选择菜单的编码非常简单。
第三个命令行确保没有定义名为ERRORLEVEL环境变量,否则将防止使用% ERRORLEVEL%语法访问命令CHOICE的退出代码来使用动态变量ERRORLEVEL的当前值。 注意:只有选择菜单命令行在以(开始并以匹配)结尾的命令块内部时,才能使用goto Label%ERRORLEVEL%
另请参见:How can I make an "are you sure" prompt in a Windows batch file? 提示1:如果用户按下不可接受的键,则CHOICE会发出蜂鸣声。无法禁止此蜂鸣声,因为CHOICE没有提供避免输出蜂鸣声的选项。
提示2:还请查看我在Where does GOTO: EOF return to?上的答案,也对exit /B进行了解释。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接