Windows 批处理文件中的安全数字比较

Mag*_*s W 6 windows cmd batch-file

我知道,在比较批处理文件中的内容是否相等时,通常将两边括在引号中,例如

IF "%myvar% NEQ "0" 
Run Code Online (Sandbox Code Playgroud)

但是,当使用“大于”或“小于”进行比较时,这不起作用,因为操作数将被视为带有引号的字符串。所以你可以这样做

IF %myvar% GTR 20000
Run Code Online (Sandbox Code Playgroud)

需要注意的是,如果变量 %myvar% 没有声明,那么就像这样做

IF GTR 20000
Run Code Online (Sandbox Code Playgroud)

这是一个语法错误。

我想出了以下解决方法:

IF 1%myvar% GTR 120000
Run Code Online (Sandbox Code Playgroud)

我希望这会导致IF 1 GTR 120000ifmyvar未定义,并且它似乎有效。

这是比较数字和计算未声明变量的安全方法,还是我只是提出了一个全新的警告?

Mof*_*ofi 4

让我们假设批处理文件包含:

\n\n
@echo off\n:PromptUser\nrem Undefine environment variable MyVar in case of being already defined by chance.\nset "MyVar="\nrem Prompt user for a positive number in range 0 to 20000.\nset /P "MyVar=Enter number [0,20000]: "\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如我在如何阻止 Windows 命令解释器在用户输入错误时退出批处理文件执行的回答中所解释的那样?用户可以自由地输入任何内容,包括字符串,这很容易导致由于语法错误而中断批处理文件的执行,或者导致执行批处理文件未编写的操作。

\n\n
\n\n

1. 用户未输入任何内容

\n\n

如果用户只按RETURN或键,则命令SET根本不会修改ENTER环境变量。在这种情况下,在提示用户是否输入字符串之前,使用显式未定义的环境变量很容易验证:MyVarMyVar

\n\n
if not defined MyVar goto PromptUser\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意:可以使用与定义默认值不同的东西set "MyVar="set "MyVar=1000"甚至可以在提示时输出,让用户可以直接点击RETURNENTER使用默认值。

\n\n

2. 用户输入一个或多个字符串"

\n\n

用户可能"有意或无意地输入包含一个或多个的字符串。例如,在当前启用的非数字键盘上按 \xc2\xa0a\xc2\xa0德语键盘键会导致输入,但使用的德语 (IBM)除外,软件仅对字母有效。因此,如果用户快速点击and或没有像许多人在键盘上键入 \xc2\xa0 那样看屏幕,则用户会错误地输入双引号字符。2CapsLock"CapsLock2RETURN2

\n\n

MyVar保存具有一个或多个"所有%MyVar%"%MyVar%"环境变量引用的字符串时,这是有问题的,因为%MyVar%Windows 命令处理器将其替换为具有一个或多个的用户输入字符串,"这几乎总是导致语法错误,或者批处理文件执行了其未设计的操作。另请参阅Windows 命令解释器 (CMD.EXE) 如何解析脚本?

\n\n

有两种解决方案:

\n\n
    \n
  1. 启用延迟扩展!MyVar!并使用或引用环境变量,"!MyVar!"因为现在用户输入字符串不再影响cmd.exe解析后执行的命令行\xc2\xa0by。
  2. \n
  3. 如果该字符串不应包含双引号字符,则从用户输入字符串中删除所有内容。 "
  4. \n
\n\n

字符"在字符串中绝对无效,字符串应该是(十进制)范围内的0数字20000。因此,可以使用另外两行来防止由".

\n\n
set "MyVar=%MyVar:"=%"\nif not defined MyVar goto PromptUser\n
Run Code Online (Sandbox Code Playgroud)\n\n

Windows 命令处理器在替换%MyVar:"=%为结果字符串之前会删除解析此行时已存在的所有双引号。因此最终执行的命令行set "MyVar=whatever was entered by the user"是安全执行的。

\n\n

上面的示例错误地输入了 a,"而不是2导致执行set "MyVar="取消定义环境变量,这就是为什么在进一步处理用户输入之前必须再次使用之前使用的IFMyVar条件的原因。

