Page 1 of 1
Macro $RTrim (trim trailing spaces) / also $LTrim and $Trim
Posted: 25 Dec 2011 19:42
by aGerman
EDIT: I was starting with that RTrim macro. dbenham helped to debug it and he changed the code to define various characters for trimming. He also developed macros for left-trimming ($LTRIM) and trimming both sides of the string ($TRIM). See http://www.dostips.com/forum/viewtopic.php?f=3&t=2697&p=12327#p12327Hi folks,
alan_b asked for a way to trim trailing spaces. See
http://www.dostips.com/forum/viewtopic.php?f=3&t=2694I wrote a basic macro that I try to complete. That's the current status:
Code: Select all
@echo off
:: Predefine variables and the macro $RTrim
setlocal DisableDelayedExpansion
:: LineFeed
set LF=^
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"&rem TWO EMPTY LINES ABOVE REQUIRED!
:: create variable containing 4096 spaces
setlocal EnableDelayedExpansion
set "spcs= "
for /l %%i in (1 1 8) do (
set "spcs=!spcs!!spcs!"
)
endlocal &set "spcs=%spcs%"
:: macro for right-trim
set $RTrim=for /l %%I in (1 1 2) do if %%I==2 (%\n%
for /f %%j in ("!_arg!") do set "_str=!%%j!"%\n%
set /a "k=4096"%\n%
set "spc=%spcs%"%\n%
for /l %%j in (1 1 13) do (%\n%
for %%k in (!k!) do (%\n%
if "!_str:~-%%k!"=="!spc!" (%\n%
set "_str=!_str:~0,-%%k!"%\n%
)%\n%
)%\n%
set /a "k/=2"%\n%
for %%k in (!k!) do set "spc=!spc:~%%k!"%\n%
)%\n%
ECHO "!_str!"%\n%
for /f "delims=*" %%j in ("!_arg!") do for /f "delims=" %%k in ("!_str!") do endlocal ^&set "%%j=%%k"%\n%
) else setlocal EnableDelayedExpansion ^&set _arg=
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Test
set "file=test.txt"
setlocal EnableDelayedExpansion
<"%file%" (
for /f %%n in ('type "%file%"^|find /c /v ""') do (
for /L %%i in (1 1 %%n) do (
set "LN=" &set /p "LN="
echo "!LN!"&rem BEFORE
%$RTrim% LN
echo "!LN!"&rem AFTER
echo(
)
)
)
endlocal
pause
Output:
Code: Select all
"qwert "
"qwert"
"qwert"
"qwert ! "
"qwert !"
"qwert "
"qwert ^! "
"qwert ^!"
"qwert !"
Each first line of the blocks above (but whithout quotation marks) is one line in test.txt. The second lines of each block are outputted from inside of the macro before I try to pass the endlocal command. The third lines are the results after endlocal.
As you can see the problem is that I try to transfer the variable into a "DelayedExpansion" environment. For that reason the exclamation marks and carets are stripped.
Is there a way to avoid that?
That's the relevant line of the macro:
Code: Select all
for /f "delims=*" %%j in ("!_arg!") do for /f "delims=" %%k in ("!_str!") do endlocal ^&set "%%j=%%k"%\n%
Regards
aGerman
Re: Macro RTrim (trim trailing spaces)
Posted: 26 Dec 2011 13:27
by aGerman
Well, I found a possible way ...
Code: Select all
:: macro for right-trim
set $RTrim=for /l %%I in (1 1 2) do if %%I==2 (%\n%
for /f %%j in ("!_arg!") do set "_str=!%%j!"%\n%
set /a "k=4096"%\n%
set "spc=%spcs%"%\n%
for /l %%j in (1 1 13) do (%\n%
for %%k in (!k!) do (%\n%
if "!_str:~-%%k!"=="!spc!" (%\n%
set "_str=!_str:~0,-%%k!"%\n%
)%\n%
)%\n%
set /a "k/=2"%\n%
for %%k in (!k!) do set "spc=!spc:~%%k!"%\n%
)%\n%
echo "!_str!"%\n%
if !_flag!==1 (%\n%
for /f "delims=*" %%j in ("!_arg!") do for /f "delims=" %%k in ("!_str!") do endlocal ^&set "%%j=%%k"%\n%
) else set "!_arg!=!_str!"%\n%
) else set "_flag=0" ^&(if "0" neq "!_flag!" set "_flag=1" ^&setlocal EnableDelayedExpansion) ^&set _arg=
... but I'm not happy with that solution. If it is executed in a "DelayedExpansion" environment the macro does not create it's own sub environment. That means all of the variables inside of the macro are still valid after it was processed and (even worse) if one of the variables existed before then its value is changed now
Regards
aGerman
Re: Macro RTrim (trim trailing spaces)
Posted: 26 Dec 2011 21:37
by dbenham
Hi aGerman
I have various versions of safe return for macros using the old macro style at
Batch "macros" with arguments - Major Update The safe return macros are found in file macroLib_Return.bat. They use techniques I learned from jeb in the
new functions: :chr, :asc, :asciiMap thread.
I adapted techniques from my macro.AnyRtn1 to your $RTRIM macro with the new macro style. It does not support <CR> or <LF> in the returned string. It could be added, but the only way I know dynamically builds a temporary macro that must be called after the main macro completes. And the temp macro cannot be called within the same code block as the main macro. Also, the concept of trimming spaces at the end of a string that includes <LF> seems weird to me.
Besides adding support for a safe return, I also fixed a couple bugs and optimized a bit
- You needed extra code to deal with potential empty _str throughout the life-cycle of the macro.
- I don't see the need to parse _arg twice, so I put the entire macro body within one %%J for loop.
- I eliminated the need for the spc variable which enabled me to eliminate one of the %%k for loops.
- The pesky EOL option during return was a problem. I set EOL=<LF> with the awful looking syntax that jeb figured out.
Code: Select all
set $RTrim=for /l %%I in (1 1 2) do if %%I==2 (%\n%
for /f %%J in ("!_arg!") do (%\n%
set "_str=!%%J!"%\n%
set /a "k=4096"%\n%
for /l %%j in (1 1 13) do (%\n%
if defined _str for %%k in (!k!) do (%\n%
if "!_str:~-%%k!"=="!spcs:~-%%k!" set "_str=!_str:~0,-%%k!"%\n%
set /a "k/=2"%\n%
)%\n%
)%\n%
ECHO "!_str!"%\n%
if defined _str (%\n%
if not defined _notDelayed (%\n%
set "_str=!_str:^=^^!"%\n%
set "_str=!_str:"=""Q!^"%\n%
call set "_str=%%^_str:^!=""E^!%%" ! %\n%
set "_str=!_str:""E=^!"%\n%
set "_str=!_str:""Q="!^"%\n%
)%\n%
for /f ^^^"eol^^^=^^^
^
^^^ delims^^^=^^^" %%k in ("!_str!") do endlocal^&endlocal^&set "%%J=%%k"!%\n%
) else endlocal^&endlocal^&set "%%J="%\n%
)%\n%
) else setlocal^&set "_notDelayed=!"^&setlocal EnableDelayedExpansion^&set _arg=
Dave Benham
Re: Macro RTrim (trim trailing spaces)
Posted: 27 Dec 2011 04:09
by aGerman
Hi Dave,
many thanks for your support!
I hoped there was a way without all these replacements of special characters. Especially the
call set is bugging me because it slows down tremendously.
dbenham wrote:- The pesky EOL option during return was a problem. I set EOL=<LF> with the awful looking syntax that jeb figured out.
I already fixed that yesterday (besides some other fixes), but I used the LF variable instead. That makes it more handsome.
I'm going to do some tests. I come back later with the results and probably a (tentatively) final version.
Thanks again
aGerman
Re: Macro RTrim (trim trailing spaces)
Posted: 27 Dec 2011 08:04
by dbenham
aGerman wrote:Especially the call set is bugging me because it slows down tremendously.
Amen to that. Unfortunately I can't think of any other good solution within a single macro. The ! must be escaped. Search and replace involving ! does not work with delayed expansion. Immediate expansion within a block of code cannot be used without a CALL if the variable was defined within the code block. The macro is a block of code and the variable is defined within it. I'm stuck.
The only way forward that I can see is to use something other than expansion search and replace to introduce the escape. The only pure batch way to do that is character by character search and replace - that seems much worse than CALL. That leaves a non-batch solution like VB Script, java script, or a 3rd party tool - I don't think you are interested in going down that road.
You could avoid the CALL if you split the single macro into a pair of macros that would have to be called one after the other. But then it could never be called within a block of code, and that would be extremely restrictive. (That is the problem I faced with my RtnAny1 and RtnAnyN macros.)
I still think your RTRIM macro is a major step forward, even with the CALL. The algorithm could be implemented as a "traditional" function, and then CALL is not needed to escape the !. But I think the macro form would still outperform the function form.
Dave Benham
Re: Macro RTrim (trim trailing spaces)
Posted: 27 Dec 2011 10:04
by aGerman
Thanks Dave.
dbenham wrote:Unfortunately I can't think of any other good solution within a single macro.
Neither do I. For that reason I assume your solution should be the best.
dbenham wrote:The only way forward that I can see is to use something other than expansion search and replace to introduce the escape. The only pure batch way to do that is character by character search and replace - that seems much worse than CALL. That leaves a non-batch solution like VB Script, java script, or a 3rd party tool - I don't think you are interested in going down that road.
I'm quite familiar with VBScript and JScript. I already suggested alan_b that I could write a script that would replace Lf by CrLf and trim trailing spaces at once. Writing a macro was more or less out of curiosity.
Well, finally I decided to do only 2 minor changes on your version.
- I inserted the creation of the spcs variable into the macro. Hence the predefined variables are reduced to what is needed by default. (And 2 milli seconds more ore less should not make any difference.)
- As I told before I used the LF variable to define the EOL option.
Code: Select all
@echo off
:: Predefine variables and the macro $RTrim
setlocal DisableDelayedExpansion
:: LineFeed
set LF=^
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"&rem TWO EMPTY LINES ABOVE REQUIRED!
:: macro for right-trimming
set $RTrim=for /l %%I in (1 1 2) do if %%I==2 (%\n%
set "_spcs= "%\n%
for /l %%i in (1 1 6) do set "_spcs=!_spcs!!_spcs!"%\n%
for /f %%J in ("!_arg!") do (%\n%
set "_str=!%%J!"%\n%
set /a "k=4096"%\n%
for /l %%j in (1 1 13) do (%\n%
if defined _str for %%k in (!k!) do (%\n%
if "!_str:~-%%k!"=="!_spcs:~-%%k!" set "_str=!_str:~0,-%%k!"%\n%
set /a "k/=2"%\n%
)%\n%
)%\n%
if defined _str (%\n%
if not defined _notDelayed (%\n%
set "_str=!_str:^=^^!"%\n%
set "_str=!_str:"=""Q!^"%\n%
call set "_str=%%^_str:^!=""E^!%%" ! %\n%
set "_str=!_str:""E=^!"%\n%
set "_str=!_str:""Q="!^"%\n%
)%\n%
for /f ^^^"eol^^=^^^%LF%%LF%^%LF%%LF%^^ delims^^=^^^" %%k in ("!_str!") do endlocal^&endlocal^&set "%%J=%%k"!%\n%
) else endlocal^&endlocal^&set "%%J="%\n%
)%\n%
) else setlocal^&set "_notDelayed=!"^&setlocal EnableDelayedExpansion^&set _arg=
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Test
set "LN=;"%%* ^^^^ ^^! !) "
setlocal DisableDelayedExpansion
echo Disabled Delayed Expansion
echo BEFORE "%LN%"
%$RTrim% LN
echo AFTER "%LN%"
endlocal
echo(
setlocal EnableDelayedExpansion
echo Enabled Delayed Expansion
echo BEFORE "!LN!"
%$RTrim% LN
echo AFTER "!LN!"
endlocal
pause
Regards
aGerman
Re: Macro RTrim (trim trailing spaces)
Posted: 27 Dec 2011 14:51
by dbenham
Looks great
I thought it might be useful to have a version that allows you to specify the character that should be trimmed. So I modified your $RTRIM macro to accept an optional 2nd argument. The 2nd argument should specify the name of a variable that contains the character to trim in the 1st position. If the 2nd argument is missing then it defaults to trimming spaces as before.
Then I thought it would be useful to have an $LTRIM (and $TRIM) as well. The
FOR /f "EOL=%char% DELIMS=%char%" solution works really well in an LTRIM function. But in a macro we can't use immediate expansion and you can't specify the DELIMS and EOL options using delayed expansion. So I can't use the FOR solution if I want to support a user defined trim character.
So here are versions of $RTRIM, $LTRIM and $TRIM that support a user defined trim character, all derived from your $RTRIM.
Code: Select all
::%$RTRIM% strVar [charVar] -- macro for right-trimming
set $RTrim=for /l %%I in (1 1 2) do if %%I==2 (%\n%
set "_spcs= "%\n%
for /f "tokens=1,2" %%J in ("!_arg!") do (%\n%
set "_str=!%%J!"%\n%
if "%%~K" neq "" if defined %%~K set "_spcs=!%%K:~0,1!"%\n%
for /l %%i in (1 1 12) do set "_spcs=!_spcs!!_spcs!"%\n%
set /a "k=4096"%\n%
for /l %%j in (1 1 13) do (%\n%
if defined _str for %%k in (!k!) do (%\n%
if "!_str:~-%%k!"=="!_spcs:~-%%k!" set "_str=!_str:~0,-%%k!"%\n%
set /a "k/=2"%\n%
)%\n%
)%\n%
if defined _str (%\n%
if not defined _notDelayed (%\n%
set "_str=!_str:^=^^!"%\n%
set "_str=!_str:"=""Q!^"%\n%
call set "_str=%%^_str:^!=""E^!%%" ! %\n%
set "_str=!_str:""E=^!"%\n%
set "_str=!_str:""Q="!^"%\n%
)%\n%
for /f ^^^"eol^^=^^^%LF%%LF%^%LF%%LF%^^ delims^^=^^^" %%k in ("!_str!") do endlocal^&endlocal^&set "%%J=%%k"!%\n%
) else endlocal^&endlocal^&set "%%J="%\n%
)%\n%
) else setlocal^&set "_notDelayed=!"^&setlocal EnableDelayedExpansion^&set _arg=
::%$LTRIM% strVar [charVar] -- macro for left-trimming
set $LTrim=for /l %%I in (1 1 2) do if %%I==2 (%\n%
set "_spcs= "%\n%
for /f "tokens=1,2" %%J in ("!_arg!") do (%\n%
set "_str=!%%J!"%\n%
if "%%~K" neq "" if defined %%~K set "_spcs=!%%K:~0,1!"%\n%
for /l %%i in (1 1 12) do set "_spcs=!_spcs!!_spcs!"%\n%
set /a "k=4096"%\n%
for /l %%j in (1 1 13) do (%\n%
if defined _str for %%k in (!k!) do (%\n%
if "!_str:~0,%%k!"=="!_spcs:~-%%k!" set "_str=!_str:~%%k!"%\n%
set /a "k/=2"%\n%
)%\n%
)%\n%
if defined _str (%\n%
if not defined _notDelayed (%\n%
set "_str=!_str:^=^^!"%\n%
set "_str=!_str:"=""Q!^"%\n%
call set "_str=%%^_str:^!=""E^!%%" ! %\n%
set "_str=!_str:""E=^!"%\n%
set "_str=!_str:""Q="!^"%\n%
)%\n%
for /f ^^^"eol^^=^^^%LF%%LF%^%LF%%LF%^^ delims^^=^^^" %%k in ("!_str!") do endlocal^&endlocal^&set "%%J=%%k"!%\n%
) else endlocal^&endlocal^&set "%%J="%\n%
)%\n%
) else setlocal^&set "_notDelayed=!"^&setlocal EnableDelayedExpansion^&set _arg=
::%$TRIM% strVar [charVar] -- macro for trimming both left and right
set $Trim=for /l %%I in (1 1 2) do if %%I==2 (%\n%
set "_spcs= "%\n%
for /f "tokens=1,2" %%J in ("!_arg!") do (%\n%
set "_str=!%%J!"%\n%
if "%%~K" neq "" if defined %%~K set "_spcs=!%%K:~0,1!"%\n%
for /l %%i in (1 1 12) do set "_spcs=!_spcs!!_spcs!"%\n%
set /a "k=4096"%\n%
for /l %%j in (1 1 13) do (%\n%
if defined _str for %%k in (!k!) do (%\n%
if "!_str:~-%%k!"=="!_spcs:~-%%k!" set "_str=!_str:~0,-%%k!"%\n%
if "!_str:~0,%%k!"=="!_spcs:~-%%k!" set "_str=!_str:~%%k!"%\n%
set /a "k/=2"%\n%
)%\n%
)%\n%
if defined _str (%\n%
if not defined _notDelayed (%\n%
set "_str=!_str:^=^^!"%\n%
set "_str=!_str:"=""Q!^"%\n%
call set "_str=%%^_str:^!=""E^!%%" ! %\n%
set "_str=!_str:""E=^!"%\n%
set "_str=!_str:""Q="!^"%\n%
)%\n%
for /f ^^^"eol^^=^^^%LF%%LF%^%LF%%LF%^^ delims^^=^^^" %%k in ("!_str!") do endlocal^&endlocal^&set "%%J=%%k"!%\n%
) else endlocal^&endlocal^&set "%%J="%\n%
)%\n%
) else setlocal^&set "_notDelayed=!"^&setlocal EnableDelayedExpansion^&set _arg=
Dave Benham
Re: Macro RTrim (trim trailing spaces)
Posted: 27 Dec 2011 16:18
by aGerman
Great idea, Dave
What do you think which character was my first attempt?
Regards
aGerman
Re: Macro $RTrim (trim trailing spaces) / also $LTrim and $T
Posted: 27 Jan 2012 03:06
by Ed Dyreen
'
The algorithm is incredibly clever
Code: Select all
if "!_str:~-%%k!"=="!_spcs:~-%%k!" set "_str=!_str:~0,-%%k!"
a++German
@ben, very nice, only thing I change is jeb's faster/smaller suggestion.
Code: Select all
set $Trim=for %%I in (1,2) do if %%I==2 (%\n%
...
) else ...
Re: Macro $RTrim (trim trailing spaces) / also $LTrim and $T
Posted: 27 Jan 2012 04:46
by Ed Dyreen
'
I require a little different functionality most of the time, the one that
eats until Left/Right/Outer.
Excuse my foolishness, didn't realize you guys were after something else
Left
Code: Select all
%= =%set $=^&set "$=^!%%~a^!"^&^&if 1%%~b neq +1%%~b (set "$=^!$:*%%~b=^!") else set "$=^!$:~0,-%%~b^!"%$n1c%
%= =%set "%%~a=^!$^!"%$n1c%
Right
Code: Select all
%= =%if "%%~b"=="" (set "$f= ") else set "$f=%%~b"%$n1c%
%= =%set $$=^&set "$$=^!%%~a^!"^&^&!forQ_! ("^!$f^!") do if 1%%~? neq +1%%~? (%$n1c%
%= =%set "?=^!$$:%%~?=" "^!"^&set ?="^!?^!"%$n1c%
%= =%!forQ_! (^^^!?^^^!) do if "%%~?" neq "" set "$=%%~?"%$n1c%
%= =%!forQ_! (^^^!$^^^!) do set "?=^!$$:*%%~?=^!###"%$n1c%
%= =%if "^!?^!"=="###" set "?=%%~?^!$^!###"%$n1c%
%= =%set "$=^!$$^!###"^&!forQ_! ("^!?^!") do set "$$=^!$:%%~?=^!"%$n1c%
%= =%) else set "$$=^!$$:~0,-%%~?^!"%$n1c%
%= =%set "%%~a=^!$$^!"%$n1c%
Re: Macro $RTrim (trim trailing spaces) / also $LTrim and $T
Posted: 01 Apr 2012 19:19
by Aacini
Ed Dyreen wrote:'
@ben, very nice, only thing I change is jeb's faster/smaller suggestion.
Code: Select all
set $Trim=for %%I in (1,2) do if %%I==2 (%\n%
...
) else ...
Excuse me Ed, where did you see that
jeb's suggestion?
I thought that this suggestion was mine!
(near the end of
this post):
Aacini wrote:Note: for %%n in (1 2) execute faster than for /L %%n in (1,1,2).