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

时间:2019-07-19 07:54:42

标签: windows batch-file cmd

我知道在批处理文件中比较相等性时,通常将双方都用引号引起来,例如

IF "%myvar% NEQ "0" 

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

IF %myvar% GTR 20000

需要说明的是,如果未声明变量%myvar%,那就像在做

IF GTR 20000

这是语法错误。

我想出了以下解决方法:

IF 1%myvar% GTR 120000

如果IF 1 GTR 120000未定义,我希望它会导致myvar,并且似乎可行。

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

2 个答案:

答案 0 :(得分:3)

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

@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]: "

正如我在How to stop Windows command interpreter from quitting batch file execution on an incorrect user input?上的回答所解释的那样,用户可以自由输入任何内容,包括字符串,这很容易导致由于语法错误而导致批处理文件执行中断,或者导致批处理文件正在执行某些操作没写。


1。用户未输入任何内容

如果用户仅按下键 RETURN ENTER ,则命令 SET 根本不会修改环境变量MyVar。在这种情况下,很容易通过明确未定义的环境变量MyVar来验证,然后提示用户是否使用以下命令输入了字符串:

if not defined MyVar goto PromptUser

注意:可以使用与set "MyVar="之类的set "MyVar=1000"之类的东西来定义默认值,该值甚至可以在提示时输出,从而使用户可以按 RETURN ENTER 使用默认值。

2。用户输入了一个或多个"

用户可能有意或错误地输入带有一个或多个"的字符串。例如,在当前启用了 CapsLock 的非数字键盘上按德语键盘键 2 会导致输入"。因此,如果用户快速敲击 2 RETURN 或不像许多人在键盘上打字那样在屏幕上看到,则会输入双引号而不是2用户错误输入。

MyVar上保存具有一个或多个"所有%MyVar%"%MyVar%"环境变量引用的字符串是有问题的,因为%MyVar%被Windows命令替换了由用户输入的带有一个或多个"的字符串处理程序,这几乎总是会导致语法错误,或者批处理文件执行的操作根本不适合。另请参见How does the Windows Command Interpreter (CMD.EXE) parse scripts?

有两种解决方案:

  1. 启用delayed expansion并使用!MyVar!"!MyVar!"引用环境变量,因为现在用户输入的字符串在解析后不再影响cmd.exe执行的命令行。
  2. 如果该字符串永远不应包含双引号字符,请从用户输入字符串中删除全部 "

字符"在字符串中绝对无效,该字符串应为020000范围内的数字(十进制)。因此,可以使用另外两行来防止由"引起的用户输入字符串的错误处理。

set "MyVar=%MyVar:"=%"
if not defined MyVar goto PromptUser

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

上面的示例错误输入了"而不是2,导致执行了set "MyVar=",这未定义环境变量MyVar,这就是 IF 条件。

3。用户输入的无效字符

用户应在020000范围内输入正十进制数。因此,用户输入字符串中除0123456789以外的任何其他字符绝对无效。例如,可以使用以下命令检查任何无效字符:

for /F delims^=0123456789^ eol^= %%I in ("%MyVar%") do goto PromptUser

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

4。用户输入的数字前导0

Windows命令处理器将前导0的数字解释为八进制数字。但是,即使用户以1或更大的0开始输入,该数字也应解释为十进制数字。因此,应先删除前导零,然后再进一步处理变量值。

for /F "tokens=* delims=0" %%I in ("%MyVar%") do set "MyVar=%%I"
if not defined MyVar set "MyVar=0"

FOR 删除分配给0的字符串开头的所有MyVar并将剩余的字符串(分配给环境变量{)分配给循环变量I {1}}。

在这种情况下,即使在用户输入MyVarset "MyVar=%%I"的情况下,

FOR 也会运行0,并且执行000的结果是未定义环境变量{ {1}}在这种特殊情况下。但是set "MyVar="是有效数字,因此必须使用 IF 条件在用户输入的数字MyVar上以1的字符串值0重新定义MyVar或更多零。

5。用户输入的数字太大

现在可以安全地将命令 IF 与运算符0一起使用,以验证用户输入的数字是否太大。

0

即使用户输入的GTR大于最大正32位整数值if %MyVar% GTR 20000 goto PromptUser ,最后一次验证也有效,因为范围溢出导致执行此82378488758723872198735897 > IF 条件。有关详细信息,请参见我在weird results with IF上的回答。


因此,用于安全评估用户输入编号21474836472147483647的整个批处理文件为:

0

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


这是一种更安全的解决方案,其缺点是用户无法输入十进制数字,而一个或多个前导20000仍是用户通常期望的十进制解释。

@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]: "

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 20000 goto PromptUser

rem Output value of environment variable MyVar for visual verification.
set MyVar
pause

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