\n\n

3. 用户输入了无效字符

\n\n

用户应输入范围为的正十进制数。因此,用户输入字符串中的任何其他字符肯定是无效的。检查是否有任何无效字符可以通过以下方式完成:0200000123456789

\n\n
for /F delims^=0123456789^ eol^= %%I in ("%MyVar%") do goto PromptUser\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果整个字符串仅由数字组成,则命令FOR不会执行。goto PromptUser在所有其他情况下,包含以零个或多个数字开头的字符串;会导致执行,goto PromptUser因为输入字符串包含非数字字符。

\n\n

4. 用户输入带前导的数字0

\n\n

Windows 命令处理器将带前导的数字解释0为八进制数字。0但即使用户在开头输入一个或多个数字,该数字也应被解释为十进制数字。因此,在进一步处理变量值之前应删除前导零。

\n\n
for /F "tokens=* delims=0" %%I in ("%MyVar%") do set "MyVar=%%I"\nif not defined MyVar set "MyVar=0"\n
Run Code Online (Sandbox Code Playgroud)\n\n

FOR删除0分配给的字符串开头的所有内容,并将分配给环境变量旁边的剩余字符串分配MyVar给循环变量。IMyVar

\n\n

FOR在这种情况下运行set "MyVar=%%I",即使用户输入0000执行结果在这种特殊情况下set "MyVar="未定义环境变量MyVar。但0它是一个有效的数字,因此需要用字符串值对用户输入的带有一个或多个零的数字重新定义IF条件。MyVar00

\n\n

5. 用户输入的数字太大

\n\n

现在可以安全地使用命令IF和运算符GTR来验证用户是否输入了太大的数字。

\n\n
if %MyVar% GTR 20000 goto PromptUser\n
Run Code Online (Sandbox Code Playgroud)\n\n

82378488758723872198735897即使用户输入的值大于最大正 32 位整数值,最后的验证也会起作用,因为执行该IF2147483647条件时会导致范围溢出。有关详细信息,请参阅我对IF 奇怪结果的回答。2147483647

\n\n
\n\n

6. 可能的解决方案1

\n\n

用于安全评估范围内的用户输入数字(仅适用于0十进制数字)的整个批处理文件是:20000

\n\n
@echo off\nset "MinValue=0"\nset "MaxValue=20000"\n\n:PromptUser\nrem Undefine environment variable MyVar in case of being already defined by chance.\nset "MyVar="\nrem Prompt user for a positive number in range %MinValue% to %MaxValue%.\nset /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "\n\nif not defined MyVar goto PromptUser\nset "MyVar=%MyVar:"=%"\nif not defined MyVar goto PromptUser\nfor /F delims^=0123456789^ eol^= %%I in ("%MyVar%") do goto PromptUser\nfor /F "tokens=* delims=0" %%I in ("%MyVar%") do set "MyVar=%%I"\nif not defined MyVar set "MyVar=0"\nif %MyVar% GTR %MaxValue% goto PromptUser\nrem if %MyVar% LSS %MinValue% goto PromptUser\n\nrem Output value of environment variable MyVar for visual verification.\nset MyVar\npause\n
Run Code Online (Sandbox Code Playgroud)\n\n

此解决方案使批处理文件编写器还可以输出错误消息,通知用户批处理文件不接受输入字符串的原因。

\n\n

如果具有值,则不需要带有运算符的最后一个IF条件,这就是为什么在此用例中使用命令REM将其注释掉的原因。LSSMinValue0

\n\n
\n\n

7. 可能的解决方案2

\n\n

这是一种更安全的解决方案,其缺点是用户无法输入带有一个或多个前导的十进制数0,但仍按 \xc2\xa0users 通常的预期将其解释为十进制。

