1

So I got VERY close to what I wanted to achieve, but I couldn't seem to get past the points I am currently stuck on.

First off, I have a .ini file that has a header along with a list of items separated by a comma:

[Items]
ListOfItems = Item01, Item02, Item03

What I need to do is find that specific section (ListOfItems) and append a new item to it that doesn't exist in a reference file.

Here's what I have so far:

SET /P NewItem=
SET ListOfItems=
FOR /F delims^=^"^ tokens^=2 %%G IN ('type File.ini') DO  (set ListOfItems=%%G)
echo ListOfItems ^=^"%ListOfItems%, %NewItem%^">File.ini

The issue I have is that I am using a prompt instead of taking a new line from a file (List.txt) because the last time I tried it kept repeating the same line over and over, instead of only taking one of each line, provided it isn't a duplicate.

The other issue I have is that, with the current code above, it doesn't preserve the current state of the .ini file. So you can have the example as per above, but once you add a new item via the prompt you end up with:

ListOfItems =", Item04"

The header is gone, the original values are gone, and it starts with a comma, instead of only adding a comma after each value.

So the expected result was:

[Items]
ListOfItems = Item01, Item02, Item03, Item04

Why is it not preserving the original data when first run, but subsequent runs perfectly copies the original data and adds to it?

How can we address the issue of it starting with a comma, with no previous data in front of it?

How can we have it pull the list of items to append from List.txt instead of manually entering them one at a time?

EDIT: I've managed to address the 2nd issue with the following:

(
    ECHO [Items]
    ECHO !ArchiveList! = !RequiredItems!
)>>File.ini

Where !RequiredItems! contain the base items Item01, Item02, Item03 which I do when the ListOfItems are not populated, as per John Kens' suggestion.

As for the 3rd issue, I solved it with:

FOR /F "delims=" %%A IN (List.txt) DO (
    SET "ListAddition=!ListAddition!%%A, "
)
SET "ListAddition=!ListAddition:~0,-2!"

Which takes each item in the List.txt file, which is on a new line, and then adds it to a list, each item separated by a comma.

1
  • I realise these are multiple questions, and in individual parts I can perform certain actions such as append to a string, find a string, etc. but when it comes to combining all of this into a single project I fell short. Multiple examples online would solve one issue or another, but the combination thereof, especially with FOR loops, needing the input of other inputs, was where I got lost. Commented Dec 4, 2018 at 1:41

1 Answer 1

2

Alright first things first, your current for loop needs some tweaking; There are a few things we can do here. If we just wanted to grab what ListOfItems equals from the seconened line and ignore the fact there may be other objects in this file, then the proper way to grab this data will the the following:

FOR /F "skip=1 tokens=2,*" %%A IN ('type File.ini') DO (set "ListOfItems=%%B")
  • skip=1 - Will skip the first line
  • tokens=2,* - Having 2,* will cause %%A to be first two objects, and %%B will be everything after 2.

To be more proper however, the correct way will be to use a find /i statment to look for the ListOfItems object within the .ini file. Lets take the following text file bellow:

[Items]
ListOfItems = Item01, Item02, Item03
[Armor]
ListOfArmor = Iron Helmet, Iron Brestplate, Iron Pants
[Weapons]
ListOfWeapons = Sword, Dagger, Holy Water

If we used that basic for loop on this statement we would get the last line of the text file:

Sword, Dagger, Holy Water

Bellow is using the find statement along with another loop we can combine them to only extract data after ListOfItems from the whole document.

Rem | Get .ini To String
FOR /F "tokens=*" %%A IN ('type File.ini') DO (

    Rem | Look For Line With Items "ListOfItems"
    FOR /F "tokens=2,*" %%B IN ('echo %%A^| find /i "ListOfItems"') DO (

        Rem | Echo Result
        echo %%C
    )
)

However, for correcting only that one line and adding a new object to the end, this is where it gets tricky! Keep in mind that batch is primitive and loosing support, it's limitation's are well, limited compared to its successor, powershell. In raw batch there is not a true command for editing just one line in the middle of a document. However, this does not make it impossible.

