Replace %%20 in string using windows batch script - string

I have a use case where I want to replace %%20 which is part of a string for example: "Calculation%%20One". I want to replace this with "Calculation One".
This where I am stuck:
#echo off
setlocal enableextensions disabledelayedexpansion
>"temp" (
echo !Option1!
echo !Option2!
echo !Option3!
)
set "pattern=%%20"
for /f "usebackq delims=" %%a in ("temp") do (
echo data: %%a
set "line=%%a"
setlocal enabledelayedexpansion
if "!line:%pattern%=!"=="!line!" (
set string=!Option1!
set string2=%!string1!:%%20= %
) else (
set string2=%%a
)
endlocal
)
del /q tempFile
Can someone please help me with this? I have a program which is a combination of batch and python.
Thanks

It is unclear for me why the options are written into a temporary file for processing the values of the environment variables Option1, Option2 and Option3. And it would have been good to see the lines which define the environment variables Option1, Option2 and Option3 for the real values assigned to them.
So I suggest here a batch file which replaces %20 as found for example in HTML files or emails by a space character in all Option environment variables.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "Option1=Calculation%%20One"
set "Option2=Calculation %%%%20Two!"
set "Option3=Any other string"
cls
echo The options before processing:
echo/
set Option
for /F "delims==" %%I in ('set Option 2^>nul') do call :ProcessOption "%%I"
echo/
echo The options after processing:
echo/
set Option
echo/
endlocal
pause
goto :EOF
:ProcessOption
setlocal EnableDelayedExpansion
set "ModifiedOption=!%~1:%%20= !"
endlocal & set "%~1=%ModifiedOption%"
goto :EOF
It is necessary to use delayed expansion on replacing all occurrences of %20 by a space character because otherwise Windows command interpreter would not know where the environment variable reference with string substitution ends. Replacing a string with percent sign is not possible using immediate expansion for that reason.
The command set Option outputs alphabetically sorted all environment variables starting case-insensitive with the string Option in format variable=value as it can be seen twice on running this batch file.
FOR executes this command with using a separate command process started in background and captures all lines written by command SET to handle STDOUT.
The string 2^>nul is executed finally as 2>nul and redirects the error message output by command SET to handle STDERR to the device NUL to suppress it. SET would output an error message in case of no environment variable starting with Option is defined in current environment. This error condition does not occur with this batch file. Read the Microsoft article about Using Command Redirection Operators. The redirection operator > must be escaped here with ^ to be interpreted first as literal character when Windows command interpreter processes the entire FOR command line before executing the command FOR which executes cmd.exe /c set Option 2>nul in background.
FOR processes the captured output of set Option line by line and splits each line up into substrings (tokens) with using the equal sign as delimiter as specified with delims==. The string left to first equal sign on each line is assigned to loop variable I. This is the name of the environment variable, i.e. Option1, Option2 and Option3 for this batch file.
With each OptionX environment variable name the subroutine ProcessOption is called for replacing all %20 in its value by a space character.
Here is a solution for environment variables with one or more exclamation marks in variable name.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "!Option1!=Calculation%%%%20One"
set "!Option2!=City%%20Country"
set "!Option3!=State%%%%20Country"
set "!Option4!=Any other address/string"
cls
echo The options before processing:
echo/
set !Option
for /F "delims==" %%I in ('set !Option 2^>nul') do call :ProcessOption "%%I"
echo/
echo The options after processing:
echo/
set !Option
echo/
endlocal
pause
goto :EOF
:ProcessOption
call set "ModifiedOption=%%%~1%%"
setlocal EnableDelayedExpansion
set "ModifiedOption=!ModifiedOption:%%%%20= !"
set "ModifiedOption=!ModifiedOption:%%20= !"
endlocal & set "ModifiedOption=%ModifiedOption%"
set "%~1=%ModifiedOption%"
set "ModifiedOption="
goto :EOF
This batch code replaces %20 AND %%20 in all environment variables starting with !Option in name by a space character in comparison to above replacing just %20 by a space in environment variables beginning with Option in name.
It is of course a bad idea to use characters in variable names which have a special meaning for Windows command interpreter like the characters &()[]{}^=;!'+,`~|<>%. It is possible as demonstrated above, but it is definitely not a good idea.
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 /?
cls /?
echo /?
endlocal /?
for /?
goto /?
set /?
setlocal /?
See also answer on Single line with multiple commands using Windows batch file for an explanation of & operator.
And read DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ why it is better to use echo/ instead of echo. to output an empty line.