\n\n
@echo off\nset "MinValue=0"\nset "MaxValue=20000"\n\n:PromptUser\nrem Undefine environment variable MyVar in case of being already defined by chance.\nset "MyVar="\nrem Prompt user for a positive number in range %MinValue% to %MaxValue%.\nset /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "\n\nif not defined MyVar goto PromptUser\nsetlocal EnableDelayedExpansion\nset /A "Number=MyVar" 2>nul\nif not "!Number!" == "!MyVar!" endlocal & goto PromptUser\nendlocal\nif %MyVar% GTR %MaxValue% goto PromptUser\nif %MyVar% LSS %MinValue% goto PromptUser\n\nrem Output value of environment variable MyVar for visual verification.\nset MyVar\npause\n
Run Code Online (Sandbox Code Playgroud)\n\n

此解决方案使用延迟环境变量扩展,如上面第 2 点的第一个选项所示。

\n\n

算术表达式用于将用户输入字符串转换为带符号的 32 位整数,将该字符串解释为十进制、八进制或十六进制数字,并返回到分配给NumberWindows 命令处理器在其上使用十进制数字系统的环境变量的字符串。由于用户字符串无效而导致计算算术表达式时出现的错误输出将被重定向到设备NUL以抑制该错误。

\n\n

接下来,如果算术表达式创建的数字字符串与用户输入的字符串不同,则使用延迟扩展进行验证。如果用户输入无效,包括具有由八进制解释的前导零的数字或输入的十六进制数字(如或 ) ,则此IF条件为真。cmd.exe0x140xe3

\n\n

在传递字符串比较时,可以安全地使用运算符and来比较MyVarwith的值。200000GTRLSS

\n\n

请阅读此答案以了解有关命令SETLOCALENDLOCAL的详细信息,因为在运行方面要做的事情要多得多setlocal EnableDelayedExpansionendlocal而不仅仅是启用和禁用延迟环境变量扩展。

\n\n
\n\n

8. 可能的解决方案3

\n\n

0如果值超出有效范围,还有一种使用更少命令行的解决方案,即用户输入的数字必须更大0

\n\n
@echo off\nset "MinValue=1"\nset "MaxValue=20000"\n\n:PromptUser\nrem Undefine environment variable MyVar in case of being already defined by chance.\nset "MyVar="\nrem Prompt user for a positive number in range %MinValue% to %MaxValue%.\nset /P "MyVar=Enter number [%MinValue%,%MaxValue%]: "\nset /A MyVar+=0\nif %MyVar% GTR %MaxValue% goto PromptUser\nif %MyVar% LSS %MinValue% goto PromptUser\n\nrem Output value of environment variable MyVar for visual verification.\nset MyVar\npause\n
Run Code Online (Sandbox Code Playgroud)\n\n

此代码用于set /A MyVar+=0将用户输入的字符串转换为 32 位有符号整数值,并将 \xc2\xa0 转换回aschipfl在上面的评论中建议的字符串。

\n\n

如果用户根本没有输入任何\xc2\xa0字符串,则的值MyVar位于带有算术表达式的命令行之后。如果用户输入字符串的第一个字符不是这些字符(例如or或 ) ,0也是如此。0-+0123456789"/(

\n\n

以数字或or开头且下一个字符是数字的用户输入字符串将转换为整数值并返回字符串值。输入的字符串可以是十进制数、八进制数或十六进制数。请看一下我对Symbol equal to NEQ, LSS, GTR, etc. in Windows批处理文件的回答,它详细解释了Windows命令处理器如何将字符串转换为整数值。-+

\n\n

此代码的缺点是,此代码无法检测到在德文键盘上按住按键7"(728导致的错误输入字符串(例如,而不是)。对用户错误输入有价值。Windows 命令处理器仅将十进制、十六进制或八进制数的第一个无效字符之前的\xc2\xa0 字符解释为整数值\xc2\xa0,并忽略字符串的其余部分。Shift2(MyVar77"(

\n\n

使用此代码的批处理文件可以安全地防止批处理文件处理意外退出,因为无论用户输入什么,都不会发生语法错误。但是,在某些情况下,代码未检测到错误输入的数字为\xc2\xa0,导致使用用户不想使用的数字进一步处理批处理文件。

\n