3

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

Beck04vall
  • 61
  • 5
  • Maybe you can consider this article: http://blogs.technet.com/b/deploymentguys/archive/2010/07/15/reading-and-modifying-ini-files-with-scripts.aspx – mihai_mandis Jan 21 '14 at 17:06
  • This might be where I would argue to use the right tool for the right job. I would recommend using anything that exposes the [`GetPrivateProfileString`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms724353%28v=vs.85%29.aspx) family of functions, as this is what most programs use which didn't reproduce the functionality from scratch. A scripting language worth looking at, which does have access to it, is [AutoIt](http://www.autoitscript.com/site/autoit/). – daalbert Jan 21 '14 at 18:56
  • dbenham's REPL.BAT solution is more robust than plain batch code when processing files, and is also faster than plain batch code. It is only reading the file once, so test his solution to double check the function. – foxidrive Jan 22 '14 at 03:49

2 Answers2

4

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
Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390
2
@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:

  1. name-matching is case-insensitive.
  2. You can use a leading + to abbreviate to a unique start-of-string, so +pr would match PressureLevelControl.
  3. Error report produced if abbreviation is not unique within a section.
  4. You can abbreviate Space Separated Names to the initials SSN.
  5. 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.

Magoo
  • 77,302
  • 8
  • 62
  • 84