I have a problem with reversing string list in a batch script. Let say I have a list L=string1,string2,string3 I would like to obtain reversed list L=string3,string2,string1. Any ideas??
You may also use this shorter/simpler approach:
#echo off
setlocal EnableDelayedExpansion
set "L=string1,string2,string3"
echo Input =%L%
set "revL="
set "str=%L:,=" & set "revL=,!str!!revL!" & set "str=%"
set "revL=%str%%revL%"
echo Output=%revL%
This method use the same procedure of the other answers, but in less lines. If you want to know what happens here, remove the #echo off line and run it. ;)
Without knowing what your input looks like, this might be a first attempt:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET var=abc,def,ghi
SET rev=
:LOOP
IF NOT "!var!"=="" (
FOR /F "delims=, tokens=1,*" %%F IN ("!var!") DO (
SET rev=%%F,!rev!
SET var=%%G
)
) ELSE (
SET rev=!rev:~0,-1!
GOTO ENDLOOP
)
GOTO LOOP
:ENDLOOP
ECHO reversed list is: !rev!
EDIT: As requested, here is an explanation how it works:
var is your starting list of strings separated by commas.
rev will be the reversed string. At the beginning this string is empty.
Now let's take a look at the loop:
In each iteration, we are separating our string into two parts: %%F and %%G. %%F will be everything before the first comma and %%G will be the rest of the string: FOR /F "delims=, tokens=1,*" %%F IN ("!var!"). delims=, means that we are using comma as delimiter. tokens=1,* means that the first found substring will be stored in %%F while the rest will be stored in %%G (%%F is defined for the first token so Windows command interpreter will put every token afterwards in G, H, I, and so on - as we are using *, everything will land in %%G). Finally, we take the first token of our string (%%F) and append ,!rev! to it. Then we set the remaining string list to everything behind the first comma (%%G).
In the first iteration, this loop does the following (pseudo code):
var=abc,def,ghi
rev=
split the string into %%F=abc and %%G=def,ghi
set rev to %%F,rev //means abc,
set var to var but without the first token //means def,ghi
In the second iteration:
var=def,ghi
rev=abc,
split the string into %%F=def and %%G=ghi
set rev to %%F,rev //means def,abc
set var to var but without the first token //means ghi
In the third iteration:
var=ghi
rev=def,abc
split the string into %%F=ghi %%G=
set rev to %%F,rev //means ghi,def,abc,
set var to var but without the first token //means empty string
Now, after jumping back to :LOOP, the if condition is no longer fulfilled as !var! has shrunk from formerly abc,def,ghi to now an empty string. So IF NOT !var!=="" becomes false and we are jumping to the ELSE clause.
There is one problem left: as we are constructing our reversed string by pre-appending the first token from the original list AND a comma, we will end up with a comma at the end of the reversed string list: ghi,def,abc,
SET rev=!rev:~0,-1! fixes this. It takes a "substring" from our string, starting at index 0 and finishing at "end-1". So this line simply removes the last , at the end of our string. Then we are jumping to :ENDLOOP.
Here is a batch file code assuming L=string1,string2,string3 is assigned to an environment variable:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ListLine=L=string1,string2,string3"
for /F "tokens=1* delims==" %%I in ("%ListLine%") do (
set "LineBegin=%%I"
set "ListItems=%%J"
)
set "ReversedItems="
for %%I in (%ListItems%) do call set "ReversedItems=%%I,%%ReversedItems%%"
set "ListLine=%LineBegin%=%ReversedItems:~0,-1%"
echo %ListLine%
endlocal
Windows command interpreter interprets a comma in list of strings in a simple FOR loop like a space character as it can be seen on running this batch file without #echo off from within a command prompt window. Therefore the second FOR loop runs first with string1 assigned to loop variable I, second with string2 and third with string3.
Command CALL is used to do a double processing of the command SET to avoid the requirement to use delayed environment variable expansion as explained by help for command SET output on running set /? in a command prompt window.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
echo /?
endlocal /?
for /?
set /?
setlocal /?
Aacini, definitely has the fastest code out of all of the answers. This is some longer code that uses a similar SET trick.
#echo off
setlocal EnableDelayedExpansion
set i=1
set "x=abc,def,ghi"
set "x!i!=%x:,=" & set /A i+=1 & set "x!i!=%"
FOR /L %%G IN (%i%,-1,1) DO (
IF "%i%"=="%%G" (
set "reverse=!x%%G!"
) ELSE (
set "reverse=!reverse!,!x%%G!"
)
)
echo %reverse%
pause
Just some quick timed testing of all 4 answers. First one uses the original string 3 characters in each of the 3 comma separated fields. The second one uses 3 characters in 9 comma separated fields. Each time I tested running each one 100 times and calculated the average. The differences are negligible.
Average of 100 tries using 3x3
Aacini 0.39254
Squashman 0.39851
Michael 0.3999
Mofi 0.40434
Average 100 tries using 3x9
Aacini 0.39925
Squashman 0.40278
Michael 0.41457
Mofi 0.43397
Related
I have a need for a Windows NT command script to do the following:
- Read variable string from command line
- Take that variable and create new string based off 1st byte of each word
The incoming string in the Windows NT command script is %1.
The string may vary in length and number of words, but always ends with (something here) [something there].
Example - Incoming value from command line including quotes:
"This is my string for a new name (comment) [comment 2]"
The string I need to create is:
Timsfann
These characters are from the 1st letter in each of the words in the above string and stopping when it gets to the parenthesis in (comment).
The above string will be concatenated with a date/time string to create a unique filename. The finished string should be set to a system variable so that programs can find the finished value.
Any help would be appreciated.
I've been attempting using a FOR loop, but can't seem to get it to work as desired.
#Crimsyn, if it's easier for you to understand, here's another one of the few ways, which doesn't use delayed expansion or the FOR statement:
#Echo Off
Call :Loop %~1
Echo(%oStr%&Pause
Exit/B
:Loop
If "%~1"=="" GoTo :EOF
Set "iStr=%~1"
If "%iStr:~,1%"=="(" GoTo :EOF
Set "oStr=%oStr%%iStr:~,1%"
Shift&GoTo Loop
Another one! Although simpler and shorter, this method could be harder to understand... ;)
#echo off
setlocal EnableDelayedExpansion
set "str=%~1 "
set "str=%str:(= " & rem %
set "result="
set "word=%str: =" & set "result=!result!!word:~0,1!" & set "word=%"
echo %result%
Hint: execute it with echo on
Just one of a few ways you could do this.
#echo off
setlocal enabledelayedexpansion
for /f "tokens=1 delims=(" %%G IN ("%~1") do set "line=%%G"
FOR %%G IN (%line%) DO (
set tline=%%G
set code=!code!!tline:~0,1!
)
echo %code%
pause
In a batch file I have a variable containing an IP. Just for example:
SET ip = 170.150.120.10
I would like to define another variable, replacing the last octet with 1, which in the example would be 170.150.120.1.
The IP may change (it is defined dynamically), varying in its length, so capturing a fixed length substring and concatenating it would not work anytime (in the example it would be SET baseIP = %ip:~0,9%.1).
How can this task be solved?
Is there some RegEx support in Windows command line?
maytham-ɯɐɥıλɐɯ has the key component of a simple solution - FOR /F. But that solution has a lot of complication that seems unrelated to the question.
The answer can be as simple as:
#echo off
set "ip=170.150.120.10"
for /f "tokens=1-3 delims=." %%A in ("%ip%") do set "new_ip=%%A.%%B.%%C.1"
echo new ip=%new_ip%
Note - You included spaces before and after the = in the SET statement in your question. That is a bad idea, as all of the spaces are significant. You have a variable name that ends with a space, and a value that begins with a space. I removed the unwanted spaces from the answer
Also, I enclosed the assignment within quotes. All characters after the last quote are ignored as long as the first quote is before the variable name. This protects against inadvertent trailing spaces in your value.
EDIT 2017-09-04
Even simpler method - treat the address as a filename, so the last node becomes the extension. Use a simple FOR and the ~n modifier to get the base name (1st 3 nodes), and then add your own extension (last node).
for %%A in (%ip%) do set "new_ip=%%~nA.1"
(Edit: added missing jump)
Here's my take. Iterates over the last four characters, looks if it is a dot, and appends the desired octet to the corresponding prefix part of the given IP. This works with any size (length) of last octet, e.g. 1.1.1.5 and 10.0.0.155
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET ip=170.150.120.10
SET new_ip_last_octet=1
ECHO Input was %ip%
FOR /L %%G IN (0,-1,-4) DO (
SET tmp=!ip:~%%G!
IF "!tmp:~0,1!" == "." (
SET new_ip=!ip:~0,%%G!.!new_ip_last_octet!
GOTO done
)
)
:done
ECHO New IP is %new_ip%
Output:
Input was 170.150.120.10
New IP is 170.150.120.1
Try this
#echo off
set ipCurrent=170.150.120.100
set ipOffsets=0.100.0.-24
#echo off
for /f "tokens=1-3 delims=. " %%a in ("%ipCurrent%") do (
set part1=%%a
set part2=%%b
set part3=%%c
)
for /f "tokens=1-3 delims=." %%a in ("%ipOffsets%") do (
set /a part1+=%%a
set /a part2+=%%b
set /a part3+=%%c
)
set ipBase= %part1%.%part2%.%part3%.1
#echo %ipCurrent% is changed to%ipBase%
EDIT
Thanks to #dbenham for input, the code above can be reduced to:
#echo off
set "ipCurrent=170.150.120.100"
#echo off
for /f "tokens=1-3 delims=. " %%a in ("%ipCurrent%") do set "ipBase=%%a.%%b.%%c.1"
#echo %ipCurrent% is changed to %ipBase%
Input any ip address range
input 170.150.120.10
or 170.150.120.110
Output 170.150.120.1
Batch resources: Link
If I get my parameter with %1 and it is "Server" how can I add a + sign after every letter?
So my result would be "S+e+r+v+e+r"?
I think Batch file to add characters to beginning and end of each line in txt file this is a similar question but I don't know how to change the code for this purpose.
Any help would be great!
I'm pretty sure this has been asked and answered before, but I couldn't find it.
There is a really cool (and fast) solution that I saw posted somewhere. It uses a new cmd.exe process with the /U option so output is in unicode. The interesting thing about the unicode is that each ASCII character is represented as itself followed by a nul byte (0x00). When this is piped to MORE, it converts the nul bytes into newlines!. Then a FOR /F is used to iterate each of the characters and build the desired string. A final substring operation is used to remove the extra + from the front.
I tweaked my memory of the code a bit, playing games with escape sequences in order to get the delayed expansion to occur at the correct time, and to protect the character when it is appended - all to get the technique to preserve ^ and ! characters. This may be a new twist to existing posted codes using this general technique.
#echo off
setlocal enableDelayedExpansion
set "str=Server bang^! caret^^"
set "out="
for /f delims^=^ eol^= %%A in ('cmd /u /v:on /c echo(^^!str^^!^|more') do set "out=!out!+^%%A"
set "out=!out:~1!"
echo Before: !str!
echo After: !out!
--OUTPUT---
Before: Server bang! caret^
After: S+e+r+v+e+r+ +b+a+n+g+!+ +c+a+r+e+t+^
This batch file should do it:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET Text=%~1
SET Return=
REM Batch files don't have a LEN function.
REM So this loop will process up to 100 chars by doing a substring on each.
FOR /L %%I IN (0,1,100) DO (
CALL SET Letter=!Text:~%%I,1!
REM Only process when a letter is returned.
IF NOT "!Letter!" == "" (
SET Return=!Return!+!Letter!
) ELSE (
REM Otherwise, we have reached the end.
GOTO DoneProcessing
)
)
:DoneProcessing
REM Remove leading char.
SET Return=%Return:~1,999%
ECHO %Return%
ENDLOCAL
Calling with Test.bat Server prints S+e+r+v+e+r to the console.
I feel like I'm going around in circles with FOR loop options.
I'm trying to take a string (output of a command) and split it on commas, then use each value to SET, e.g.
String: USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
So I want to split on comma and then literally use that variable in SET. I don't know ahead of time how many many variables there will be.
I've tried things like:
FOR %%L IN (%MYSTRING%) DO ECHO %%L
but that splits on the equals sign too so I end up with
USER
Andy
IP
1.2.3.4
etc
I just want to be able to do the following so I can SET USER=Andy etc, something like:
FOR %%L IN (%MYSTRING%) DO SET %%L
What option or flags am I missing?
The default delimiters for elements in plain FOR command (no /F option) are spaces, tab, commas, semicolons and equal signs, and there is no way to modify that, so you may use FOR /F command to solve this problem this way:
#echo off
set MYSTRING=USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
:nextVar
for /F "tokens=1* delims=," %%a in ("%MYSTRING%") do (
set %%a
set MYSTRING=%%b
)
if defined MYSTRING goto nextVar
echo USER=%USER%, IP=%IP%, HOSTNAME=%HOSTNAME%, PORT=%PORT%
Another way to solve this problem is first taking the variable name and then executing the assignment for each pair of values in a regular FOR command:
setlocal EnableDelayedExpansion
set MYSTRING=USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
set varName=
for %%a in (%MYSTRING%) do (
if not defined varName (
set varName=%%a
) else (
set !varName!=%%a
set varName=
)
)
echo USER=%USER%, IP=%IP%, HOSTNAME=%HOSTNAME%, PORT=%PORT%
EDIT 2023/01/20: New method added
I know this is a very old question. However, I can't resist the temptation to post a new very interesting method to solve this old problem:
#echo off
set MYSTRING=USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
set "%MYSTRING:,=" & set "%"
echo USER=%USER%, IP=%IP%, HOSTNAME=%HOSTNAME%, PORT=%PORT%
If you want to know where the magic is, remove the #echo off line, execute the program and carefully review the screen...
In case your input is something like HOSTNAME:PORT and you need to split into separate variables then you can use this
#echo off
set SERVER_HOST_PORT=10.0.2.15:8080
set SERVER_HOST_PORT=%SERVER_HOST_PORT::=,%
for /F "tokens=1* delims=," %%a in ("%SERVER_HOST_PORT%") do (
set SERVER_HOST=%%a
set SERVER_PORT=%%b
)
echo SERVER_HOST=%SERVER_HOST%
echo SERVER_PORT=%SERVER_PORT%
I'm trying to remove an asterisk from an environmental variable string, but can't seem to do it.
I'm creating an m3u file based around search strings, so for instance I if I want to make an m3u file containing every song with the word love in it, I would enter:
m3u *Love*
And m3u.bat would create the file:
xLovex.m3u
But the regular method of replacing characters does not work with an asterisk. (Though I don't have that problem with the question mark.)
set nam=%nam:*=x%.m3u
Instead creates the filename
x.m3u
The easy answer is no.
The problem that you're encountering stems from the fact that the asterisk * is a special character when used with the SET search and replace method. It matches multiple characters in a limited, but still useful, way. You can learn about that here.
The hard answer is Yes!
I will provide you with two solutions. One an incomplete solution but elegent,
the other complete and inelegent.
Both methods will search for * and replace it with an x.
Both methods will both search and modify the following string:
*love*
The first method that comes to mind is using a 'FOR /L' statement, and requires that you know how many characters long the environmental variable is.
::Major Edit::
I thought I knew the various maximum size strings of environmental variables, but dbenham has taken me to school, shown me a kick-in-the-behind length function, and in the mean time completely reversed my opinions of the two solutions I'm presenting.
Other than for the Windows 95/98/ME limitation of a 256 Character maximum environmental variable size. It seems that all versions of Windows using CMD.EXE have a limitation of 8,192 characters, well below what the documentation suggests.
Both versions require delayed environmental variable expansion, but for two different reasons. One because I'm operating inside a FOR statement. The other because you cannot put a % pair inside another % pair because the command processor matches the second % that it encounters to the first one it encounters, but we need to use a variable inside another variable expression. (You'll see.)
This solution uses the strLen function (in line 3) from DosTips.com that can be found Here. Just slap it into a file called strLen.bat and be amazed at it's speed!
Solution 1: (FOR /L Solution) :: Preferred Solution ::
setlocal ENABLEDELAYEDEXPANSION
set nam=*love*
rem calling strLen
call :strLen nam len
for /l %%x in (0,1,%len%) do if not "!nam:~%%x,1!"=="" if "!nam:~%%x,1!"=="*" (
set /a plusone=%%x+1
for /l %%y in (!plusone!, 1, !plusone!) do (
set nam=!nam:~0,%%x!x!nam:~%%y!
)
)
echo %nam%
ENDLOCAL
I think this is a quick and elegant solution It could be sped up by adding the contents of strLen.bat to the routine, but I wanted no confusion as to the author.
If you, for some reason, do not wish to use strLen, then the next quickest method would probably use a GOTO loop.
Solution 2: (Goto Solution)
setlocal ENABLEDELAYEDEXPANSION
set nam=*love*
set num=0
:loop
set /a plusone=%num%+1
if "!nam:~%num%,1!"=="*" set nam=!nam:~0,%num%!x!nam:~%plusone%!
set /a num=%num%+1
if not "!nam:~%num%,1!"=="" goto :loop
echo %nam%
EndLocal
Special thanks to dbenham for pointing out the strLen function. It works faster than any batch based function has a right to!
Although there were already some very good and robust ways explained here, I'd still like to add another option for the sake of completion.
It's not as good as the other options but I personally use it in some cases where I'd like to keep the code clean and where I know that it will suffice:
The way it works is by using for /f's delims to cut the string into two parts, which are then put back together, getting rid of the * in the process:
for /f "tokens=1,* delims=*" %%a in ("a*b") do (set string=%%a%%b)
>>> string=ab
Obviously, the downside to this is that it can only be used to remove one *.
To remove more, we can either just use more tokens...
for /f "tokens=1-3,* delims=*" %%a in ("a*b*c*d") do (set string=%%a%%b%%c%%d)
>>> string=abcd
... or we can put the first line in a for /l-loop:
setlocal enableDelayedExpansion
set string=a*b*c*d
for /l %%a in (1, 1, 3) do (
for /f "tokens=1,* delims=*" %%b in ("!string!") do (set string=%%b%%c)
)
>>> string=abcd
Another thing to note is that you can define more than one character in delims, and they will all be removed at once:
for /f "tokens=1,* delims=+-*/" %%a in ("a*-/+b") do (set string=%%a%%b)
>>> string=ab
Another solution to the stated problem is to use a PowerShell replace command within your batch script.
set var=*Love*
echo %var%>var.txt | powershell -command "((get-content var.txt) -replace '[\x2A]','x') -replace '.{1}$' | set-content var.txt"
set /p var=<var.txt
set var=%var%.m3u
echo %var%
In the above code, the second line
writes your string into a text file
calls a PowerShell command to get the contents of that file
replaces the * character with null
overwrites the text file with the new value
Once that is done, you read the value back into your variable.
To further explain the replace command, the first single quotes is what you are searching for. We are using square brackets to identify the * character as a hex character (\x2A is the hex value for *). After the comma, the second set of single quotes contains no value so that the searched object is removed. To prevent a space between xLovex and the .m3u, we have to use -replace '.{1}$' before writing the result to the text file.
Once you are done with the text file, enter a line to delete it.
if exist var.txt del var.txt
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/!_
See this answer, and with set-ast.bat you'll want to put set-ast nam "x" in your file where needed.
set-ast takes the parameters <variable-to-modify> <string-to-replace-asterisks-with>