Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot escape exclamation mark in batch file

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!.

like image 575
VanZuron Avatar asked Oct 29 '22 18:10

VanZuron


1 Answers

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:

  • changed exit to exit /B to only terminate the batch script but not the parent cmd instance;
  • changed echo. to echo/, because the former might fail in case ther is a file called echo (no file name extension) in the current directory;
  • improved set syntax by quoting the entire assignment expression consistently;
  • changed %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;

like image 113
aschipfl Avatar answered Nov 09 '22 04:11

aschipfl