:lTrim bug and improved function template
Posted: 01 Apr 2011 12:44
There is a bug in the current :lTrim function that prevents it from supporting strings containing !
test0.bat - Test with existing code:
Results:
The :lTrim body is using EnabledDelayedExpansion yet it never attempts to use the feature.
This results in the following when variable test is expanded within the function:
1) "!kind!" is expanded to "cruel"
2) "! Goodbye!" is expanded to nothing because the variable does not exist
3) "again!" simply becomes "again" and the rest is preserved because there is no matching !
I believe the intended behavior is achieved by simply changing the SETLOCAL to use DisableDelayedExpansion. But this only works when :lTrim is called with DisableDelayedExpansion.
test1.bat - Test with fixed code:
Results:
This is a general problem with the current function template.
To handle function calls with delayed expansion we need to escape any ! in the return. But if we do that when delayed expansion is disabled we introduce unwanted ^ in the result.
To build a function that can support EnableDelayedExpansion and DisableDelayedExpansion we must:
1) detect whether the function was called with delayed expansion
2) if it was, then escape any ! in the return value before the return
test2.bat - Test with support for enabled and disabled delayed expansion:
Results:
Based on the above, I propose the following modified function template:
Dave Benham
test0.bat - Test with existing code:
Code: Select all
@echo off
setlocal disableDelayedExpansion
set kind=cruel
set test= Hello !kind! world! Goodbye! and Hello again! and so on...
set test
call :lTrim test
set test
exit /b
:lTrim string char -- strips white spaces (or other characters) from the beginning of a string
:: -- string [in,out] - string variable to be trimmed
:: -- char [in,opt] - character to be trimmed, default is space
:$created 20060101 :$changed 20080227 :$categories StringManipulation
:$source http://www.dostips.com
:::
::: Should not be using EnabledDelayedExpansion - Causes probles when ! in string
:::
SETLOCAL ENABLEDELAYEDEXPANSION
call set "string=%%%~1%%"
set "charlist=%~2"
if not defined charlist set "charlist= "
for /f "tokens=* delims=%charlist%" %%a in ("%string%") do set "string=%%a"
( ENDLOCAL & REM RETURN VALUES
IF "%~1" NEQ "" SET "%~1=%string%"
)
EXIT /b
Results:
Code: Select all
D:\utils>test0
test1= Hello !kind! world! Goodbye! and Hello again! and so on...
test1=Hello cruel world and Hello again and so on...
The :lTrim body is using EnabledDelayedExpansion yet it never attempts to use the feature.
This results in the following when variable test is expanded within the function:
1) "!kind!" is expanded to "cruel"
2) "! Goodbye!" is expanded to nothing because the variable does not exist
3) "again!" simply becomes "again" and the rest is preserved because there is no matching !
I believe the intended behavior is achieved by simply changing the SETLOCAL to use DisableDelayedExpansion. But this only works when :lTrim is called with DisableDelayedExpansion.
test1.bat - Test with fixed code:
Code: Select all
@echo off
setlocal disableDelayedExpansion
set kind=cruel
set disabled= Hello !kind! world! Goodbye! and Hello again! and so on...
set enabled=%disabled%
echo:
set disabled
call :lTrim disabled
set disabled
setlocal enableDelayedExpansion
echo:
set enabled
call :lTrim enabled
set enabled
exit /b
:lTrim string char -- strips white spaces (or other characters) from the beginning of a string
:: -- string [in,out] - string variable to be trimmed
:: -- char [in,opt] - character to be trimmed, default is space
:::
::: fixed enabled delayed expansion bug in body
::: But only works with ! in string if called with disabled delayed expansion
:::
setlocal disabledelayedexpansion
call set "string=%%%~1%%"
set "charlist=%~2"
if not defined charlist set "charlist= "
for /f "tokens=* delims=%charlist%" %%a in ("%string%") do set "string=%%a"
( endlocal & rem return values
if "%~1" neq "" set "%~1=%string%"
)
exit /b
Results:
Code: Select all
D:\utils>test1
disabled= Hello !kind! world! Goodbye! and Hello again! and so on...
disabled=Hello !kind! world! Goodbye! and Hello again! and so on...
enabled= Hello !kind! world! Goodbye! and Hello again! and so on...
enabled=Hello cruel world and Hello again and so on...
This is a general problem with the current function template.
To handle function calls with delayed expansion we need to escape any ! in the return. But if we do that when delayed expansion is disabled we introduce unwanted ^ in the result.
To build a function that can support EnableDelayedExpansion and DisableDelayedExpansion we must:
1) detect whether the function was called with delayed expansion
2) if it was, then escape any ! in the return value before the return
test2.bat - Test with support for enabled and disabled delayed expansion:
Code: Select all
@echo off
setlocal disableDelayedExpansion
set kind=cruel
set disabled= Hello !kind! world! Goodbye! and Hello again! and so on...
set enabled=%disabled%
echo:
set disabled
call :lTrim disabled
set disabled
setlocal enableDelayedExpansion
echo:
set enabled
call :lTrim enabled
set enabled
exit /b
:lTrim string char -- strips white spaces (or other characters) from the beginning of a string
:: -- string [in,out] - string variable to be trimmed
:: -- char [in,opt] - character to be trimmed, default is space
:::
::: Now can call with delayed expansion enabled or disabled and still works with ! in string
:::
if !errorlevel!==%errorlevel% (
setlocal disableDelayedExpansion
set delay=true
) else (
setlocal disableDelayedExpansion
set delay=
)
call set "string=%%%~1%%"
set "charlist=%~2"
if not defined charlist set "charlist= "
for /f "tokens=* delims=%charlist%" %%a in ("%string%") do set "string=%%a"
if defined delay set string=%string:!=^^!%
(endlocal & rem return values
if "%~1" neq "" set "%~1=%string%"
)
exit /b
Results:
Code: Select all
D:\utils>test2
disabled= Hello !kind! world! Goodbye! and Hello again! and so on...
disabled=Hello !kind! world! Goodbye! and Hello again! and so on...
enabled= Hello !kind! world! Goodbye! and Hello again! and so on...
enabled=Hello !kind! world! Goodbye! and Hello again! and so on...
Based on the above, I propose the following modified function template:
Code: Select all
:myFunctionName -- function description here
:: -- %~1: argument description here
::
:: Use this version if function body requires DisabledDelayedExpansion
::
IF !ERRORLEVEL!==%ERRORLEVEL% (
SETLOCAL DISABLEDELAYEDEXPANSION
SET delay=true
) ELSE (
SETLOCAL DISABLEDELAYEDEXPANSION
SET delay=
)
REM.--function body here
SET LocalVar1=...
SET LocalVar2=...
IF DEFINED delay (
set LocalVar1=%LocalVar1:!=^^!%
set LocalVar2=%LocalVar1:!=^^!%
)
(ENDLOCAL & REM -- RETURN VALUES
IF "%~1" NEQ "" SET %~1=%LocalVar1%
IF "%~2" NEQ "" SET %~2=%LocalVar2%
)
GOTO:EOF
:myFunctionName -- function description here
:: -- %~1: argument description here
::
:: Use this version if function body requires EnabledDelayedExpansion (untested, but should work)
::
IF !ERRORLEVEL!==%ERRORLEVEL% (
SETLOCAL ENABLEDELAYEDEXPANSION
SET "delay=ENDLOCAL&"
) ELSE (
SETLOCAL ENABLEDELAYEDEXPANSION
SET delay=
)
REM.--function body here
SET LocalVar1=...
SET LocalVar2=...
IF DEFINED delay (
SETLOCAL DISABLEDELAYEDEXPANSION
SET LocalVar1=%LocalVar1:!=^^!%
SET LocalVar2=%LocalVar1:!=^^!%
)
(%delay%ENDLOCAL & REM -- RETURN VALUES
IF "%~1" NEQ "" SET %~1=%LocalVar1%
IF "%~2" NEQ "" SET %~2=%LocalVar2%
)
GOTO:EOF
Dave Benham