2

假设在启用延迟扩展的情况下,想要使用子字符串替换语法用感叹号替换某些子字符串,他们必须使用立即(正常)扩展,因为解析器无法区分扩展的 s 和文字的扩展。!

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

以下脚本将!字符串中的 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

只要走线...:

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

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

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

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

我浏览了这篇文章Windows 命令解释器 (CMD.EXE) 如何解析脚本?但我找不到这种行为的解释,因为我知道正常(或立即,百分比)扩展是在延迟扩展发生之前很久就完成的,甚至可以识别任何感叹号。插入符号的识别和转义似乎也在之后发生。此外,通常在解析器中隐藏插入符号的字符串周围甚至还有引号。

4

1 回答 1

1

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

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

问题是,立即(正常,%)扩展是在相当早的阶段完成的,而延迟扩展(!),顾名思义,是作为最后步骤之一完成的。因此,立即展开的弦也会经过延迟展开阶段。作为证明,将变量设置VARValue!X!X0然后执行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`

Usingset "DELEXP=%DELEXP:^!=`%"会按字面意思搜索^!,所以当然不会找到任何出现的地方(所以!保留了原始字符串中的所有文字,最后通过延迟扩展对其进行处理)。

替换完美地set "DELEXP=%DELEXP:`=!%"替换`!,结果字符串是string!with!exclamation!marks!,但是之后的延迟扩展会消耗这些字符串。

转义的替换%DELEXP:`=^!%替换`^!字面意思,所以结果是string^!with^!exclamation^!marks^!; 在延迟扩展阶段之后处理转义,最终产生文字!和返回字符串string!with!exclamation!marks!


根据帖子Windows命令解释器(CMD.EXE)如何解析脚本?,还有第二个阶段发生逃逸,即延迟扩展阶段。这适用于原始问题中的示例,因为第一个转义(在特殊字符识别阶段)由于周围的引号而被禁用(省略这些会导致需要双重转义,如^^!)。

于 2016-07-19T06:26:56.523 回答