无法在批处理文件中转义感叹号

时间:2016-10-16 02:29:46

标签: batch-file escaping

嘿伙计我在编码方面相当新,我似乎无法使用^^!来逃避感叹号。我知道我需要使用setlocal disabledelayedexpansion或只使用endlocal,但我无法找到正确的位置而不会出现任何错误。

这是我的脚本,用于在cmd窗口的水平中心显示文本:

@echo off
setlocal enabledelayedexpansion
title Center Text
mode 80,50

set "cmdwidth=80"
:Display
cls
set Center=This is a test^^! & call :CenterText Center strLen
echo.
pause
exit

:CenterText
  if not "!%1:~%len%!"=="" set /A len+=1 & goto :CenterText
(endlocal & set %2=%len%)

goto CenterTextDisplay
:AddSpace
set "spaces=%spaces% "
goto :eof
:CenterTextDisplay
set /a "indent=(cmdwidth - strLen)/2" 
set "spaces= "
for /l %%a in (1,1,%indent%) do call :AddSpace
echo %spaces%%Center%
set "len=0"
goto :eof

这是我的代码没有错误,但我无法正确转义感叹号,结果是This is a test而不是This is a test!

1 个答案:

答案 0 :(得分:2)

每次启用延迟扩展时,当文字字符串包含感叹号时,以及当包含感叹号的变量立即展开(正常%扩展)时,感叹号会丢失(或导致其他意外结果)在其字符串值;对于for参数(例如,%%I)和参数引用(例如,%1)也是如此,因为所有这些参数在延迟扩展发生之前都会被扩展。
为了避免任何此类问题,只有在实际需要时才应启用延迟扩展。

在您的代码中,您可以全局启用延迟扩展。变量Center实际上在您正确转义时会保留感叹号,但只要在行%Center%中展开echo %spaces%%Center%就会丢失。

以下是改编后的剧本:

@echo off
setlocal DisableDelayedExpansion
title Center Text
mode 80,50

set "cmdwidth=80"
:Display
cls
set "Center=This is a test!" & call :CenterText Center strLen
echo/
pause
endlocal
exit /B

:CenterText
setlocal EnableDelayedExpansion
:CenterText_Loop
if not "!%~1:~%len%!"=="" set /A len+=1 & goto :CenterText_Loop
endlocal & set "%~2=%len%"
set /a "indent=(cmdwidth-strLen)/2" 
set "spaces= "
for /l %%a in (1,1,%indent%) do call :AddSpace
echo(%spaces%%Center%
set "len=0"
goto :eof

:AddSpace
set "spaces=%spaces% "
goto :eof

除了纠正延迟扩展问题外,我还修正了以下内容:

  • exit更改为exit /B以仅终止批处理脚本,但不终止父cmd实例;
  • echo.更改为echo/,因为如果当前目录中的文件名为echo(无文件扩展名),前者可能会失败;
  • 通过引用整个赋值表达式来改进set语法;
  • %1更改为%~1,将%2更改为%~2,以从展开的值中删除潜在的周围引号;
  • 向下移动子例程:AddSpace以澄清执行流程并避免需要标签:CenterTextDisplay和相关的goto;
    实际上,如果用这个替换命令行for /l %%a in (1,1,%indent%) do call :AddSpace,你甚至可以删除该子程序:

    for /l %%a in (1,1,%indent%) do call set "spaces=%%spaces%% "
    

    这说明了延迟扩展的替代方法:将变量周围的%符号加倍并使用call;但是这并不适用于所有情况,因为某些字符可能仍然会导致问题,for无法运行某些命令(如ifcall);