To get around this we will have to take the entire .ini file and use the type command to break down the document line by line and save it as a string. From there we can use syntax-replace to edit the "String" and save it to a new document. From there we just delete and rename.

To further expand on this we will need to check if ListOfItems is actually populated. The basic if exists statement will work great here. Now because your statement has an = in the equation, simple syntax-replace will not work without further complications. From my previous edit, I changed the simple function to a script that we will call too. This script will be called Replace.bat. All you need to do is make a new .bat file and paste it in from bellow. This file will never need modified.

Bellow is the entire project that should solve all your issues:

Replace.Bat:

(This entire script is the equivalent of a single 15 character command in powershell lol!)

@echo off
setlocal EnableExtensions DisableDelayedExpansion

set "FILE_I=%~1"
set "SEARCH=%~2"
set "REPLAC=%~3"
set "FILE_O=%~4"
set "FLAG=%~5"
if not defined FILE_I exit /B 1
if not defined SEARCH exit /B 1
if not defined FILE_O set "FILE_O=con"
if defined FLAG set "FLAG=#"

for /F "delims=" %%L in ('
    findstr /N /R "^" "%FILE_I%" ^& break ^> "%FILE_O%"
') do (
    set "STRING=%%L"
    setlocal EnableDelayedExpansion
    set "STRING=!STRING:*:=!"
    call :REPL RETURN STRING SEARCH REPLAC %FLAG%
    >> "%FILE_O%" echo(!RETURN!
    endlocal
)

endlocal
exit /B


:REPL  rtn_string  ref_string  ref_search  ref_replac  flag
setlocal EnableDelayedExpansion
set "STR=!%~2!"
set "SCH=!%~3!"
set "RPL=!%~4!"
if not defined SCH endlocal & set "%~1=" & exit /B 1
set "SCH_CHR=!SCH:~,1!"
if not "%~5"=="" set "SCH_CHR="
if "!SCH_CHR!"=="=" set "SCH_CHR=" & rem = terminates search string
if "!SCH_CHR!"==""^" set "SCH_CHR=" & rem " could derange syntax
if "!SCH_CHR!"=="%%" set "SCH_CHR=" & rem % ends variable expansion
if "!SCH_CHR!"=="^!" set "SCH_CHR=" & rem ! ends variable expansion
call :LEN SCH_LEN SCH
call :LEN RPL_LEN RPL
set /A RED_LEN=SCH_LEN-1
set "RES="
:LOOP
call :LEN STR_LEN STR
if not defined STR goto :END
if defined SCH_CHR (
    set "WRK=!STR:*%SCH_CHR%=!"
    if "!WRK!"=="!STR!" (
        set "RES=!RES!!STR!"
        set "STR="
    ) else (
        call :LEN WRK_LEN WRK
        set /A DFF_LEN=STR_LEN-WRK_LEN-1,INC_LEN=DFF_LEN+1,MOR_LEN=DFF_LEN+SCH_LEN
        for /F "tokens=1,2,3 delims=," %%M in ("!DFF_LEN!,!INC_LEN!,!MOR_LEN!") do (
            rem set "RES=!RES!!STR:~,%%M!"
            if defined WRK set "WRK=!WRK:~,%RED_LEN%!"
            if "!STR:~%%M,1!!WRK!"=="!SCH!" (
                set "RES=!RES!!STR:~,%%M!!RPL!"
                set "STR=!STR:~%%O!"
            ) else (
                set "RES=!RES!!STR:~,%%N!"
                set "STR=!STR:~%%N!"
            )
        )
    )
) else (
    if "!STR:~,%SCH_LEN%!"=="!SCH!" (
        set "RES=!RES!!RPL!"
        set "STR=!STR:~%SCH_LEN%!"
    ) else (
        set "RES=!RES!!STR:~,1!"
        set "STR=!STR:~1!"
    )
)
goto :LOOP
:END
if defined RES (
    for /F delims^=^ eol^= %%S in ("!RES!") do (
        endlocal
        set "%~1=%%S"
    )
) else endlocal & set "%~1="
exit /B


:LEN  rtn_length  ref_string
setlocal EnableDelayedExpansion
set "STR=!%~2!"
if not defined STR (set /A LEN=0) else (set /A LEN=1)
for %%L in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
    if defined STR (
        set "INT=!STR:~%%L!"
        if not "!INT!"=="" set /A LEN+=%%L & set "STR=!INT!"
    )
)
endlocal & set "%~1=%LEN%"
exit /B

