Another take on safe string replace

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: Another take on safe string replace

#16 Post by Liviu » 24 Nov 2012 01:01

carlos wrote:Liviu. I would like understand the method. Please you can explain me? Maybe It helpme for maybe improve the codification.
I improve the codification of nice strlen function.

That's not _my_ function, as clearly shown by the attributions in the quoted code. The :strlen in question is essentially a binary search cleverly coded in (and around limitations of) batch language. I believe it traces back to this thread http://www.dostips.com/forum/viewtopic.php?p=5537#p5537.

Liviu

carlos
Expert
Posts: 503
Joined: 20 Aug 2010 13:57
Location: Chile
Contact:

Re: Another take on safe string replace

#17 Post by carlos » 24 Nov 2012 11:04

Ok. Thanks. This is a more shorter and faster codification:

Code: Select all

:strlen
(SETLOCAL ENABLEDELAYEDEXPANSION &SET L=0 &SET "S=A!%~1!" &FOR %%A in (
4096 2048 1024 512 256 128 64 32 16 8 4 2 1) DO (SET /A "L|=%%A"
FOR %%B IN (!L!) DO IF "!S:~%%B,1!"=="" SET /A "L&=~%%A"))
(ENDLOCAL &SET "%~2=%l%" 2>NUL &EXIT /B %L%)


Anyways, I ask for the method of safe string replace.

Sponge Belly
Posts: 234
Joined: 01 Oct 2012 13:32
Location: Ireland
Contact:

Re: Another take on safe string replace

#18 Post by Sponge Belly » 24 Nov 2012 15:54

Hi Carlos!

I have learnt the hard way not to bother Liviu until he's had his morning coffee ;-)

See Jeb's answer in this SO thread for an example of safe string return.

And once you've absorbed that, you can try bending your mind around Dave Benham's Macros with Arguments.

Dave says you can wrap any function in these macros and it will return a string uncorrupted, regardless of expansion. Haven't tried it myself yet. Still gazing at the code in bewilderment.

Btw, Jeb wrote the StrLen function that you based your code on. If your version really is an improvement over his gold standard, then it's a Big Deal and deserves a thread of its own. Or at least contact Jeb by Private Message (PM).

Good luck! :-)

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: Another take on safe string replace

#19 Post by Liviu » 24 Nov 2012 16:16

carlos wrote:This is a more shorter and faster codification:

Reformatting and using shorter variable names is generally not considered fair game to "shorten" the code ;-) As for faster, yours probably is so since it pre-computes the powers of 2 (instead of 1<<%%A). However, I'd be surprised if this made much of a measurable difference in practice.

If you browse the old thread linked in my previous post, there are some variations on the same theme. In particular, have a look at jeb's http://www.dostips.com/forum/viewtopic.php?p=6267#p6267.

carlos wrote:Anyways, I ask for the method of safe string replace.

I tried to explain my method the best I could in the first post. Beyond that, I can only suggest you insert some echo's in the code to see what's going on step by step.

That said, unless you do it for pure fun, I don't recommend my code for actual work. The brute force method, for example Dave's code, is easier to follow and also provably faster, as noted in the subsequent posts.

Liviu

P.S.
Sponge Belly wrote:I have learnt the hard way not to bother Liviu until he's had his morning coffee ;-)
Hard? That was hardly hard, my friend ;-)

carlos
Expert
Posts: 503
Joined: 20 Aug 2010 13:57
Location: Chile
Contact:

Re: Another take on safe string replace

#20 Post by carlos » 25 Nov 2012 23:14

Hello. Finally I undestand the method and the code. Is very nice.
I do some edits to the first code, removing some unnecessary setlocal, a label, a call, redundant lines.
Coming soon I will mix these edited code with the second code of Liviu.

Code: Select all

@echo off & setlocal disableDelayedExpansion
call :repStr "%~1" "%~2" "%~3" outStr
( endlocal
  if not "%~4"=="" (set "%~4=%outStr%") else if not "%~1"=="" (set "%~1=%outStr%")
) & goto :eof

