convert user input character by character with batch - string

ok basicly i want a simple batch program to change X characters to Y characters in a way like this
a-b
b-c
c-d
d-e
e-f
etc etc etc
ive looks up strings and other variable tricks but it doesnt work. heres what i tried and you can see on "codeb" where i tried an alternate method
set /p code=[paste your code here]
set code=%codea:~1%
set /a %code:~2% %codeb%
this was basically my way of attempting to split all the input characters into seperate variables.
if your feeling....bored below is the exact conversion for the translation
a='
b=v
c=x
d=s
e=w
f=d
g=f
h=g
i=u
j=h
k=j
l=k
m=n
n=b
o=i
p=o
q=\
r=e
s=a
t=r
u=y
v=c
w=q
x=z
y=t
z=/
essentially i should be able to "paste" this "v'rxg" into the batch and hit enter and it then displays "batch"

I modified Andriy's code for a faster version that use Batch variables instead of FIND text file. Here it is:
#echo off
setlocal EnableDelayedExpansion
for /f "delims=" %%a in (codechart.txt) do set %%a
:loop
set /P encoded=[paste your code here]
if "%encoded%" == "" goto :eof
set decoded=
call :decode
echo Result:
echo\%decoded%
goto loop
:decode
if "%encoded%" == "" goto :eof
set ec=%encoded:~0,1%
set encoded=%encoded:~1%
set dc=?
if defined %ec% set dc=!%ec%!
set decoded=%decoded%%dc%
goto decode
However, my version does not work when special characters (non-valid variable names) are encoded.
EDIT The only differences of my code vs. Andriy's are these:
setlocal EnableDelayedExpansion: This command is required to use !var! expansion besides %var%, allowing to use both expansions in the same command.
for /f "delims=" %%a in (codechart.txt) do set %%a: This command takes the lines of your conversions and execute a set command with each one, that is, set a=' set b=v etc. This way, the conversions are stored in Batch variables with the name of the original character and the value of the new character.
if defined %ec% set dc=!%ec%!: This line convert the character in ec variable via a direct replacement. For instance, if ec is "b": if defined b set dc=!b!; because b variable exists and contains a "v", then execute set dc=v.