Main.Bat:

@ECHO OFF
@setlocal EnableDelayedExpansion

Rem | Configuration
set "CustomINI=File.ini"
set "ListHeader=[Items]"
set "Object=ListOfItems"
set "ReplaceScript=Replace.bat"
SET "ItemList=List.txt"

Rem | Check If "CustomINI" Exists
if not exist "%CustomINI%" (
    echo File "%CustomINI%" Not Found!
    pause
    goto :EOF
)

Rem | Check If "ItemList" Exists
if not exist "%ItemList%" (
    echo File "%ItemList%" Not Found!
    pause
    goto :EOF
)
goto StartFunction

:StartFunction

Rem | Generate the list of items from textfile
FOR /F "delims=" %%A IN (%ItemList%) DO (
    set "ListAddition=!ListAddition!%%A, "
)
set "ListAddition=!ListAddition:~0,-2!"

Rem | Get .ini To String
set HeaderFound=false
FOR /F "tokens=*" %%A IN ('type !CustomINI!') DO (

    Rem | First Find The Header "[Items]" & Extract "ListOfItems" Line Data
    for /f "tokens=*" %%B in ('echo %%A') do (
        set "item=%%B"
        if /i "!item!"=="!ListHeader!" (
            set HeaderFound=true
        ) else if not "!item!"=="!item:ListOfItems=!" if "!HeaderFound!"=="true" (

            Rem | Turn Items For Line "ListOfItems" To String
            for /f "tokens=2,*" %%C in ('echo %%B') do (

               Rem | Set String
               set "SEARCHTEXT=%%D"
            )
            set HeaderFound=false

        )
    )
)

Rem | Check If "ListOfItems" Is Actually Populated
If "%SEARCHTEXT%"=="" (
    Rem | Not Populated
    set "SEARCHTEXT=!Object! = "
    set "REPLACETEXT=!Object! = !ListAddition!"
    goto EditString
) ELSE (
    Rem | Populated
    set "REPLACETEXT=!SEARCHTEXT!, !ListAddition!"
    goto EditString
)

:EditString

Rem | Edit Only "ListOfItems" Line
Rem | Usage: call "1" "2" "3" "4" "5"
Rem | call - Calls external script
Rem | "1" - Name of External script
Rem | "2" - File to Edit
Rem | "3" - Text to replace ex: red apple
Rem | "4" - Text to replace to ex: green apple
Rem | "5" - Output file
call "%ReplaceScript%" "%CustomINI%" "%SEARCHTEXT%" "%REPLACETEXT%" "%CustomINI%.TEMP"

Rem | Delete Original File, Restore New
del "%CustomINI%"
rename "%CustomINI%.TEMP" "%CustomINI%"

goto :EOF

PS - Keep note of the following: The above script expects that when ListOfItems = is not populated, it has a space after the =. If this is not how it is in your .ini file then change set "SEARCHTEXT=!OBJECT! = " to set "SEARCHTEXT=!OBJECT! =" from in the for statement.


EDIT: Since recent requests, The following was updated:

Firstly, since I was unsure of the OP's meaning of ListOfItems = being "Blank", I assumed that he/she was referring to it being ListOfItems =. - Not it being actually missing from the ListHeader it's self. In the example bellow.

My Vision - File.ini:

[Items]
ListOfItems = 
[Armor]
ListOfArmor = Iron Helmet, Iron Brestplate, Iron Pants
[Weapons]
ListOfWeapons = Sword, Dagger, Holy Water

OP's Vision - File.ini

[Items]

[Armor]
ListOfArmor = Iron Helmet, Iron Brestplate, Iron Pants

