I can't escape an exclamation mark using ^^!
. I know I need to either use setlocal disabledelayedexpansion
or just use endlocal
but I can't find the right spot to put it without getting any errors.
Here is my script that is intended to display text at the horizontal center of the cmd
window:
@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 my code without errors but I cannot escape the exclamation mark properly, the result is This is a test
instead of This is a test!
.
Exclamation marks get lost (or cause other unexpected results) every time delayed expansion is enabled, when a literal string contains exclamation marks, and also when a variable is expanded immediately (normal %
expansion) that holds exclamation marks in its string value; the same is also true for for
parameters (e. g., %%I
) and argument references (e. g., %1
), because all those are expanded before delayed expansion happens.
To avoid any such problems, delayed expansion should only be enabled when it is actually needed.
In your code, you enable delayed expansion globally. The variable Center
actually holds the exclamation mark as you properly escape it, but it gets lost as soon as expanding %Center%
in line echo %spaces%%Center%
.
Here is the adapted script:
@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
Besides the correction of the delayed expansion issue, I also fixed the following things:
exit
to exit /B
to only terminate the batch script but not the parent cmd
instance;echo.
to echo/
, because the former might fail in case ther is a file called echo
(no file name extension) in the current directory;set
syntax by quoting the entire assignment expression consistently;%1
to %~1
and %2
to %~2
to remove potential surrounding quotation marks from the expanded values;moved subroutine :AddSpace
downwards to clarify execution flow and to avoid need of label :CenterTextDisplay
and the related goto
;
actually you could even remove that subroutine if you replace the command line for /l %%a in (1,1,%indent%) do call :AddSpace
by this:
for /l %%a in (1,1,%indent%) do call set "spaces=%%spaces%% "
this illustrates an alternative to delayed expansion: doubling the %
signs around a variable and using call
; this does not work in all situations though, because some characters may still cause trouble and some commands (like for
and if
) cannot be run by call
;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With