在Windows批处理文件中,IF条件中的"..."和x"..."有什么区别?

6
我最近发现了这篇文章【如何判断一个字符串是否为另一个字符串的子串】,其中提到需要考虑:
@setlocal enableextensions enabledelayedexpansion
@echo off
set str1=%1
if not x%str1:bcd=%==x%str1% echo It contains bcd
endlocal

那么

x 在等式两边之前是为了确保字符串 bcd 正常工作。它还可以防止某些“不适当”的起始字符。

然而,我没有找到任何关于这个 x 的实际效果的解释。那么 x"%string%""%string%" 之间有什么区别呢?


1
你也可以使用其他字母字符代替“x”。'x'并没有什么特别之处。 - DYZ
4
我一直在引用我的字符串比较:if not "%str1:bcd=%"=="%str1%" - Squashman
2个回答

8
那只是一个非常糟糕的字符串比较代码。两侧的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 分配给环境变量 str1IF条件会将环境变量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 /?

另请参见:


5
在一个字符串前添加字母x(或其他任何字母)可以确保在字符串为空的情况下,关系语句在语法上是有效的。假设str1是一个空字符串。那么,在替换后,比较%str1:bcd=%==%str1%变成了==,这是语法上无效的。但是,通过在前面加上x,比较变成了x==x,可以进行评估。自然地,将相同的前缀添加到两个字符串中不会影响它们的相等性或不相等性。

但是如果比较的字符串本身有空格,则它不会保护比较。 - Squashman
2
不,它不会(但是要责备原始帖子!)。为此,一对双引号就足够了。 - DYZ
str1为空时,表达式%str1:bcd=%==%str1%实际上不会得到==,而是bcd=str1(运行包含@set "str1="@echo/%str1:bcd=%==%str1%的批处理文件)。我知道这看起来很奇怪,但是当变量为空时,cmd.exe会中止子字符串替换/扩展,因此第二个%实际上被解释为另一个开放性的标示符。还可以看看这篇文章…… - aschipfl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接