Related

Batch Manipulating String by Input Variable Using "=&rem."

Using Server 2016 here with simple batch-file programming (for now).
I have what I think is an easy problem, just that I am not seeing a workable solution in front of me right now. I'm going to limit my request right down to a testing scenario to keep it simple. What I am trying to do is trim a string from the right until it hits the current year in the format "_%year%-" where the year does come from a variable elsewhere, but is set static in my example. Where I am running into issues is referring to a variable from within the code I have.
This is a working example NOT using a variable on the rem line, and gives the desired output of "Machined_Cam-2286:"
#ECHO OFF
SETLOCAL DisableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set testString=%testString:_2017-=&rem.%
echo %testString%
pause
You can see that "_2017-" hard-coded in on the 10th line. What I am looking to do in a purely logical sense on the rem line specifically:
set testString=%testString:%yearModified%=&rem.%
Because of the way this command modifies testString in-line, it makes it difficult to inject a variable into it. I have tried a huge combination of escapes and expansion settings to get the variable to take with no success so far. I have also tried to "build" the command as a string and attempt to call it and pipe the output to a variable:
#ECHO OFF
SETLOCAL DisableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set "callCMD=%%testString:%yearModified%=^&rem.%%"
call %callCMD% > %testString%
echo %testString%
pause
This seems like such a simple issue, but my lack of understanding of string manipulation under DOS is apparent. As is stands, the rest of the script is running perfectly with the "_2017-" hard-coded. It would be nice to eliminite that bit of maintenance.
Any help or direction is appreciated.
Thank you.
May I show you another method of splitting. Replace your "Delimiterstring" with a proper Delimiter and use a for to split (for uses single-letter delimiters). Enabling delayed expansion helps, but is not neccessary. As you explicitely disabled it (you may have your reasons), I'll show you both:
#echo off
set "TestString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "CutHere=_2017"
setlocal enabledelayedexpansion
for /f "delims=§" %%a in ("!TestString:%CutHere%=§!") do set Result=%%~a
echo enabled: %Result%
endlocal
setlocal disabledelayedexpansion
for /f "delims=§" %%a in ('call echo "%%TestString:%CutHere%=§%%"') do set Result=%%~a
echo disabled: %Result%
endlocal
Just be sure, you use a delimiter (§ here) that surely won't be in your string(s).
you are trying to control what you call using the contents of a variable
try using a temp file instead -
echo %%testString:%yearModified%=^&rem.%%" > temp.bat
call temp.bat > %testString%
You NEED delayed expansion for this type of operation, so enabe it.
I suggest to first get the remainder of the string
and remove this from the original string.
#ECHO OFF
SETLOCAL EnableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set tempString=!testString:*%yearModified%=!
set testString=!testString:%yearModified%%tempstring%=!
echo %testString%
pause
Sample output:
Machined_Cam-2286
#echo off
setlocal EnableDelayedExpansion
set "testString=Machined_Cam-2286_2017-09-08.slddrw - SOLIDWORKS"
set "systemYear=2017"
set "yearModified=_%systemYear%-"
echo "%testString%" | find "%yearModified%" >NUL || goto :EOF
set testString=!testString:%yearModified%=^&rem.!
echo %testString%
pause
You shoud use a carrot(^) in Delayed Expansion mode.

Batch Move based on filename without Delimiters, String Only

