假设批处理文件包含以下内容:
@echo off
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range 0 to 20000.
set /P "MyVar=Enter number [0,20000]: "
正如我在如何防止Windows命令解释器在用户输入错误时退出批处理文件执行?中所解释的那样,用户有自由输入任何内容,包括可能导致语法错误或执行批处理文件不符合预期的字符串。
1. 用户未输入任何内容
如果用户只按下 RETURN 或 ENTER 键,命令 SET 不会修改环境变量 MyVar
。在这种情况下,可以通过在提示用户之前明确未定义环境变量 MyVar
并检查用户是否输入了字符串来验证:
if not defined MyVar goto PromptUser
注意:可以使用与“set“ MyVar =”不同的内容,例如“set“ MyVar =1000”来定义默认值,甚至可以在提示符上输出默认值,让用户有可能只需按
RETURN 或
ENTER 即可使用默认值。
2. 用户输入了一个带有一个或多个“"”的字符串
用户可能故意或错误地输入了一个带有一个或多个“"”的字符串。例如,在启用了
CapsLock的非数字键盘上按下
German keyboard键
2会导致输入
"
,除非使用
German(IBM),对于该键盘,
CapsLock仅在字母上通过软件激活。因此,如果用户快速或没有查看屏幕而按下
2和
RETURN,则用户可能会错误地输入双引号字符而不是
2
。
如果MyVar
包含一个或多个"
,那么所有%MyVar%
或"%MyVar%"
的环境变量引用都会出现问题,因为%MyVar%
会被Windows命令处理器替换为用户输入的字符串,其中包含一个或多个"
,这几乎总是导致语法错误或批处理文件执行了它不应该执行的操作。参见Windows Command Interpreter (CMD.EXE)如何解析脚本?。
有两个解决方案:
- 启用延迟扩展,并使用
!MyVar!
或"!MyVar!"
引用环境变量,此时用户输入字符串不再影响由cmd.exe
解析后执行的命令行。
- 如果该字符串永远不包含双引号字符,则从用户输入字符串中删除所有
"
。
字符
"
在一个应该是介于
0
到
20000
(十进制)范围内的数字字符串中绝对无效。因此,可以使用两行代码来防止由
"
引起的用户输入字符串处理错误。请注意保留HTML标签。
set "MyVar=%MyVar:"=%"
if not defined MyVar goto PromptUser
Windows命令处理器在替换%MyVar:"=%
为结果字符串之前,会先删除此行上的双引号。因此,最终执行的命令行set "MyVar=用户输入的任何内容"
在执行时是安全的。
上述示例中错误输入了"
而不是2
,导致执行set "MyVar="
,这将取消定义环境变量MyVar
,这就是为什么在进一步处理用户输入之前必须再次使用之前使用的IF条件的原因。
3. 用户输入了无效字符
用户应该输入范围在0
到20000
之间的正十进制数。因此,除0123456789
之外的任何其他字符都是无效的。例如,可以通过以下方式检查任何无效字符:
for /F delims^=0123456789^ eol^= %%I in ("%MyVar%") do goto PromptUser
命令
FOR在整个字符串只包含数字的情况下不会执行
goto PromptUser
。在所有其他情况下,包括以
;
开头的字符串,零个或多个数字之后,由于输入字符串包含非数字字符,将导致执行
goto PromptUser
。
4. 带有前导0
的用户输入数字
Windows命令处理器将带有前导0
的数字解释为八进制数。但是,即使用户在开头输入一个或多个0
,该数字也应被解释为十进制数。因此,在进一步处理变量值之前,应删除前导零。
for /F "tokens=* delims=0" %%I in ("%MyVar%") do set "MyVar=%%I"
if not defined MyVar set "MyVar=0"
FOR 去除赋值给 MyVar
的字符串开头的所有 0
,并将剩余的字符串分配给循环变量 I
,然后将其赋值给环境变量 MyVar
。
FOR 在这种情况下运行 set "MyVar=%%I"
,即使用户输入了 0
或 000
,也会导致执行 set "MyVar="
,在这种特殊情况下取消定义环境变量 MyVar
。但是,0
是一个有效的数字,因此必须使用 IF 条件来重新定义 MyVar
,并在用户输入数字 0
时用字符串值 0
替换其中一个或多个零。
5. 用户输入过大的数字
现在可以安全地使用带有运算符 GTR
的命令 IF 来验证用户是否输入了过大的数字。
if %MyVar% GTR 20000 goto PromptUser
这个最后的验证即使用户输入大于最大正32位整数值的82378488758723872198735897,也可以工作,因为范围溢出会导致在执行此IF条件时使用2147483647。有关详细信息,请参见
weird results with IF上我的答案。
6. 可能的解决方案1
一个完整的批处理文件,用于安全评估用户输入的数字范围在 0
到 20000
之间,仅适用于十进制数:
@echo off
set "MinValue=0"
set "MaxValue=20000"
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
set /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
if not defined MyVar goto PromptUser
set "MyVar=%MyVar:"=%"
if not defined MyVar goto PromptUser
for /F delims^=0123456789^ eol^= %%I in ("%MyVar%") do goto PromptUser
for /F "tokens=* delims=0" %%I in ("%MyVar%") do set "MyVar=%%I"
if not defined MyVar set "MyVar=0"
if %MyVar% GTR %MaxValue% goto PromptUser
rem if %MyVar% LSS %MinValue% goto PromptUser
rem Output value of environment variable MyVar for visual verification.
set MyVar
pause
这个解决方案还可以让批处理文件编写者输出错误消息,告诉用户为什么输入字符串未被批处理文件接受。
如果MinValue的值为0,则最后一个IF条件与运算符LSS是不必要的,这就是为什么在此用例中它被注释掉并使用REM命令。
7. 可能的解决方案2
这是另一个安全的解决方案,但缺点是用户无法输入具有一个或多个前导0
的十进制数,即使通常被用户期望为十进制。
@echo off
set "MinValue=0"
set "MaxValue=20000"
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
set /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
if not defined MyVar goto PromptUser
setlocal EnableDelayedExpansion
set /A "Number=MyVar" 2>nul
if not "!Number!" == "!MyVar!" endlocal & goto PromptUser
endlocal
if %MyVar% GTR %MaxValue% goto PromptUser
if %MyVar% LSS %MinValue% goto PromptUser
rem Output value of environment variable MyVar for visual verification.
set MyVar
pause
此解决方案使用延迟环境变量扩展,作为上述第2点的第一选项。
算术表达式用于将用户输入字符串转换为带符号的32位整数,将字符串解释为十进制、八进制或十六进制数,并将其转换回分配给环境变量Number
的字符串,其中Windows命令处理器使用十进制数字系统。由于无效的用户字符串导致算术表达式计算错误输出被重定向到设备NUL以抑制它。
接下来,使用延迟扩展验证算术表达式创建的数字字符串是否与用户输入的字符串不同。如果这个IF条件为真,则表示用户输入无效,包括数字具有前导零被cmd.exe
解释为八进制,或者输入十六进制数字如0x14
或0xe3
。
通过字符串比较后,可以安全地使用运算符GTR
和LSS
将MyVar
的值与20000
和0
进行比较。
请阅读
此答案 了解有关命令
SETLOCAL 和
ENDLOCAL 的详细信息,因为运行
setlocal EnableDelayedExpansion
和
endlocal
比仅启用和禁用延迟环境变量扩展要做的更多。
8. 可能的解决方案 3
如果值0
不在有效范围内,即用户输入的数字必须大于0
,则可以使用更少的命令行来提供另一种解决方案。
@echo off
set "MinValue=1"
set "MaxValue=20000"
:PromptUser
rem Undefine environment variable MyVar in case of being already defined by chance.
set "MyVar="
rem Prompt user for a positive number in range %MinValue% to %MaxValue%.
set /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "
set /A MyVar+=0
if %MyVar% GTR %MaxValue% goto PromptUser
if %MyVar% LSS %MinValue% goto PromptUser
rem Output value of environment variable MyVar for visual verification.
set MyVar
pause
这段代码使用
set /A MyVar+=0
将用户输入的字符串转换为32位有符号整数值,并按照
aschipfl在
他的评论中的建议将其转换回字符串。
如果用户没有输入任何字符串,则命令行中带有算术表达式后
MyVar
的值为
0
。如果用户输入的字符串的第一个字符不是这些字符之一
-+0123456789
,例如
"
或
/
或
(
,则
MyVar
的值也为
0
。
一个以数字、减号或加号开头且下一个字符为数字的用户输入字符串将被转换为整数值,然后再转换回字符串值。输入的字符串可以是十进制数、八进制数或十六进制数。请查看我在
Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files上的答案,其中详细解释了Windows命令处理器如何将字符串转换为整数值。
这段代码的缺点是,如果由于在德语键盘上按下2和(键时按住
Shift导致输入字符串错误,例如
7"(
而不是
728
,则此代码无法检测到。当用户输入错误时,
MyVar
的值为
7
。Windows命令处理器只解释直到第一个非有效字符为止的字符作为整数值,并忽略字符串的其余部分。
使用此代码的批处理文件在处理过程中不会因为语法错误而意外退出,这是安全的,无论用户输入什么。但是,如果用户错误地输入了一个数字,有时代码不会检测到,导致继续处理批处理文件,并使用用户不想使用的数字。
set /a myvar=myvar
或set /a myvar*=1
。如果变量未定义或不是数字,则set /a
将把变量设置为0
。 - Stephanset var=0z
这样的吗?没错,set /a newvar=%var%
会报错并让newvar
保持不变。但是尝试一下set /a var=var
(没有%
!)或者set /a var*=1
。甚至可以尝试set /a newvar=var
。 - Stephanmyvar
已定义且仅包含数字,您可以使用if defined myvar Echo:%myvar%|findstr /BE "[0123456789]*" >NUl 2>&1 &&(Echo Only numbers)||(Echo invalid chars)
。注意:使用findstr的范围[0-9]
也将包括上标²³
。 - user6811411