Well, here's a working script that you can play with. It uses your chart in the form of a text file (named codechart.txt in my setup):
#ECHO OFF
:loop
SET /P encoded=[paste your code here]
IF [%encoded%]==[] GOTO :EOF
SET decoded=
CALL :decode
ECHO Result:
ECHO(%decoded%
GOTO loop
:decode
IF [%encoded%]==[] GOTO :EOF
SET ec=%encoded:~0,1%
SET encoded=%encoded:~1%
SET dc=?
FOR /F "delims==" %%C IN ('FIND "=%ec%" ^<codechart.txt') DO SET dc=%%C
SET decoded=%decoded%%dc%
GOTO decode
If you enter a non-empty string, it will produce a result. Hitting Enter without actually entering anything will terminate the batch. If a character is not found in the chart, it will be represented as a ? in the output.

Related

Batch Trim Lines in Text File after Sub-String - REM Trim with Expansion

I am running Windows 10 Pro using batch files (open to using VBS and PS1 files) and I have a text file automatically exported by software that can look like this:
Sub_Group691_FE7IP11_2017-12-12.sldasm_bin/parts/Loft-Project.sldasm_bin/parts/Loft-Project...
Sub_Group691_FE7IP12_2017-12-12.sldasm_bin/parts/Loft-Project.sldasm_bin/parts/Loft-Project...
Sub_Group691_FE7IP13_2017-12-12.sldasm_bin/parts/Loft-Project.sldasm_bin/parts/Loft-Project...
Each line continues specifying sub-parts after the "..." and could contain "poison" characters. The Sub_Group part is pulled from the filename and can also contain "poison" characters.
What I am looking to do is export just the filename which is right at the beginning of each line, up to and including the first file extension, in this case ".sldasm." Everything to the right of the first instance of .sldasm should be trimmed.
What I have cobbled together so far from research on Stackoverflow is:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "inputfile=C:\Scratch\ASMExport.txt"
SET "outputfile=C:\Scratch\InputFiles.txt"
(
FOR /f "usebackqdelims=" %%a IN ("%inputfile%") DO (
SET "currentline=%%a"
ECHO("!currentline:.sldasm=.sldasm & rem "!"
)
)>"%outputfile%"
GOTO :EOF
My problem lies with the "rem" line, which does not seem to work as intended either because of being within a FOR loop or because of needing to enable delayed expansion. It seems to be parsing the "& rem" as text, which looks to be because of the way delayed expansion works. What I get from the above lines is:
SubGroup691_FE7IP11_2017-12-12.sldasm" & rem "/bin/parts/Loft-Project.sldasm" & rem ""
SubGroup691_FE7IP12_2017-12-12.sldasm" & rem "/bin/parts/Loft-Project.sldasm" & rem ""
SubGroup691_FE7IP13_2017-12-12.sldasm" & rem "/bin/parts/Loft-Project.sldasm" & rem ""
I can use this same line outside the loop and without ENABLEDELAYEDEXPANSION like this:
#ECHO OFF
SETLOCAL
SET "inputstring=Sub_Group691_FE7IP11_2017-12-12.sldasm_bin/parts/Loft-Project.sldasm_bin/parts/Loft-Project"
SET "outputstring=%inputstring:.sldasm=.sldasm & rem "%"
echo %outputstring%
The output to that would give me what I am looking for:
Sub_Group691_FE7IP11_2017-12-12.sldasm
In searching, I am beginning to think that rem cannot be used in this way, and I must move to a token delimiter loop using a bogus delimiter.
I would be content in getting this to work and not worrying about "poison" characters in the filename by being diligent about naming files correctly.
Thank you for your help!
The & rem approach to truncate strings to the right cannot work with delayed expansion (!), it relies on normal/immediate expansion (%). This is because immediate expansion is done before commands (like rem) are recognised, but delayed expansion happens afterwards, so the rem command is never executed.
However, you could in the loop replace every .sldasm by a forbidden character like | and then split the string by a for /F loop, like this:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "InputFile=C:\Scratch\ASMExport.txt"
set "OutputFile=C:\Scratch\InputFiles.txt"
set "Extension=.sdlasm"
> "%OutputFile%" (
for /F usebackq^ delims^=^ eol^= %%A in ("%InputFile%") do (
set "CurrentLine=%%A"
setlocal EnableDelayedExpansion
set "CurrentLine=!CurrentLine:%Extension%=|!"
for /F "delims=|" %%B in ("!CurrentLine!") do (
endlocal
echo(%%B
setlocal EnableDelayedExpansion
)
endlocal
)
)
endlocal
exit /B
Delayed expansion is toggled so that no for variables become expanded when it is enabled, in order to avoid loss of or problems with exclamation marks.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q47788829.txt"
FOR /f "usebackqtokens=1delims=/" %%a IN ("%filename1%") DO (
FOR /f "tokens=1*delims=." %%b IN ("%%a") DO (
FOR /f "tokens=1*delims=_" %%d IN ("%%c") DO (
ECHO %%%%-a=%%~a %%%%-b=%%~b %%%%-c=%%~c %%%%-d=%%~d %%%%-e=%%~e ^<^<
)
)
)
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q47788829.txt containing your data for my testing.
You should be able to assemble your required report data from the elements %%a..%%e displayed. All a matter of using tokens and delims constructively.
If your input is the text that you wrote, with the same structure, I think this batch should work and extract the names that you want quickly.
CODE:
#echo off
setlocal enabledelayedexpansion
set "inputFile=input.txt"
set "outputFile=result.txt"
for /f "delims=/ tokens=1" %%A in (%inputFile%) do (
set "tempFileName=%%A"
echo !tempFileName:~0,-4!>> %outputFile%
)
You only had to adjust the variables 'inputFile' and 'outputFile' to your needs.

How to use FINDSTR to find a string in value of a variable?

Is it possible to use FINDSTR with a variable instead of a file?
I've tried researching this but my knowledge on batch isn't good enough yet.
I've made most of my batch file work flawlessly for what I need, but I'm having trouble extracting part of a string variable into a new variable.
The original variable for example would be S02E12 - Charge!. I would like to extract 02 to ep_seas, 12 to ep_num, and Charge! to ep_name. I have it working right now if that's the exact pattern of names, but I've come across some files that are in this pattern: S02E124 - Charge #2!
Is there a way I can dynamically get the values I need regardless of their length?
My idea was to use FINDSTR to search between the S and the E, then between E and space   (or -), then between - and the end. I'm not sure how I would proceed with this though.
Does anyone have a solution I can look into or can someone provide an example?
setlocal DisableDelayedExpansion
set mkvmerge="C:/Program Files/MKVToolNix/mkvmerge.exe"
set "output_folder=%cd%\Muxing"
for /r %%a in (*.mkv) do (
set fi=%%a
set ep=%%~na
call :merge
)
goto :eof
:merge
set ep_name=%ep:~9%
set ep_num=%ep:~4,2%
set ep_seas=%ep:~2,1%
call %mkvmerge% -o "%output_folder%\%ep%.mkv" --track-name "0:%ep_name%" --language 0:und --default-track 0:yes --track-name "1:[JAP]" --language 1:jpn --default-track 1:yes --track-name "2:[ENG]" --language 2:eng --default-track 2:yes --forced-track 2:yes "%fi%" --track-order 0:0,0:1,0:2 --title "Fate Zero - Episode %ep_num%"
goto :eof
You could use this code for your task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "output_folder=%CD%\Muxing"
for /R %%I in (*.mkv) do (
set "fi=%%I"
set "ep=%%~nI"
call :merge
)
goto :EOF
:merge
for /F "tokens=1,2* delims=ES- " %%A in ("%ep%") do (
set "ep_seas=%%A"
set "ep_num=%%B"
set "ep_name=%%C"
)
"%ProgramFiles%\MKVToolNix\mkvmerge.exe" -o "%output_folder%\%ep%.mkv" --track-name "0:%ep_name%" --language 0:und --default-track 0:yes --track-name "1:[JAP]" --language 1:jpn --default-track 1:yes --track-name "2:[ENG]" --language 2:eng --default-track 2:yes --forced-track 2:yes "%fi%" --track-order 0:0,0:1,0:2 --title "Fate Zero - Episode %ep_num%"
goto :EOF
The command FOR /F as used here parses the string in double quotes which is the file name.
The option "tokens=1,2* delims=ES- " results in splitting up the file name strings S02E12 - Charge! and S02E124 - Charge #2! into 3 substrings using the 4 specified characters as delimiters.
Token 1 is 02 for both file names assigned to specified loop variable A.
Token 2 is 12 and 124 for the two file names assigned to next loop variable after A according to ASCII table which is B.
Token 3 is everything after the delimiters after token 2 which is Charge! and Charge #2! for the two files assigned to next but one loop variable C.
You know now why loop variables are case-sensitive while environment variables are not case-sensitive. The specified loop variable defines which characters the next loop variables have on using for /F with more than one token/substring.
* appended to a token number X means rest of the string/line after the delimiters after substring X should be assigned to next loop variable without any further splitting on delimiters. So the string after S02E12 -  and S02E124 -  can contain also delimiter characters and is nevertheless assigned completely to loop variable C.
Please note that a string starting with E or S after hyphen and space is interpreted also as delimiter and therefore missing in string assigned to loop variable C. The FOR loop in subroutine merge could be replaced by following code to get a working solution for a file name like S03E48 - Extended Version.mkv.
for /F "tokens=1* delims=- " %%A in ("%ep%") do (
set "ep_name=%%B"
for /F "tokens=1,2 delims=ES" %%C in ("%%A") do (
set "ep_seas=%%C"
set "ep_num=%%D"
)
)
The outer loop assigns S03E48 to loop variable A and Extended Version to loop variable B. The inner loop splits up S03E48 once again into 03 assigned to loop variable C and 48 assigned to loop variable D.
By the way: The directory separator on Windows is \ and not / as on Unix/Linux/Mac although Windows kernel functions support also file/directory paths with / with automatic correction to \.
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 /?
goto /?
set /?
setlocal /?
One last note:
It is possible to use FINDSTR on an environment variable for example with:
echo %ep% | findstr /R "^S[0123456789][0123456789]"
But FINDSTR never outputs just the matching string. It always outputs the entire line containing the found string. So FINDSTR is of no help here for splitting the file name up into separate parts.

Find string with special character in text file and add line break before each occurrence

I have a text file that is one long string like this:
ISA*00*GARBAGE~ST*TEST*TEST~CLP*TEST~ST*TEST*TEST~CLP*TEST~ST*TEST*TEST~CLP*TEST~GE*GARBAGE*~
And I need it to look like this:
~ST*TEST*TEST~CLP*TEST
~ST*TEST*TEST~CLP*TEST
~ST*TEST*TEST~CLP*TEST
I first tried to add a line at every ~ST to split the string up, but I can't for the life of me make this happen. I have tried various scripts, but I thought a find/replace script would work best.
#echo off
setlocal enabledelayedexpansion
set INTEXTFILE=test.txt
set OUTTEXTFILE=test_out.txt
set SEARCHTEXT=~ST
set REPLACETEXT=~ST
for /f "tokens=1,* delims=~" %%A in ( '"type %INTEXTFILE%"') do (
SET string=%%A
SET modified=!string:%SEARCHTEXT%=%REPLACETEXT%!
echo !modified! >> %OUTTEXTFILE%
)
del %INTEXTFILE%
rename %OUTTEXTFILE% %INTEXTFILE%
Found here How to replace substrings in windows batch file
But I'm stuck because (1) the special character ~ makes the code not work at all. It gives me this result:
string:~ST=~ST
The code does nothing at all if using quotes around "~ST". And (2) I can't figure out how to add a line break before ~ST.
The final task for this would be to delete the ISA*00*blahblahblah and ~GE*blahblahblah lines after all splits have been performed. But I am stuck on the splitting at ~ST part.
Any suggestions?
#echo off
setlocal EnableDelayedExpansion
rem Set next variable to the number of "~" chars that delimit the wanted fields, or more
set "maxTokens=7"
rem Define the delimiters that starts a new field
set "delims=/ST/GE/"
for /F "delims=" %%a in (test.txt) do (
set "line=%%a"
set "field="
rem Process up to maxTokens per line;
rem this is a trick to avoid a call to a subroutine that have a goto loop
for /L %%i in (0,1,%maxTokens%) do if defined line (
for /F "tokens=1* delims=~" %%b in ("!line!") do (
rem Get the first token in the line separated by "~" delimiter
set "token=%%b"
rem ... and update the rest of the line
set "line=%%c"
rem Get the first two chars after "~" token like "ST", "CL" or "GE";
rem if they are "ST" or "GE":
for %%d in ("!token:~0,2!") do if "!delims:/%%~d/=!" neq "%delims%" (
rem Start a new field: show previous one, if any
if defined field echo !field!
if "%%~d" equ "ST" (
set "field=~%%b"
) else (
rem It is "GE": cancel rest of line
set "line="
)
) else (
rem It is "CL" token: join it to current field, if any
if defined field set "field=!field!~%%b"
)
)
)
)
Input:
ISA*00*GARBAGE~ST*TEST1*TEST1~CLP*TEST1~ST*TEST2*TEST2~CLP*TEST2~ST*TEST3*TEST3~CLP*TEST3~GE*GARBAGE*~CLP~TESTX
Output:
~ST*TEST1*TEST1~CLP*TEST1
~ST*TEST2*TEST2~CLP*TEST2
~ST*TEST3*TEST3~CLP*TEST3
Don't reinvent the wheel, use a regexp replace tool such as sed or JREPL.BAT:
call jrepl "^.*?~ST(.+?)~GE.*$" "'~ST'+$1.replace(/~ST/g,'\r\n$&')" /jmatch <in.txt >out.txt
The ~ cannot be used as the first character of a search string in the substring substitution syntax %VARIABLE:SEARCH_STRING=REPLACE_STRING%, because it is used to mark the substring expansion %VARIABLE:~POSITION,LENGTH% (type set/? for more information).
Supposing your text file contains a single line of text only and it does not exceed a size of about 8 kBytes, I see the following option for accomplishing your task. This script makes use of the substring substitution syntax %VARIABLE:*SEARCH_STRING=REPLACE_STRING%; the * defines to match everything up to the first occurrence of SEARCH_STRING:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem initialise constants:
set "INFILE=test_in.txt"
set "OUTFILE=test_out.txt"
set "SEARCH=ST"
set "TAIL=GE"
rem read single-line file content into variable:
< "%INFILE%" set /P "DATA="
rem remove everything before first `~%SEARCH%`:
set "DATA=~%SEARCH%!DATA:*~%SEARCH%=!"
rem call sub-routine, redirect its output:
> "%OUTFILE%" call :LOOP
endlocal
goto :EOF
:LOOP
rem extract portion right to first `~%SEARCH%`:
set "RIGHT=!DATA:*~%SEARCH%=!"
rem skip rest if no match found:
if "!RIGHT!"=="!DATA!" goto :TAIL
rem extract portion left to first `~%SEARCH%`, including `~`:
set "LEFT=!DATA:%SEARCH%%RIGHT%=!"
rem the last character must be a `~`;
rem so remove it; `echo` outputs a trailing line-break;
rem the `if` avoids an empty line at the beginning;
rem the unwanted part at the beginning is removed implicitly:
if not "!LEFT:~,-1!"=="" echo(!LEFT:~,-1!
rem output `~%SEARCH%` without trailing line-break:
< nul set /P "DUMMY=~%SEARCH%"
rem store remainder for next iteration:
set "DATA=!RIGHT!"
rem loop back if remainder is not empty:
if not "!DATA!"=="" goto :LOOP
:TAIL
rem this section removes the part starting at `~%TAIL%`:
set "RIGHT=!DATA:*~%TAIL%=!"
if "!RIGHT!"=="!DATA!" goto :EOF
set "LEFT=!DATA:%TAIL%%RIGHT%=!"
rem output part before `~%TAIL%` without trailing line-break:
< nul set /P "DUMMY=!LEFT:~,-1!"
goto :EOF
The following restrictions apply to this approach:
the input file contains a single line;
the size of the input file does not exceed about 8 kBytes;
there is exactly one instance of ~GE, that occurs after all instances of ~ST;
there is always at least one character in between two adjacent ~ST instances;
no special characters occur in the file, like: SPACE, TAB, ", %, !, =;

Batch call another batch and use output as variable

I'm trying to compare a bunch of files with names like
"BD12-CD9.txt"
to folders named
"BD12-CD9 - somefoldername"
I'm removing the .txt extension to get the ID code "BD12-CD9" and want to check if the folder name contains the code.
I can't figure out how to do this without an if statement that compares strings exactly. (might be an easy solution here like jQuery's indexOf())
So I need to strip the ID code from the file name and do a direct comparison.
The problem is the ids and names are not always exactly the same length e.g. "BD12-CD10.txt" so I can't just do "set folderName2=!folderName:~0,8!"
I found a strlength function and put it in another batch file called strlen.bat and it works great, echoing the length of my stripped ID codes. How do I use the echoed value though?
move.bat
#echo off
#setlocal enabledelayedexpansion
set "errfolderpath=C:\Testing\Moving txt files automatically\"
echo.
for %%x in (*.*) do (
set fileName=%%x
set fileName2=!fileName!
set fileName2=!fileName2:~0,-4!
call strlen !fileName2!
FOR /D %%K in ("%errfolderpath%*") DO (
SET folderName=%%~nK
set folderName2=!folderName!
set folderName2=!folderName:~0,11!
echo folder: !folderName2!, file: !fileName2!
if !folderName2! == !fileName2! echo MOVE '!fileName!' to '!folderName!'
)
echo %_len%
echo.
)
PAUSE
strlen.bat
#echo off
:: strlen.bat
:: http://acm.zhihua-lai.com
if [%1] EQU [] goto end
:loop
if [%1] EQU [] goto end
set _len=0
set _str=%1
set _subs=%_str%
:getlen
if not defined _subs goto result
:: remove first letter until empty
set _subs=%_subs:~1%
set /a _len+=1
goto getlen
:result
echo %_len%
shift
goto loop
:end
For the batch file variable usage, if you look to the code, a variable called _len is defined. This variable is then echoed to console, but is not removed (no reasignation, no endlocal, ...) , so just use it.
Anyway, it is easier than it seems.
#echo off
setlocal enableextensions disabledelayedexpansion
set "errfolderpath=C:\Testing\Moving txt files automatically"
for %%a in (*.txt) do (
set "notMoved=1"
for %%b in ("%errorfolderpath%\%%~na*") do if defined notMoved (
set "notMoved="
move "%%~fa" "%%~fb"
)
)
For each file, search a folder with the same prefix (the name of the file referenced in %%a, without extension, is %%~na). If found, move the file (the full path to the file is %%~fa) to the target folder and use a switch variable to "mark" the file as moved to avoid problems in the case of two folders with the same prefix (that will be retrieved in the inner for %%b) .

.bat file search and replace strings in a structured text file

I have a text file that groups different subsystems using [ ] and then contains item flags in each subgroup. Here is a snippet of the file such that you can get an understanding for what it looks like (notice each subgroup can have the same items):
[EV]
Verbosity=0
Alignment=123
[FluidLevelControl]
BufferTypeLastUsed=TWEEN
Enable Dip Tube=no
Alignment=456,efg
[PressureLevelControl]
Enabled=yes
Alignment=789,abc
Calibration Date=1280919634
[BufferFrontValve]
Log=yes
Alignment=987
Note, the above file is in excess of 2000 lines. I imagine the script is going to take a little while to execute. I also know that there is a better framework to do this but in our application we need it to run from a flash drive and be able to be plugged into our instrument which run WinXP without a .NET frameworks etc.
What I would like to do is use a .bat file to search the document for a specific subsystem (ie. [DesiredSubsystem]) and desired item within the subsystem then modify the item data. For example, in the above text I may want to change the Alignment from 789 to 12345 in the PressureLevelControl subgroup.
I understand there is no way to effective replace / update a text file using a bat file. I've created a function to read in a file and write it to a new file, now I'm trying to develop a clean way to identify the line items and what subgroup they are in as to replace the desired text as needed.
Here is what I have commented with my plan:
Update: I spent the afternoon writing some code that seems to work as shown below, there is most def better methods.
::SET VARS
set "varDebugFP=\\svchafile\Teams\Test Engineering\Productivity Tools\MFG BAT Files\SpecificTest\"
set varSource=%varDebugFP%Debug\
set varDestination=%varDebugFP%Debug\
set varFileName=specific.ini
::Do Text File Editing
setlocal enableDelayedExpansion
set "LastGroup=NONE"
::preserve blank lines using FINDSTR, even if a line may start with :
for /f "usebackq delims=*" %%A in (`type "%srcFile%" ^| findstr /n "^"`) do (
set "strLine=%%A"
set "strLine=!strLine:*:=!"
::Check to see if the is defined and greater than 2 characters inidicating a good line
if defined strLine if NOT "!strLine:~2,1!"=="" if "!strLine:~0,1!!strLine:~-1!"=="[]" (set "LastGroup=!strLine!")
::Set the paramaters looking to match
set "DesiredGroup=[TestGroup]"
set "DesiredItem=TestItem"
set "ReplaceLineWith=NewTestItemLine=NewData"
::Look for match on current line
if defined strLine if "!LastGroup!"=="!DesiredGroup!" if NOT "!strLine!"=="!strLine:TestItem=Mod!" (set "strLine=!ReplaceLineWith!")
::Note, in the above line I would like 'TestItem' to be the 'DesiredItem' variable but I can't get it working due to the DelayedExpansion
::Set the additonal paramaters looking to match
::Note, there are multiple items I want to change at once without having to reitterate through the org long (2000+lines) file
set "DesiredGroup=[TestGroup2]"
set "DesiredItem=TestItem2"
set "ReplaceLineWith=NewTestItemLine2=NewData2"
if defined strLine if "!LastGroup!"=="!DesiredGroup!" if NOT "!strLine!"=="!strLine:TestItem=Mod!" (set "strLine=!ReplaceLineWith!")
::I plan to copy and paste the above section as many times as needed to capture all the lines I need to edit (at this point about ~10)
::I don't really understand why the "(" in the below line, I found it in an example on stackoverflow and it seems to work.
echo(!strLine!>>"%newFile%"
)
endlocal
::Replace org file with new file, delete org file (this part I have figured out)
Is there a better way of doing this? Can anyone help complete the code as I'm having a lot of trouble parsing this correctly.
Update: Thanks for the two methods proposed in the answers below. They are very long and I learned a lot from them. Not entirely sure how to implement the functions however and my biggest concern is that using the function will make repetitive reads from the file slowing it down dramatically. I'm very new to this bat file thing but I know its very powerful if you know the commands and are creative.
Thanks in advance for any and all help. -Dan
So many people want to use batch to edit text files - There are many SO questions dealing with the subject. But it is quite difficult (and relatively slow) to do so robustly using only native batch commands.
You are better off using some other tool. One good option is to use something like a free Windows port of sed or awk. But those require downloading non-native executables onto your machine, something that is forbidden in many offices.
I have written REPL.BAT - a hybrid JScript/batch utility that performs a regular expression search and replace on stdin and writes the result to stdout.. The script only uses native scripting available to all modern Windows machines from XP onward. Full documentation is embedded within the script, including a link to a MicroSoft page that describes all available JScript regex metacharacters.
Assuming that REPL.BAT is in your current directory, or better yet, somewhere within your PATH, then the following simple batch script can be used to modify the value of any item within a specific subsystem.
::MODIFY_CONFIG.BAT File SubSystem Item NewValue
::
:: Any argument that contains spaces or special characters should be quoted.
::
:: File = File to modify, may include full path
:: SubSystem = The section containing the item to modify (without brackets)
:: Item = The Item within the SubSystem that is to be modified
:: NewValue = The new value for the item
#echo off
type "%~1"|repl "(^ *\[%~2] *\r?\n(?: *[^[].*\n)*? *%~3=)[^\r\n]*" "$1%~4" m >"%~1.new"
move /y "%~1.new" "%~1" >nul
Here is a call to the script that changes Alignment within PressureLevelControl to 12345
MODIFY_CONFIG yourFile.ini PressureLevelControl Alignment 12345
#ECHO OFF
SETLOCAL
:: Read parameters
:: %1 is subgroup
:: %2 is item
:: %3 is new value
:: %3 missing = report value
SET "subgroup=%~1"
SET "item=%~2"
SET "newval=%~3"
IF NOT DEFINED subgroup ECHO syntax:%~nx0 "subgroup" "item" "newvalue"&GOTO :EOF
IF NOT DEFINED item ECHO syntax:%~nx0 "subgroup" "item" "newvalue"&GOTO :EOF
ECHO %*
:: state=0 (looking for subgroup) 1 (found subgroup)
SET /a state=0
:: result=0 (did nothing) 2 (found subgroup, not data line) 3 (found subgroup more than once)
:: 4 (found and replaced data line) 5 (found subgroup more than once, replaced data once)
:: 6 (detected dataline more than once, replaced once) 9 (reporting only - value found)
SET /a result=0
(
FOR /f "tokens=1*delims=:" %%a IN ('findstr /n /r "^" q21263073.txt') DO (
SET "line=%%b"
CALL :process
IF DEFINED repro ECHO(%%b
REM pause
)
)>newfile.txt
SET "replacefile="
CALL :report%result%
IF DEFINED replacefile ECHO A NEW FILE HAS BEEN CREATED
GOTO :EOF
:report0
ECHO [%subgroup%] NOT found
GOTO :eof
:report2
ECHO [%subgroup%] %item% NOT found
GOTO :eof
:report3
ECHO [%subgroup%] found repeatedly - %item% NOT found
GOTO :eof
:report4
ECHO [%subfound%] %olditem% found replaced %oldvalue% with %newval%
SET replacefile=Y
GOTO :eof
:report5
ECHO [%subgroup%] found repeatedly - %olditem% found replaced %oldvalue% with %newval%
GOTO :eof
:report6
ECHO [%subgroup%] %item% found repeatedly - %olditem% found replaced %oldvalue% with %newval% ONCE
GOTO :eof
:report9
ECHO [%subgroup%] %olditem% found with value %oldvalue%
GOTO :eof
:process
:: blank line ?
SET repro=Y
IF NOT DEFINED line GOTO :EOF
IF "%line:~0,1%%line:~-1%"=="[]" GOTO fsubsys
:: only process data lines if state=1
IF NOT %state%==1 GOTO :EOF
IF %result% gtr 5 GOTO :EOF
SET "fvalue="
FOR /f "tokens=1*delims==" %%p IN ("%line%") DO SET "fitem=%%p"&SET "fvalue=%%q"
:: Did we have an item=value line?
IF NOT DEFINED fvalue GOTO :EOF
CALL :matchit "%fitem%" "%item%"
IF NOT DEFINED matched GOTO :eof
:: we found a matching item within a subgroup.
:: result must be 2,3,4 or 5
FOR %%z IN (2.4 3.5 4.6 5.6) DO FOR /f "tokens=1,2delims=." %%c IN ("%%z") DO IF %result%==%%c SET result=%%d
IF %result%==6 GOTO :EOF
:: Haven't yet replaced value
:: Do we have a replacement?
SET "olditem=%fitem%"&SET "oldvalue=%fvalue%"
IF NOT DEFINED newval SET result=9&GOTO :eof
SET "repro="
ECHO(%fitem%=%newval%
GOTO :eof
:: found a subgroup name
:fsubsys
SET /a state=0
:: Is it the one we're looking for?
CALL :matchit "%line:~1,-1%" "%subgroup%"
IF NOT DEFINED matched GOTO :eof
SET /a state=1
FOR %%z IN (0.2 2.3 4.5) DO FOR /f "tokens=1,2delims=." %%c IN ("%%z") DO IF %result%==%%c SET result=%%d
IF %result%==2 SET "subfound=%line:~1,-1%"
GOTO :eof
:: match %1 to %2. If matches, set matched to not empty. if not, set matched to empty
:: here is where we can have some fun.
:matchit
SET "matched="
SET "string1=%~1"
SET "string2=%~2"
:: Case-insensitive exact match?
IF /i "%string1%"=="%string2%" SET matched=Y&GOTO :EOF
:: partial-string match. If specified item begins "+" then match rest against item found
:: so +ali matches "Alignment"
IF NOT %string2:~0,1%==+ GOTO npsm
CALL SET string3=%%string1:*%string2:~1%=%%
IF /i "%string3%"=="%string1%" GOTO :eof
IF /i "%string2:~1%%string3%"=="%string1%" SET matched=Y
GOTO :EOF
:: initials - so "Enable Dip Tube" is matched by "edt"
:npsm
CALL :inits %string1%
IF /i "%string3%"=="%string2%" SET matched=Y
GOTO :eof
:inits
SET "string3=%2"
IF NOT DEFINED string3 GOTO :EOF
SET "string3="
:initsl
SET string1=%1
IF NOT DEFINED string1 GOTO :EOF
SET string3=%string3%%string1:~0,1%
SHIFT
GOTO initsl
OK - I got carried away...
I used a file named q21263073.txt with your sample data for my testing. The file newfile.txt will be produced and may be made to replace the original if desired (advisable only if replacefile is defined.)
Required syntax is thisbatch subgroup item newvalue
Any parameter that contains spaces should be "quoted".
Features:
name-matching is case-insensitive.
You can use a leading + to abbreviate to a unique start-of-string, so +pr
would match PressureLevelControl.
Error report produced if abbreviation is not unique within a section.
You can abbreviate Space Separated Names to the initials SSN.
if the newvalue is omitted, a report of an existing value is shown.
It should be fairly obvious that you could easily make modifications to allow values to be added or deleted rather than just changed.
Playing with the filenames and other matters are now in the hands of those that show an interest.

Resources