那只是一个非常糟糕的字符串比较代码。两侧的
x
使得即使在解析整个命令行时,
%str1:bcd=%
或
%str1%
被Windows命令处理器替换为空字符串,也可以比较这两个字符串。
但是,如果环境变量
str1
的值包含空格字符或
"&<>|
,则批处理文件执行仍然会因为语法错误而立即退出。
cmd.exe
将参数字符串用双引号括起来会导致所有字符(百分号除外),并且启用延迟的环境变量扩展,感叹号也会被解释为文字字符,包括空格在内的双引号之外的字符会被解释为参数字符串分隔符。
所以更好的写法是:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
if "%~1" == "" goto EndBatch
set "str1=%~1"
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
批处理文件的第一个参数首先将不带任何双引号与空字符串进行比较。因此,如果批处理文件没有任何参数或只有
""
作为第一个参数字符串启动,则 Windows 命令处理器执行命令
GOTO,导致恢复先前推到栈上的环境,并退出批处理文件。
否则,批处理文件真正地以参数字符串被调用。如果有双引号,则将此参数字符串分配给环境变量
str1
,并删除周围的双引号。因此,在使用参数
test
调用批处理文件时,值
test
被分配给环境变量
str1
,在使用
"another test"
调用它时,值
another test
而不带双引号的值被分配给
str1
。即使使用错误编码的参数字符串
"bcd test
(缺少第二个
"
),也会将
bcd test
分配给环境变量
str1
。
IF条件会将环境变量
str1
的值与未修改的变量值中删除所有
bcd
后的所有出现进行比较。两个字符串周围的双引号使得即使包含空格、&符号或重定向运算符
<>|
,也可以比较这两个字符串。在比较这两个字符串时,命令
IF会包括双引号。
那么,现在这段代码安全了吗?
不,如果有人以缺少第一个双引号的参数字符串test_bcd"
调用无效的批处理文件,则不安全。在这种情况下,由cmd.exe
执行的第一条IF命令行是:
if "test_bcd"" == "" goto EndBatch
错误指定的参数字符串的末尾引号未被
cmd.exe
移除,在执行此命令行时会导致语法错误,可以在将批处理文件从命令提示符窗口运行并将第一行修改为
@echo on
时看到此错误。
不使用延迟环境变量扩展的解决方案之一是:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
if not defined str1 goto EndBatch
set "str1=%str1:"=%"
if not defined str1 goto EndBatch
if not "%str1:bcd=%" == "%str1%" echo It contains bcd
:EndBatch
endlocal
这段代码确保在执行比较字符串的
IF命令之前,
str1
不包含任何双引号。
另一种解决方案是使用延迟环境变量扩展:
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "str1=%~1"
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
上述代码没有使用延迟环境变量扩展,看起来比较好。但如果参数字符串是例如"!Hello!"
,则无法按预期工作,因为在这种情况下,if not
条件也为true,因此输出消息It contains bcd
,尽管字符串!Hello!
不包含bcd
。
解决方案是:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "str1=%~1"
setlocal EnableDelayedExpansion
if not "!str1:bcd=!" == "!str1!" echo It contains bcd
endlocal
endlocal
将参数字符串赋值给环境变量 str1
时未启用延迟扩展,这导致字符串 "!Hello!"
中的感叹号被解释为字面字符。因此,在使用延迟环境变量扩展进行字符串比较时启用了延迟扩展,这避免了在执行IF命令之前,cmd.exe
修改命令行本身。
要理解所使用的命令及其工作原理,请打开命令提示窗口,在其中执行以下命令,并非常仔细地阅读每个命令显示的所有帮助页面。
call /?
... 解释了 %~1
,但不如此处操作得好。
echo /?
endlocal /?
goto /?
if /?
set /?
setlocal /?
另请参见:
if not "%str1:bcd=%"=="%str1%"
- Squashman