[Weapons]
ListOfWeapons = Sword, Dagger, Holy Water

Since then, I have now updated the script to find [Items] (String) then add a new line under it. This was done using a script by Magoo.

Since there is nothing to replace, we simply can just "Add" onto the the .ini thus we call a different function.

:EditMissingString

Rem | Export SearchString
echo !ListHeader!>> %~dp0ListHeader.txt

Rem | Add Text Under %ListHeader%
(
    FOR /f "delims=" %%i IN (ListHeader.txt) DO (
    SET AddAfter=%%i

    FOR /f "delims=" %%n IN ('findstr /n "^" %CustomINI%') DO (
        SET line=%%n
        SET line=!line:*:=!

        ECHO(!line!
            IF "!line!"=="!AddAfter!" ECHO(%AddTEXT%
        )
    )
)>>%CustomINI%.TEMP

Rem | Remove ListHeader.txt
DEL %~dp0ListHeader.txt

Rem | Delete Original File, Restore New
DEL %CustomINI%
REN %CustomINI%.TEMP %CustomINI%

goto :EOF

Being that we are no longer editing ListOfArmor = alone but rather adding onto it, we no longer will need the Replace.bat script. I also fixed the original script to properly reserve empty lines!

New replace function W/H Line preserve.

:EditExistingString

REM | Make sure we only edit the ListOfItems line.
FOR /F "delims=" %%n IN ('findstr /n "^" %CustomINI%') DO (
    SET line=%%n
    SET Modified=!line:%SearchText%=%ReplaceText%!
    SET Modified=!Modified:*:=!

    REM | Output the entire edited INI to a temporary file.
    >> %CustomINI%.TEMP ECHO(!Modified!
)

Rem | Delete Original File, Restore New
DEL %CustomINI%
REN %CustomINI%.TEMP %CustomINI%

goto :EOF

Result In:

[Items]
ListOfItems = Item1, Item2, Item3

[Armor]
ListOfArmor = Iron Helmet, Iron Brestplate, Iron Pants

[Weapons]
ListOfWeapons = Sword, Dagger, Holy Water

Result Out:

[Items]
ListOfItems = Item1, Item2, Item3, Item4, Item5, Item6

[Armor]
ListOfArmor = Iron Helmet, Iron Brestplate, Iron Pants

[Weapons]
ListOfWeapons = Sword, Dagger, Holy Water

Final Batch Script:

@ECHO OFF
@setlocal EnableDelayedExpansion

Rem | Configuration
set "CustomINI=File.ini"
set "ListHeader=[Items]"
set "Object=ListOfItems"
SET "ItemList=List.txt"

Rem | Check If "CustomINI" Exists
if not exist "%CustomINI%" (
    echo File "%CustomINI%" Not Found!
    pause
    goto :EOF
)

Rem | Check If "ItemList" Exists
if not exist "%ItemList%" (
    echo File "%ItemList%" Not Found!
    pause
    goto :EOF
)
goto StartFunction

:StartFunction

Rem | Generate the list of items from textfile
FOR /F "delims=" %%A IN (%ItemList%) DO (
    set "ListAddition=!ListAddition!%%A, "
)
if "%ListAddition%"=="" (
    echo ERROR: File "%ItemList%" Is Empty!
    pause
    goto :EOF
) ELSE (set "ListAddition=!ListAddition:~0,-2!")

Rem | Get .ini To String
set HeaderFound=false
FOR /F "tokens=*" %%A IN ('type !CustomINI!') DO (

    Rem | First Find The Header "[Items]" & Extract "ListOfItems" Line Data
    for /f "tokens=*" %%B in ('echo %%A') do (
        set "item=%%B"
        if /i "!item!"=="!ListHeader!" (
            set HeaderFound=true
        ) else if "!HeaderFound!"=="true" (

            Rem | Turn Items For Line "ListOfItems" To String
            for /f "tokens=2,*" %%C in ('echo %%B') do (

               Rem | Set String
               set "SearchText=%%D"
            )
            Rem | Header Was Found, End Loop & goto HeaderContinue
            set HeaderFound=false
            goto HeaderContinue
        )

    )
)
Rem | Header Was Not Found
echo ERROR: The Header "%ListHeader%" Was Not Found!
pause
goto :EOF

:HeaderContinue

Rem | Check If "ListOfItems" Is Actually Populated
If "%SearchText%"=="" (
    Rem | Not Populated
    set "SearchText=!ListHeader!"
    set "AddTEXT=!Object! = !ListAddition!"
    goto EditMissingString
) ELSE (
    Rem | Populated
    set "REPLACETEXT=!SearchText!, !ListAddition!"
    goto EditExistingString
)

:EditExistingString

REM | Make sure we only edit the ListOfItems line.
FOR /F "delims=" %%n IN ('findstr /n "^" %CustomINI%') DO (
    SET line=%%n
    SET Modified=!line:%SearchText%=%ReplaceText%!
    SET Modified=!Modified:*:=!

    REM | Output the entire edited INI to a temporary file.
    >> %CustomINI%.TEMP ECHO(!Modified!
)

Rem | Delete Original File, Restore New
DEL %CustomINI%
REN %CustomINI%.TEMP %CustomINI%

goto :EOF

:EditMissingString

Rem | Add Text Under %ListHeader%
(
    FOR /f "delims=" %%i IN ('Echo !ListHeader!') DO (
    SET AddAfter=%%i

    FOR /f "delims=" %%n IN ('findstr /n "^" %CustomINI%') DO (
        SET line=%%n
        SET line=!line:*:=!

        ECHO(!line!
            IF "!line!"=="!AddAfter!" ECHO(%AddTEXT%
        )
    )
)>>%CustomINI%.TEMP

Rem | Delete Original File, Restore New
DEL %CustomINI%
REN %CustomINI%.TEMP %CustomINI%

goto :EOF

PS: I know your find command is in a different location or something just change the command find to %WINDIR%\System32\FIND.exe in the script.

DEBUG/CHANGES:

Scraped.


For help on any of the commands do the following:

  • call /?
  • set /?
  • for /?
  • if /?
  • find /?
  • So on.
Sign up to request clarification or add additional context in comments.

13 Comments

Thank you for your in-depth response! I've tried your entire project suggestion, as is, but I keep getting the errors FIND: /I': No such file or directory and FIND: ListOfItems': No such file or directory as well as FIND: /I': No such file or directory and FIND: ListOfItems': No such file or directory. I'm going to try and troubleshoot to see if I can't figure out what went wrong, but if you realise something in the meanwhile, please do let me know. Also, I noticed you do SET "SEARCHTEXT=%%C" I thought we assign them a value, where is it getting the value of %%C from?
Also, as per my original post, is it actually possible to take a list of items from a .txt file, where each item exists on a new line, and then put them together separated by commas before appending them to the ListOfItems object? Regarding if the line doesn't exist at all, but we need to add it, what is the best course of action? Do we specify the blank template of ListOfItems = and then add our first item without a comma, and any subsequent ones have commas? What would the logic for that be?
UPDATE: Regarding my first comment, the issue is with another version of FIND being executed. I resolved this by using %WINDIR%\System32\FIND.exe instead. So everything works as per your example, flawlessly. The issue now is that if we have just ListOfItems = as a blank entry, it will override the ENTIRE file with string:=, UserInput string:=, UserInput on two separate lines, UserInput being what was entered. And in the worst case, if the file exists, but is blank, it simply outright deletes the file. I believe the latter can easily be resolved with a IF EXIST "%INTEXTFILE%" check.
@Deadmano I will update my response based on those issues. I completely forgot about checking errors relating to the field being empty.
Hey John! So your edits have helped me greatly in moving forward with this little project! I managed to include items to be added from a text file instead of a user prompt, which I've added as an edit to my original question, as that was something I had asked for. The only thing I am stuck with now, that I can't quite figure out is 1) how can I preserve the blank lines in the INI file, as the batch file strips all empty lines and 2) how can I find the section [Items] in the INI and then append the ListOfItems section to it? I just need to know how to find it, and append directly below it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.