Okay, Windows 7 Enterprise x64 here with a Windows batch file question. I am a somewhat basic user with little knowledge of batch file creation. I've modified existing scripts I've found online to my use, that is about it.
I have a piece of software that dumps it's output to a folder with file names concatenated from the project title, current system date, current system time, and project settings.
It will export two files in this format every time the project is saved:
PROJECTTITLE_2016_10_07__09_45_11__A_B_C.iges
PROJECTTITLE_2016_10_07__09_45_11__A_B_C.step
The A, B, and C representing switches used in the software that are specific to the project. These may exist or may not exist as in the file name can be _R_F or _R_F_Z etc.
"PROJECTTITLE" can literally be anything. This is where my problem arises using delimiters. You could potentially have file names like all of the following (using real examples from users):
11475shacklebody_2016_10_07__09_45_11__R_F.iges
11475shacklebody_2016_10_07__09_45_11__R_F.step
test_EFMflow_2016_10_07__09_45_11__R_Z.iges
test_EFMflow_2016_10_07__09_45_11__R_Z.step
untitled16_2016_10_07__09_45_11__R.iges
untitled16_2016_10_07__09_45_11__R.step
#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.iges
#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.step
prooftestwithoutupperlandsimproved-4_2016_10_07__09_45_11.iges
prooftestwithoutupperlandsimproved-4_2016_10_07__09_45_11.step
What I want to do is move both the IGES and STEP files from the output folder on the C drive to a new folder on a networked drive with name based on "PROJECTTITLE," creating that folder if needed. I have already task scheduled a -delim based batch to run every night, but found delimiters to not be sufficient for my use.
While it seems like an easy job to do with delimiters, looking for the first underscore, I have had projects with underscores in their names that screws up the folder naming. Since delimiters only work for individual characters, not strings, I have been looking for examples of batch files I can adapt to my use with no luck finding any similar to what I want to do that do not use delimiters.
It seems simple, search for string "_2016," in filename, and take all characters before "_2016" and create a new directory, placing files containing those characters in that folder. I am lost as to how to do this without delimiters though.
What I have working so far, using delimiters looking for underscores, creating folders and moving to those folders on a mapped drive:
for /f "delims=_" %%V in ('dir /b /a-d C:\Output\*_*.iges') do (
mkdir "I:\ENG\PARTS\%%V" 2>nul
move "C:\Output\%%V_*.iges" "I:\ENG\PARTS\%%V" >NUL 2>nul
move "C:\Output\%%V_*.step" "I:\ENG\PARTS\%%V" >NUL 2>nul
)
It is a very simple batch file for what it does, but falls flat on it's face with titles containing underscores.
A few things:
Searching for _2016 would mean the batch file would need yearly maintenance. Could we either search for system %YEAR% or somehow search for "_####_##_##_" with something like a regular expression to get away from searching for system date or a specific year?
EDIT:
While not ideal as the switches are useful, I found a way in the software to not export the project specific switches at the end of the file name. So now the export would be:
PROJECTTITLE_2016_10_07__09_45_11.iges
PROJECTTITLE_2016_10_07__09_45_11.step
Which I know I can trim from right, what, 21 characters, use that name to make a folder and search the directory for all files containing that trimmed string.
It still would be nice to figure out how to search for the specific string though with the switches intact. I am willing to learn, so throw all explanations of your code you can at me.
Double (Late) EDIT:
First, thank you all for answering this, I deeply appreciate your help.
Double underscores in the the filename are quite rare, but do happen, probably due to typos. It becomes more of an issue with the volume of files I'm planning to move with this. The software runs on maybe 100 user machines with 20 or so being heavy users, each saving up to maybe 1000 files a day. The software will automatically save in increments, as the user runs the simulation. I'm trying to give them a way to collaborate and view each other's simulation results over the network (that we actively encourage them to use) and it figures that the software is hard coded to output to the C drive (for "performance" as the vendor tells me). I'm looking into scheduling the files to move every 15 minutes on every PC in their OU instead of nightly to give a near-real-time view on what they are working on.
Another option I explored was symbolically linking the output folder on each machine to the software's folder network drive, but found that this really doesn't solve the user's "organization" issue, putting like project runs in the same folder. This also has the issue of not allowing them to save a project out of the office without VPN, which some users do do, then move their files over when they get back in the office.
Thank you for your assistance.
Next batch script should do the job even if a file name contains cmd poisonous characters like space or % percent sign etc.
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_fouts=C:\Output" your setting
set "_fouts=D:\test\39924063" my setting
for /F %%G in ('wmic OS get localdatetime ^|find "."') do set "_fyear=%%G"
pushd "%_fouts%"
set "_fyear=%_fyear:~0,4%"
call :doDir
set /A _fyear -= 1
call :doDir
popd
ENDLOCAL
goto :eof
:doDir
rem debugging output echo(%_fyear%
for /f "delims=" %%V in ('
dir /b /a-d "*_%_fyear%_*.iges" "*_%_fyear%_*.step" 2^>NUL') do (
set "_fname=%%~nV" filename without extesion
set "_fexte=%%~xV" extesion only
call :doAll
)
goto :eof
:doAll
call set "_ftail=%%_fname:*_%_fyear%_=%%"
call set "_fproj=%%_fname:_%_fyear%_%_ftail%=%%"
rem debugging output echo("%_fproj%" "%_fname%" "%_fexte%"
ECHO mkdir "I:\ENG\PARTS\%_fproj%" 2>nul
ECHO move "%_fname%%_fexte%" "I:\ENG\PARTS\%_fproj%\"
goto :eof
Output (note that operational mkdir and move commands are merely displayed for debugging purposes using ECHO mkdir and ECHO move, respectively):
==> dir /B /S "D:\test\39924063" /S
D:\test\39924063\#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.iges
D:\test\39924063\#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.step
D:\test\39924063\%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.iges
D:\test\39924063\%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.step
==> D:\bat\SO\39924063.bat
mkdir "I:\ENG\PARTS\#14drop_wire-edm"
move "#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.iges" "I:\ENG\PARTS\#14drop_wire-edm\"
mkdir "I:\ENG\PARTS\%PROJECT TITLE"
move "%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.iges" "I:\ENG\PARTS\%PROJECT TITLE\"
mkdir "I:\ENG\PARTS\#14drop_wire-edm"
move "#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.step" "I:\ENG\PARTS\#14drop_wire-edm\"
mkdir "I:\ENG\PARTS\%PROJECT TITLE"
move "%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.step" "I:\ENG\PARTS\%PROJECT TITLE\"
==>
Resources (required reading, incomplete):
(command reference) An A-Z Index of the Windows CMD command line
(additional particularities) Windows CMD Shell Command Line Syntax
(%~nV, %~xV etc. special page) Command Line arguments (Parameters)
(set "_fyear=%_fyear:~0,4%" etc.) Extract part of a variable (substring)
(%variable:StrToFind=NewStr% etc.) Variable Edit/Replace
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
REM (
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*__*" '
) DO (
IF /i "%%~xa"==".iges" CALL :process "%%a"
IF /i "%%~xa"==".step" CALL :process "%%a"
)
REM )>"%outfile%"
GOTO :EOF
:: Process filename "%1"
:process
SET "fullname=%~1"
SET "junk=%fullname:*__=%"
CALL SET "project=%%fullname:%junk%=%%"
SET "project=%project:~0,-13%
ECHO(MD "%destdir%\%project%"
ECHO(MOVE "%sourcedir%\%~1" "%destdir%\%project%\"
GOTO :eof
You would need to change the settings of sourcedir and destdir to suit your circumstances.
The required MD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MD to MD to actually create the directories. Append 2>nul to suppress error messages (eg. when the directory already exists)
The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)
This approach simply performs a directory list without directorynames (/a-d) of each file in the source directory that contains a double-underscore. Each filename matching is assigned to %%a and if the extension part (%%~xa) is one of the targets, then process the filename.
Processing consists of removing all of the characters before the double-underscore and then removing that junk part from the full name, giving project+date. Remove the last 13 characters and you have your project name.
Will have problems with any filename containing certain symbols like % or = but should be fine with underscores.
This version assumes there will not be an instance of _YYYY_ in the PROJECTTITLE.
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
(Set OutDir=I:\ENG\PARTS)
(Set SrcDir=C:\Output)
If Not Exist "%SrcDir%\" Exit/B 1
If Not Exist "%OutDir%\" Exit/B 1
For /F "EOL=Y" %%A In ('WMIC Path Win32_LocalTime Get Year') Do (
For /F "Delims=" %%B In ("%%A") Do Set "ThisYr=%%B")
For /F "Delims=" %%A In ('Where "%SrcDir%:*_%ThisYr%_*.iges"') Do (
If Exist "%%~dpnA.step" Call :Sub %%~nA)
Exit/B
:Sub
Set "BigTit=%~1"
For /F "Delims=" %%A In ('CMD /Q /C "Call Echo %%BigTit:_%ThisYr%_=&:%%"') Do (
If Not Exist "%OutDir%\%%A\" MD "%OutDir%\%%A"
Move "%SrcDir%\%~1*.*" "%OutDir%\%%A">Nul)
Edit, there is no need to export without the potentially useful project switches using the above code.
Supposing the PROJECTTITLE part of the file names does not contain two consecutive underscores, you could use the following script, which splits off the first occurrence of __ and everything after (so the time part and the optional switches are removed), using a standard for loop rather than for /F; then it splits off another 11 characters (hence the remaining date part), using sub-string expansion:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_SOURCE=."
set "_TARGET=."
for /F delims^=^ eol^= %%F in ('
pushd "%_SOURCE%" ^&^& ^(
dir /B /A:-D ^
"*_????_??_??__??_??_??*.iges" ^
"*_????_??_??__??_??_??*.step" ^
^& popd^)
') do (
call :PROCESS "%%F"
)
endlocal
exit /B
:PROCESS val_file
setlocal DisableDelayedExpansion
set "FILE=%~1"
setlocal EnableDelayedExpansion
for %%I in ("!FILE:__=";"!") do (
endlocal
set "ITEM=%%~I"
setlocal EnableDelayedExpansion
goto :NEXT
)
:NEXT
if defined ITEM (
set "ITEM=!ITEM:~,-11!"
md "%_TARGET%\!ITEM!" 2> nul
if not exist "%_TARGET%\!ITEM!\!FILE!" (
move /Y "%_SOURCE%\!FILE!" "%_TARGET%\!ITEM!\" > nul
)
)
endlocal
endlocal
exit /B
Finally I come up with a script that is able to handle even files whose PROJECTTITLE part contain __ on their own. It splits off the last occurrence of __ and everything after (so the optional switches are removed, if any, or the time part is removed otherwise), using a standard for loop rather than for /F; then it splits off another 21 characters (hence the remaining date and time parts), or, if no optional switches were present, just another 11 characters (hence the remaining date part), using sub-string expansion:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_SOURCE=."
set "_TARGET=."
for /F delims^=^ eol^= %%F in ('
pushd "%_SOURCE%" ^&^& ^(
dir /B /A:-D ^
"*_????_??_??__??_??_??*.iges" ^
"*_????_??_??__??_??_??*.step" ^
^& popd^)
') do (
call :PROCESS "%%F"
)
endlocal
exit /B
:PROCESS val_file
setlocal DisableDelayedExpansion
set "FILE=%~1"
set "BASE=%~n1"
set "NAME=" & set "ITEM="
setlocal EnableDelayedExpansion
for %%I in ("!BASE:__=";"!") do (
for /F "delims=" %%E in ("!NAME!!ITEM!__") do (
endlocal
set "NAME=%%E"
set "ITEM=%%~I"
setlocal EnableDelayedExpansion
)
)
if defined NAME (
set "NAME=!NAME:~2,-2!"
echo("!ITEM!"| > nul findstr /R "^\"[0-9][0-9]_[0-9][0-9]_[0-9][0-9]\"$" || (
if defined NAME set "NAME=!NAME:~,-10!"
)
if defined NAME set "NAME=!NAME:~,-11!"
if defined NAME (
md "%_TARGET%\!NAME!" 2> nul
if not exist "%_TARGET%\!NAME!\!FILE!" (
move /Y "%_SOURCE%\!FILE!" "%_TARGET%\!NAME!\" > nul
)
)
)
endlocal
endlocal
exit /B

Get base name of file without file extension

Let's say I'd have a file named "testfile.txt" set on a variable:%File% and I'd like to remove the extension when echoing it . Echo %File:~0,8% would come out as "testfile" but what I want to do is to have it display anything and everything to the left of the ".txt" because I won't always make files which have 8 characters in their name.
Is there a simple solution to this ?
Yep.
for %%I in ("testfile.txt") do echo %%~nI
or
for %%I in ("%file%") do echo %%~nI
Do help for in a cmd console window and see the last two pages for more information on tilde operations.
There is another way to accomplish what you want, using substring substitution similar to your attempts illustrated in your question.
set "file=testfile.txt"
echo %file:.=&rem;%
That substitutes the dot with &rem;. When the variable is evaluated, the batch interpreter treats the newly substituted data as a compound command. And since everything following rem is treated as a remark to be ignored, you're left with only testfile as the output. This will only work if:
you don't include quotation marks in your variable value
your filename only includes the one dot
you don't do it within a parenthetical code block (if statement or for loop) where delayed expansion is required
You can try this:
#echo off
set "file=testfile.txt"
call :removeExtension "%file%"
echo %newFile%
pause
goto :eof
:removeExtension
set "newFile=%~n1"
goto :eof
However, this only works if the file has no path. If it does, you can do this:
#echo off
setlocal EnableDelayedExpansion
set "file=files\testfile.txt"
call :removeExtension "%file%"
echo %newFile%
pause
goto :eof
:removeExtension
set "file=%~1"
set "newFile=!file:%~x1=!"
goto :eof

Reading multiple text files and replacing a certain type of string in each of them using a batch file

I need to read all the text files from a directory and then replace a certain type of string (that comes in a line having 'volumelabel') with another (replacewith) in each of them. Following is the code snippet:
for /r %%g in (*.txt) do (
set filename=%%~nxg
for /F "tokens=3 delims=<>" %%i in ('findstr "volumelabel" !filename!') do (
set tobereplaced=%%i
)
echo !filename! has !tobereplaced! to be replaced by %replacewith%
for /F "tokens=*" %%a in (!filename!) do (
set str=%%a
set str=!str:!tobereplaced!=%replacewith%!
echo !str!>>new!filename!
)
)
Now the problem I am facing is it prints only tobereplaced (literally) in every line of the new files when
set str=!str:!tobereplaced!=%replacewith%!
echo !str!>>new!filename!
is used and prints tobereplaced=replacewith (values) when
set str=%str:!tobereplaced!=%replacewith%%
echo !str!>>new!filename!
is used. Can someone help me?
The least convoluted solution (in my humble opinion) is to use a subroutine to set str. Going more than one level deep of delayed expansion tends to cause severe brain hurt. Oh, you could probably fix your set str line by doing something like
call call call set str=%%%%str:%%tobereplaced%%=%replacewith%%%%%
...or similar. See what I mean about brain hurt? It's hard to follow the recursion.
So here's my suggestion for a solution. I also fixed another potential problem or two while I was at it. Since you're doing a recursive search for *.txt, I made the for loops able to work with whatever text files they find within subdirectories. I haven't tested this, so let me know if you get any grotesque errors.
#echo off
setlocal enabledelayedexpansion
set replacewith=whatever
for /r %%g in (*.txt) do (
set newfile=%%~dpng.new%%~xg
for /F "tokens=3 delims=<>" %%i in ('findstr "volumelabel" "%%g"') do (
set "tobereplaced=%%i"
echo %%~nxg has !tobereplaced! to be replaced by %replacewith%
rem combining your for loops this way makes the second only fire if the first is true
rem using "findstr /n" in your for loop preserves blank lines
for /F "delims=" %%a in ('findstr /n "^" "%%g"') do (
rem ...but you have to strip off the line numbers
set "str=%%a" && set "str=!str:*:=!"
rem "call :repl" to work around the delayed expansion conundrum
call :repl "!str!" "!tobereplaced!" "%replacewith%" str
echo !str!>>!newfile!
)
)
)
goto :EOF
:repl <line> <find> <replace> <var>
setlocal enabledelayedexpansion
set "line=%~1"
set "line=!line:%~2=%~3!"
set "%4=%line%"
goto :EOF
Caveat: If your text file contains exclamation marks, equal signs or carats, they might not make it into textfile.new.txt.
For what it's worth, if I were in your position, instead of using a batch file I would probably use sed (the binaries should be all you need). You wouldn't even need a script. You could do it as a one liner like this:
for /r %I in (*.txt) do sed -r "s/volumelabel/replacement/ig" "%I" > "%~dpnI.new%~xI"
By the way, see the last couple of pages of help for for an explanation of the %~dpnI sort of notation.

Escaping an equals sign in DOS batch string replacement command

I need to replace some text in a JNLP file using a DOS batch file to tune it for the local machine.
The problem is that the search pattern contains an equals sign which is messing up the string replacement in the batch file.
I want to replace the line,
<j2se version="1.5" initial-heap-size="100M" max-heap-size="100M"/>
with specific settings for the initial and max heap sizes.
For example at the moment I have,
for /f "tokens=* delims=" %%a in (%filePath%agility.jnlp) do (
set str=%%a
set str=!str:initial-heap-size="100M"=initial-heap-size="%min%M"!
echo !str!>>%filePath%new.jnlp)
but the = in the search pattern is being read as part of the replacement command.
How do I escape the equals sign so it is processed as text?
The best solution is to download and install Cygwin or GNUWin32 but, if you're really limited to the standard command processor, it can get a little messy.
This is not the fastest method in the world but it's at least functional. This command file processes each line one character at a time, treating specially the case where you find the stanza you're looking for.
#echo off
set init=50M
set max=75M
setlocal enableextensions enabledelayedexpansion
for /f "tokens=* delims=" %%a in (agility.jnlp) do (
set str1=%%a
call :morph
echo !str2!>>agility_new.jnlp
echo !str2!
)
endlocal
goto :eof
:morph
set str2=
:morph1
if not "x!str1!"=="x" (
if "!str1:~0,18!"=="initial-heap-size=" (
set str2=!str2!initial-heap-size="!init!"
set str1=!str1:~24!
goto :morph1
)
if "!str1:~0,14!"=="max-heap-size=" (
set str2=!str2!max-heap-size="!max!"
set str1=!str1:~20!
goto :morph1
)
set str2=!str2!!str1:~0,1!
set str1=!str1:~1!
goto :morph1
)
goto :eof
With the input file:
<j2se version="1.5" initial-heap-size="100M" max-heap-size="100M"/>
next line
===
you end up with:
<j2se version="1.5" initial-heap-size="50M" max-heap-size="75M"/>
next line
===
One cannot simply replace (a substring with) an equal-sign, without splitting up (for-statement with "delims==") or trimming…
But perhaps you could go for this simpler but more confusing approach, using the following statement in your for-loop:
set str=!str:"100M" max-heap-size="%min%M" max-heap-size!
It just combines the string to replace with what comes after instead of what comes before, avoiding any equal-sign replacements entirely.
If you can pass the arguments as something else, such as double underscores, you can iterate through them and convert them to '=' in the batch file.
#rem Replace __ with = in batch files.
#rem This works around the lack of equals signs in args
#rem args contains full args string with substitutions in place
setlocal enabledelayedexpansion
:argloop
if "%~1" NEQ "" (
set str=%~1
set out=!str:__==!
set %~1=!out!
set args=!args!!out!
SHIFT
goto :argloop
)
#rem Can now run program on a line on its own with just %args%
Source: https://github.com/mlabbe/batchargs
Here's an alternative solution. If you can afford to download GNU tools, you can use sed:
C:\test>set a=200
C:\test>sed -i.bak "s/^\(.*initial-heap-size=\"\).*\( max.*\)/\1%a%\"\2/" file

Resources