Here is an approach that does not walk through all characters of a string, but it uses a for /F loop to split the string at every occurrence of a (sequence of a) certain character. The actual functionality is packed into a sub-routine for easy reuse, so the main section of the following script just contains some code to test:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
::This is the main routine of the script holding code for test and demonstration:
rem // Definition of some sample text to test (note that `%%` becomes one literal `%`):
set "DATA=some text,"^&"&;0'@%%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_"
echo/
call :REPL_CHAR TEXT DATA "*" "?"
setlocal EnableDelayedExpansion
echo(In: !DATA!
echo(Out:!TEXT!
echo/
echo(In: !TEXT!
call :REPL_CHAR TEXT TEXT "=" "/"
echo(Out:!TEXT!
endlocal
endlocal
exit /B
:REPL_CHAR
::This function replaces in a string every occurrence of a sequence of a certain character
::by another character or a string. It even correctly handles the characters `*` and `=`.
:: USAGE:
:: call :REPL_CHAR ref_output_string ref_input_string val_search_char val_replace_char
:: PARAMETERS:
:: ref_output_string reference to (name of) variable to receive the resulting string;
:: ref_input_string reference to variable that holds the original string; if empty
:: (`""`), the variable referenced by `ref_output_string` is used;
:: val_search_char single character that is to be replaced;
:: val_replace_char character or string to replace every sequence of `val_search_char`
:: with; this may even be empty;
rem // Localise environment and detect whether delayed expansion is enabled (needed later):
setlocal & set "$NDX=!"
setlocal DisableDelayedExpansion
rem // Fetch arguments and verify them:
set "#RET=%~1" & if not defined #RET endlocal & endlocal & exit /B 2
set "#STR=%~2" & if not defined #STR set "#STR=%#RET%"
set "CHR=%~3"
if not defined CHR endlocal & endlocal & exit /B 1
set "RPL=%~4"
setlocal EnableDelayedExpansion
rem // Initialise several auxiliary variables:
set "TST=!%#STR%!" & set "CHR=!CHR:~,1!" & set "INS="
if "!CHR!"=="_" (set "BUF=#" & set "WRK=!TST!#") else (set "BUF=_" & set "WRK=!TST!_")
:REPL_CHAR_LOOP
rem // Check whether the end of the string has been reached:
if not defined TST set "BUF=!BUF:~1,-1!" & goto :REPL_CHAR_NEXT
rem // Split the string at the next sequence of search characters:
for /F tokens^=1*^ delims^=^%CHR%^ eol^=^%CHR% %%S in ("!BUF!!INS!!WRK!") do (
rem // Store the portions before and after the character sequence:
endlocal & set "BUF=%%S" & set "TST=%%T" & set "WRK=%%T" & setlocal EnableDelayedExpansion
)
rem // Loop back and find the next character sequence:
set "INS=!RPL!" & goto :REPL_CHAR_LOOP
:REPL_CHAR_NEXT
rem // Return the resulting string with all special characters properly handled:
if not defined $NDX if defined BUF set "BUF=!BUF:"=""!^"
if not defined $NDX if defined BUF set "BUF=!BUF:^=^^^^!"
if not defined $NDX if defined BUF set "BUF=%BUF:!=^^^!%" !
if not defined $NDX if defined BUF set "BUF=!BUF:""="!^"
for /F "delims=" %%S in (^""!BUF!"^") do endlocal & endlocal & endlocal & set "%#RET%=%%~S" !
exit /B
The input and output data of this script (let us call it repl_char_demo.bat) are:
>>> repl_char_demo.bat
In: some text,"&"&;0'@%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'@%~#`$:wild?card??.re<dir>=|+([{parens}])-^/equal==to=!_
In: some text,"&"&;0'@%~#`$:wild?card??.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'@%~#`$:wild?card??.re<dir>/|+([{parens}])-^/equal/to/!_
This is a script that uses for /L loops to walk through all characters of the string, to check each character against a predefined one and replaces it as specified. This method replaces every single matching character rather than sequences. Again the functionality is put into a sub-routine (the main section is dismissed this time):
:REPL_CHAR
::This function replaces in a string every occurrence of one certain character by another
::character or a string. It even correctly handles the characters `*` and `=`, as well as
::sequences of search characters so that every single one becomes replaced.
:: USAGE:
:: call :REPL_CHAR ref_output_string ref_input_string val_search_char val_replace_char
:: PARAMETERS:
:: ref_output_string reference to (name of) variable to receive the resulting string;
:: ref_input_string reference to variable that holds the original string; if empty
:: (`""`), the variable referenced by `ref_output_string` is used;
:: val_search_char single character that is to be replaced;
:: val_replace_char character or string to replace every single `val_search_char`
:: with; this may even be empty;
rem // Localise environment and detect whether delayed expansion is enabled (needed later):
setlocal & set "$NDX=!"
setlocal DisableDelayedExpansion
rem // Fetch arguments and verify them:
set "#RET=%~1" & if not defined #RET endlocal & endlocal & exit /B 2
set "#STR=%~2" & if not defined #STR set "#STR=%#RET%"
set "CHR=%~3"
if not defined CHR endlocal & endlocal & exit /B 1
set "RPL=%~4"
setlocal EnableDelayedExpansion
rem // Initialise several auxiliary variables:
set "WRK=!%#STR%!" & set "CHR=!CHR:~,1!" & set "BUF="
rem // Loop through all characters and check for match:
if defined WRK for /L %%J in (0,1,63) do for /L %%I in (0,1,127) do (
set /A "POS=%%J*64+%%I" & for %%P in (!POS!) do (
set "TST=!WRK:~%%P,1!" & if not defined TST goto :REPL_CHAR_QUIT
rem // Store character or replacement depending on whether there is a match:
if "!TST!"=="!CHR!" (set "BUF=!BUF!!RPL!") else (set "BUF=!BUF!!TST!")
)
)
:REPL_CHAR_QUIT
rem // Return the resulting string with all special characters properly handled:
if not defined $NDX if defined BUF set "BUF=!BUF:"=""!^"
if not defined $NDX if defined BUF set "BUF=!BUF:^=^^^^!"
if not defined $NDX if defined BUF set "BUF=%BUF:!=^^^!%" !
if not defined $NDX if defined BUF set "BUF=!BUF:""="!^"
for /F "delims=" %%S in (^""!BUF!"^") do endlocal & endlocal & endlocal & set "%#RET%=%%~S" !
exit /B
There are actually two nested for /L loops rather than a single one, both of which become broken as soon as the end of the string is reached, using the goto command. Breaking a for /L loop means that it completes iterating in the background although its body is no longer executed. Therefore, using a single loop takes much more time to finish after being broken rather than two nested ones.
The input and output data of this script (with the same main section as above) are:
>>> repl_char_demo.bat
In: some text,"&"&;0'@%~#`$:wild**card*?.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'@%~#`$:wild??card??.re<dir>=|+([{parens}])-^/equal==to=!_
In: some text,"&"&;0'@%~#`$:wild??card??.re<dir>=|+([{parens}])-^/equal==to=!_
Out:some text,"&"&;0'@%~#`$:wild??card??.re<dir>/|+([{parens}])-^/equal//to/!_