使用算术表达式将用户输入的字符串转换为带符号的32位整数,将其解释为十进制,八进制或十六进制数,然后返回分配给环境变量0的字符串,在该变量上环境变量为@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]: " 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 20000 goto PromptUser if %MyVar% LSS 0 goto PromptUser rem Output value of environment variable MyVar for visual verification. set MyVar pause Windows命令处理器使用。由于用户字符串无效而导致对算术表达式求值时出现的错误输出被重定向到设备 NUL 来对其进行抑制。

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

在通过字符串比较后,可以使用运算符0xe3MyVar比较200000GTR的值。

请阅读this answer,以获取有关命令 SETLOCAL ENDLOCAL 的详细信息,因为运行LSS和{{1} },而不仅仅是启用和禁用延迟的环境变量扩展。

答案 1 :(得分:1)

应答对nitpick的呼叫

Mofi一直要求我在这里编写自己的解决方案,即“ 更短”,正如我向他指出的那样,他使用&而不是{{1}编写代码的方式},然后是命令,然后是回车符和另一个命令,或者`((接着是回车符,接着是另一个命令,再是回车符,接着是另一个命令)设置了一个先例,这使这很难达成共识。 / p>

我也不是说这曾经是提供答案的要点,但是当变化很小时,主要是固定逻辑,或者提供稍有不同的解决方案,这真的有很大的不同吗?真的有必要单独回答吗?

那就是说,如果不编辑他的回答,我看不出有更好的方法。但是,这仍然使待解决的问题悬而未决。

不幸的是,在与Mofi进行讨论时,他已编辑了对可能导致无效选择的答案。

虽然我已经指出了这一点,但是我确定这只是他的一个小问题,但我觉得没有在此处发布代码会导致他积极地降低问题的质量,这总是有可能的挑剔的结果。

虽然Mofi是该活动的推动力,但我不喜欢它对他的影响,因为我正试图通过不去使用来避免对我的代码产生这种影响,所以我决定发布代码比较为他们带来一些封闭。

请不要,我将发布他的原始代码(最新的未使用错误方法的代码),然后将其重构为我将如何编写,然后将发布我的原始代码,然后将其重构为我的原始代码。相信他会写(可能不按顺序,但我会逐一列出)

所以下面是结果

Mofi原始文件:

这很难说,如果您应该计算每一行,在某些情况下,使用&排队命令,而IFS从不使用括号,这是我通常不会使用的。

(

我的代码重构为Mofi的形式

@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

Mofi的代码已重构

Mofi的上述代码重构为我更紧凑的形式,其中@ECHO OFF SETLOCAL EnableDelayedExpansion SET /A "_Min=-1","_Max=20000" :Menu CLS SET "_Input=" REM Prompt user for a positive number in range %_Min% to %_Max%. SET /P "_Input=Enter number [%_Min%,%_Max%]: " SET /A "_Tmp=%_input%" && if /I "!_input!" EQU "!_Tmp!" if !_Input! GEQ %_Min% if !_Input! LEQ %_Max% SET _Input & pause & GOTO :EOF GOTO :Menu 跟随第一个命令,但在(语句上使用时除外,而IF跟随最后一个命令。这也使得真正进行验证的整个部分容易辨认,仅是)函数中的部分,不计算:PromtUser行或REM行,这是13行代码

blank

我的紧凑格式的代码

这里要比较的是我的代码,也是我将Mofi的代码重构到上面的紧凑形式。同样,只有函数本身内部的行在这里“繁重”,需要进行比较。我确实忘记了,当我最初编写代码时,我试图匹配Mofi的形式,这让我在将&&(下一行或全部都保留为单行)方面格外高兴。因此,我将发布两个变体

@(SETLOCAL
  echo off
  SET /A "MinValue=0","MaxValue=20000")

CALL :Main

( ENDLOCAL
  EXIT /B )

:Main
  CALL :PromptUser MyVar
  REM Output value of environment variable MyVar for visual verIFication.
  SET MyVar
  PAUSE
GOTO :EOF


:PromptUser
  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 )
GOTO :EOF

我的紧凑格式2中的代码

@(SETLOCAL ENABLEDELAYEDEXPANSION
  ECHO OFF
  SET /A "_Min=-1","_Max=20000" )

CALL :Main

( ENDLOCAL
  EXIT /B )

:Main
  CALL :Menu _input
  REM Output value of environment variable _input for visual verIFication.
  SET _input
  PAUSE
GOTO :EOF


:Menu
  CLS
  SET "_input="
  REM Prompt user for a positive number in range %_Min% to %_Max%. Store it in "_input"
  SET /P "_Input=Enter number [%_Min%,%_Max%]: "
  SET /A "_Tmp=%_input%" && (
    IF /I "!_input!" EQU "!_Tmp!" IF !_Input! GEQ %_Min% IF !_Input! LEQ %_Max% GOTO :EOF )
GOTO :Menu