转义替换字符串所需的感叹号,但搜索字符串中没有(带延迟扩展的子字符串替换)?

时间:2016-07-15 11:52:50

标签: batch-file replace cmd escaping delayedvariableexpansion

假设有人想在启用substring replacement时使用delayed expansion语法用感叹号替换某些子字符串,则必须使用立即(正常)扩展,因为解析器无法区分{{1}用于扩展和文字扩展。

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

以下脚本用!替换字符串中的!,然后以相反的顺序替换,所以我希望结果等于初始字符串(不能包含任何反向标记)它当然是):

`

这个结果绝对不是我想要的,最后一个字符串是不同的:

@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

....并将set "DELEXP=%DELEXP:`=!%" 替换为!,因此转义替换字符串中的感叹号,结果正是我所期望的:

^!

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

我走过帖子How does the Windows Command Interpreter (CMD.EXE) parse scripts?,但我无法找到对此行为的解释,因为我了解到正常(或即时,百分比)扩展是在延迟扩展发生之前很久就完成的,并且任何感叹号都被识别。此外,似乎还发生了插入符号识别和转义。此外,字符串周围甚至还有引号,通常会将解析符隐藏在解析器中。

1 个答案:

答案 0 :(得分:1)

实际上,对于子串替换本身,不需要转义。仅对后面的解析阶段变得必要。这就是原因:

  

但是,为什么必须在替换字符串中转义感叹号?

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

  

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

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

让我们看看原始的例子(仅摘录):

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^!;在延迟扩展阶段之后处理转义,最终产生文字!和返回字符串string!with!exclamation!marks!

根据帖子How does the Windows Command Interpreter (CMD.EXE) parse scripts?,存在第二阶段,其中发生逃逸,这是延迟扩展阶段。这是适用于原始问题中的示例的那个,因为第一次转义(在特殊字符识别阶段期间)由于周围的引号而被禁用(省略这样会导致需要双重转义,如{{1 }})。