转义替换字符串中需要的感叹号而不是搜索字符串中的感叹号(子字符串替换延迟扩展)?

Escaping exclamation marks required in replace string but not in search string (substring replacement with delayed expansion on)?

假设一个人想用感叹号替换某些子字符串,使用 substring replacement syntax while delayed expansion 启用,他们必须使用立即(正常)扩展,因为解析器无法区分 !s 用于扩展和字面上的。

但是,为什么要在替换字符串中转义感叹号呢?为什么在搜索字符串中的感叹号被转义时没有必要甚至破坏性?

以下脚本将字符串中的 !s 替换为 ` 并在之后以相反的顺序替换,因此我希望结果等于初始字符串(不得包含任何后退 -当然是自己打勾):

@echo off
setlocal EnableExtensions DisableDelayedExpansion
rem This is the test string:
set "STRING=string!with!exclamation!marks!"
set "DELOFF=%STRING%"
set "DELOFF=%DELOFF:!=`%"
set "DELOFF=%DELOFF:`=!%"
setlocal EnableDelayedExpansion
set "DELEXP=!STRING!"
set "DELEXP=%DELEXP:!=`%"
set "DELEXP=%DELEXP:`=!%"
echo(original   string: !STRING!
echo(normal  expansion: !DELOFF!
echo(delayed expansion: !DELEXP!
endlocal
endlocal
exit /B

这个结果肯定不是我想要的,最后一串不一样:

original   string: string!with!exclamation!marks!
normal  expansion: string!with!exclamation!marks!
delayed expansion: stringexclamation

一接电话...:[=​​23=]

set "DELEXP=%DELEXP:`=!%"

...并用 ^! 替换 ! ,因此转义替换字符串中的感叹号,结果正是我所期望的:

original   string: string!with!exclamation!marks!
normal  expansion: string!with!exclamation!marks!
delayed expansion: string!with!exclamation!marks!

当我尝试其他转义组合时(在替换和搜索字符串中转义感叹号,或仅在后者中转义),结果又是上述不需要的组合。

我浏览了 post How does the Windows Command Interpreter (CMD.EXE) parse scripts? 但我找不到对这种行为的解释,因为我了解到正常(或立即,百分比)扩展在延迟扩展发生之前很久就完成了并且甚至可以识别任何感叹号。插入符识别和转义似乎也在之后发生。此外,通常在解析器中隐藏插入符号的字符串周围甚至还有引号。

实际上,子字符串替换本身不需要转义。只有后面的解析阶段才有必要。这就是为什么:

However, why does one have to escape exclamation marks in the replacement string?

事实是,即时(正常,%)扩展是在相当早的阶段完成的,而延迟扩展(!),顾名思义,是作为以下之一完成的最后的步骤。因此,立即展开的字符串也会经过延迟展开阶段。作为证明,设置变量VARValue!X!X0,然后执行echo %VAR%,结果为Value0
但是回到最初的问题,当使用立即子串替换时,替换字符串是扩展值的一部分,所以它也经过了延迟扩展阶段。因此,必须转义文字感叹号,以免被延迟扩展消耗。这意味着替换本身不需要转义,它实际上是在之后完成的,因此包含转义的给定替换字符串是按字面意义应用的。

And why is it not necessary and even disruptive when exclamation marks in the search string are escaped?

由于插入符识别和转义发生在立即扩展之后,所以搜索字符串按字面意思处理。此外,搜索字符串被替换,因此不包括在立即子字符串替换的输出中,因此它不会通过延迟扩展阶段。


让我们看一下原始示例(仅摘录):

set "STRING=string!with!exclamation!marks!"
setlocal EnableDelayedExpansion
set "DELEXP=!STRING!"
set "DELEXP=%DELEXP:!=`%"
set "DELEXP=%DELEXP:`=!%"
echo(delayed expansion: !DELEXP!
endlocal

替换 set "DELEXP=%DELEXP:!=`%" 搜索 !。结果值为 string`with`exclamation`marks`.

使用set "DELEXP=%DELEXP:^!=`%"会按字面搜索^!,所以当然不会找到任何匹配项(因此保留了原始字符串中的所有文字!,它们由终于推迟了扩张)。

替换set "DELEXP=%DELEXP:`=!%"`完美替换为!,结果字符串为string!with!exclamation!marks!,但被后续延迟扩展消耗。

转义替换 %DELEXP:`=^!% 按字面意思用 ^! 替换了 `,所以结果是 string^!with^!exclamation^!marks^!;转义在延迟扩展阶段之后进行处理,最终导致文字 ! 和 return 字符串 string!with!exclamation!marks!


根据post How does the Windows Command Interpreter (CMD.EXE) parse scripts?,还有第二个阶段发生逃逸,即延迟扩张阶段。这是适用于原始问题中示例的那个,因为第一次转义(在特殊字符识别阶段)由于周围的引号而被禁用(省略这样会导致需要像 [=35 这样的双重转义=]).