:: replaces a substring with a (possibly empty) substitute string
:: to be called from 'setlocal disableDelayedExpansion' environment
::
:: %1 [in,  required] - name of variable containing the input string
:: %2 [in,  required] - name of variable containing the string being replaced
:: %3 [in,  optional] - name of variable containing the replacement string
::                      if missing or empty, occurences of string %2 are removed
:: %4 [out, optional] - name of variable to receive the output string
::                      if missing or empty, the %1 value is updated in place
::
:: errorlevel  0  ok
:: errors      1  missing %1 input string variable, error
::             2  missing %2 string to replace variable, error
:: warnings   -1  empty %1 input string variable, output string set to empty
::            -2  empty %2 string to replace variable, input copied to output
::           -10  called from enableDelayedExpansion, returned output may be wrong

:repStr
if "%~1"=="" (exit /b 1) else if "%~2"=="" exit /b 2

setlocal disableDelayedExpansion
if not "%~4"=="" (set "outStrRef=%~4") else (set "outStrRef=%~1")

setlocal enableDelayedExpansion
set "inStr=!%~1!" & if not defined inStr endlocal & endlocal & set "%outStrRef%=" & exit /b -1
set "oldStr=!%~2!" & if not defined oldStr endlocal & endlocal & set "%outStrRef%=%inStr%" & exit /b -2
if not "%~3"=="" (set "newStr=!%~3!") else set "newStr="
endlocal & set "inStr=%inStr%" & set "oldStr=%oldStr%" & set "newStr=%newStr%"

call :rigStr inStr
call :rigStr oldStr
call :rigStr newStr

setlocal enableDelayedExpansion
set "outStr=!inStr:%oldStr%=%newStr%!"
endlocal & set "outStr=%outStr%"

for /f "tokens=1-5" %%1 in ("%% ~ * = !") do endlocal & set "%outStrRef%=%outStr%"
if not "!"=="" (exit /b 0) else (exit /b -10)

:: replaces '%' = '%~1', '~' = '%~2', '*' = '%~3', '=' = '%~4', '!' = '%~5'
:rigStr %~*=!
setlocal enableDelayedExpansion
set "tail=#!%~1!"
endlocal & set "tail=%tail%"
set "outStr="
:: used by rigStr - replaces one delim, updates 'outStr' and 'tail'
:loop
for /F "delims=%%~*=!" %%A in ("%tail%") do set "next=%%A"
setlocal enableDelayedExpansion
call :strlen next offset
if %offset% leq 1 (
 if "!tail:~1,1!"=="%%" (set "outStr=!outStr!%%~1"
  ) else if "!tail:~1,1!"=="~" (set "outStr=!outStr!%%~2"
  ) else if "!tail:~1,1!"=="*" (set "outStr=!outStr!%%~3"
  ) else if "!tail:~1,1!"=="=" (set "outStr=!outStr!%%~4"
  ) else if "!tail:~1,1!"=="^!" (set "outStr=!outStr!%%~5"
  )
 set "tail=!tail:~2!"
) else (
  set "outStr=!outStr!!next:~1!"
  set "tail=!tail:~%offset%!"
)
endlocal & set "outStr=%outStr%" & set "tail=%tail%"
if defined tail (
set "tail=#%tail%"
goto :loop
)
set "%~1=%outStr%"
goto :eof

:strlen
(SETLOCAL ENABLEDELAYEDEXPANSION &SET L=0 &SET "S=A!%~1!" &FOR %%A in (
4096 2048 1024 512 256 128 64 32 16 8 4 2 1) DO (SET /A "L|=%%A"
FOR %%B IN (!L!) DO IF "!S:~%%B,1!"=="" SET /A "L&=~%%A"))
(ENDLOCAL &SET "%~2=%l%" 2>NUL &EXIT /B %L%)


Post Reply