以下是一份正常运行批处理代码:
@echo off
setlocal EnableExtensions EnableDelayedExpansion
rem ...
if "%$ecbId%" == "" (
echo Enter '1' to blabla
echo Enter '2' to blabla
echo Enter anything else to abort.
echo/
set "UserChoice=abort"
set /P "UserChoice=Type input: "
if "!UserChoice!" == "1" (
echo toto1
rem save into property file
>>MDC.properties echo currentMaster=%ecb%
>>MDC.properties echo masterDcName=ECBDC
>>MDC.properties echo currentClone=%bdf%
>>MDC.properties echo cloneDcName=BDFDC
)
if "!UserChoice!" == "2" (
echo toto2
rem save into property file
>>MDC.properties echo currentMaster=%bdf%
>>MDC.properties echo masterDcName=BDFDC
>>MDC.properties echo currentClone=%ecb%
>>MDC.propertiesecho cloneDcName=ECBDC
)
if not "!UserChoice!" == "1" (
echo toto3
if not "!UserChoice!" == "2" (
echo toto4
echo Unknown input ... Aborting script
endlocal
exit /B 400
)
)
)
endlocal
延迟扩展通过命令setlocal EnableExtensions EnableDelayedExpansion
在批处理文件顶部启用。命令SETLOCAL不仅启用了延迟扩展,还将整个命令进程环境保存在堆栈上,如我在此回答中详细解释的那样。在执行命令ENDLOCAL时,命令进程环境将从堆栈中恢复,这也可以由Windows命令处理器在退出批处理文件处理时隐式完成,而无需显式执行endlocal
。
在以(
开始并以匹配的)
结束的命令块中设置或修改的所有环境变量,在同一命令块内引用时必须使用延迟扩展。
当Windows命令处理器到达第四行时,从第4行到倒数第二行的整个命令块都将被解析和预处理,然后才会评估IF条件。在预处理步骤中,从第4行到第36行的整个代码块中使用百分号引用的所有环境变量都将被扩展。
这意味着在第4行评估任何IF条件之前,%ecb%
和%bdf%
的所有出现都将被当前环境变量ecb
和bdf
的值替换,就像IF条件本身中的%$ecbId%
一样。
仅使用延迟扩展引用的环境变量UserChoice
在解析/预处理整个命令块时不会被扩展。
因此,可以将默认值abort
分配给环境变量UserChoice
,以备批处理用户只按下RETURN或ENTER而未输入任何内容的情况。
由于使用了环境变量UserChoice
的延迟扩展,用户输入的字符串永远不会在解析/预处理阶段扩展。每次评估IF条件时,总是解释UserChoice
的当前字符串值。
可以通过以下方法轻松观察Windows命令处理器的这种行为:
- 从批处理文件的第一行中删除
@echo off
,
- 打开命令提示符窗口,
- 在命令提示符窗口中运行批处理文件,如果当前目录不是批处理文件的目录,则输入带有完整路径的批处理文件名称。
Windows命令处理器现在始终在实际执行之前输出解析后的代码块或行。因此,可以看到,在到达第一个IF条件时,整个命令块已经预处理并使用适当的字符串从环境变量中替换了所有%...%
的出现,分别是环境变量未定义时的空字符串。可以看到,在Windows命令解析器执行整个命令块中的下一条命令时,%ecb%
和%bdf%
的值永远不会再次被修改。
更
@echo off
setlocal EnableExtensions EnableDelayedExpansion
rem ...
if "%$ecbId%" == "" (
echo Enter '1' to blabla
echo Enter '2' to blabla
echo Enter anything else to abort.
echo/
set "UserChoice=abort"
set /P "UserChoice=Type input: "
if "!UserChoice!" == "1" (
echo toto1
rem save into property file
>>MDC.properties echo currentMaster=%ecb%
>>MDC.properties echo masterDcName=ECBDC
>>MDC.properties echo currentClone=%bdf%
>>MDC.properties echo cloneDcName=BDFDC
) else if "!UserChoice!" == "2" (
echo toto2
rem save into property file
>>MDC.properties echo currentMaster=%bdf%
>>MDC.properties echo masterDcName=BDFDC
>>MDC.properties echo currentClone=%ecb%
>>MDC.properties echo cloneDcName=ECBDC
) else (
echo Unknown input ... Aborting script
endlocal
exit /B 400
)
)
endlocal
另一个解决方案是使用自Windows Vista以来默认可用的choice
命令。它的优点是用户只需要按下三个键中的一个即可。在这种情况下,不需要延迟环境变量扩展,因为choice
退出一个值,该值取决于按下的键,该键分配给环境变量errorlevel
,即使在命令块内部也可以评估而无需延迟扩展。
@echo off
rem ...
if "%$ecbId%" == "" (
echo Enter '1' to blabla
echo Enter '2' to blabla
echo Enter 'A' to abort.
echo/
%SystemRoot%\System32\choice.exe /C 12A /N /M "Type input:"
if errorlevel 3 (
echo Aborting script
exit /B 400
) else if errorlevel 2 (
echo toto2
rem save into property file
>>MDC.properties echo currentMaster=%bdf%
>>MDC.properties echo masterDcName=BDFDC
>>MDC.properties echo currentClone=%ecb%
>>MDC.properties echo cloneDcName=ECBDC
) else (
echo toto1
rem save into property file
>>MDC.properties echo currentMaster=%ecb%
>>MDC.properties echo masterDcName=ECBDC
>>MDC.properties echo currentClone=%bdf%
>>MDC.properties echo cloneDcName=BDFDC
)
)
用户必须按下
1,导致
choice
以值
1
退出,因为在选项
/C
之后指定了字符
1
作为第一个字符,或者按下
2让
choice
以值
2
退出,或者按下不区分大小写的
A键,结果
choice
会以值
3
退出,因为在选项
/C
之后的下一个参数字符串中指定了字符
A
作为第三个字符。此外,还可以按
Ctrl+C来退出批处理文件处理,需要额外确认。其他所有按键都将被
choice
忽略,并且将发出响铃声以通知用户按下了错误的键。
现在最好使用命令
choice
进行此类用户提示,只需使用文件名或完全限定的文件名即可获得最大的执行安全性。