5

I have written a batch file which I want to overwrite key strings with strings from another .txt file.

currently it copies the new File.txt file perfectly but does not replace the strings with the strings from OldFile.txt file.

example of strings in File.txt file:

...

# Password
Pword=

# AccountName
Account=

# TownName
Town=

# Postcode
Postcode=

# LocationChangedDate
LocationChanged=

example of strings in OldFile.txt file I want to replace from:

...

# Password
Pword=ABC

# AccountName
Account=123

# TownName
Town=LDN

# Postcode
Postcode=WS77TP

# LocationChangedDate
LocationChanged=01/01/2015

Can someone please point me in the right direction or explain where I have made a mistake?

@echo off

setlocal disableDelayedExpansion

::Variables
set InputFile=F:\EXCHANGE\3\Machine\File.txt
set OutputFile=F:\EXCHANGE\3\File-New.txt
set CopyFile=F:\EXCHANGE\3\OldMachine\OldFile.txt

set _strFindPword=Pword=.*
for /F "delims=" %%A in ('findstr /x "Pword=.*" %CopyFile%') do set _strInsertPword=%%A

echo.%_strInsertPword%

set _strFindAccount=Account=.*
for /F "delims=" %%B in ('findstr /x "Account=.*" %CopyFile%') do set _strInsertAccount=%%B

echo.%_strInsertAccount%

set _strFindTown=Town=.*
for /F "delims=" %%C in ('findstr /x "Town=.*" %CopyFile%') do set _strInsertTown=%%C

echo.%_strInsertTown%

set _strFindLocationChanged=LocationChanged=.*
for /F "delims=" %%D in ('findstr /x "LocationChanged=.*" %CopyFile%') do set _strInsertLocationChanged=%%D

echo.%_strInsertLocationChanged%

set _strFindPostcode=Postcode=.*
for /F "delims=" %%E in ('findstr /x "Postcode=.*" %CopyFile%') do set _strInsertPostcode=%%E

echo.%_strInsertPostcode%


(
  for /F "delims=" %%L in ('findstr /n "^" "%InputFile%"') do (
    set "line=%%L"
    setlocal EnableDelayedExpansion
    set "line=!line:*:=!"
    if "%%L" equ "_strFindPword" (echo.!_strInsertPword!) else (
       if "%%L" equ "%_strFindAccount%" (echo.!_strInsertAccount!) else (
          if "%%L" equ "%_strFindTown%" (echo.!_strInsertTown!) else (
             if "%%L" equ "%_strFindLocationChanged%" (echo.!_strInsertLocationChanged!) else (
                if "%%L" equ "%_strFindPostcode%" (echo.!_strInsertPostcode!) else (echo.!line!)
             )
          )
       )
    )
    endlocal
  )
) > "%OutputFile%"

del %InputFile% 

ren %OutputFile% File.txt



pause
Community
  • 1
  • 1
  • Wouldn't it be easier to just `copy /y OldFile.txt File.txt`? :) – CristiFati Sep 13 '16 at 13:20
  • @CristiFati the example file.txt only shows a small section of the text that I'm interested in, there is a lot of the strings in the OldFile.txt I don't want copied into File.txt. Keeping empty lines in the file has led me to the long way around. Another constraint is that I used a method that removed the lines from where they were in the file and placed them at the bottom. But I want them to be replaced on the line they are originally on. –  Sep 13 '16 at 13:27
  • I figured that :). One thing that's not yet clear to me is: you want __all__ the string present in both files to be overwritten, or just the 5 mentioned? – CristiFati Sep 13 '16 at 13:29
  • @CristiFati Just the 5 mentioned. The code I have got has been cobbled together from bits I've read. This is only my 3rd batch file I've written so it may be a lot longer than required due to my lack of experience. –  Sep 13 '16 at 13:33

4 Answers4

2

I think I finally got it...

What it does:

  • It goes through the OldFile.txt content, searching for markers, if found they are stored into environment variables to be used in the nest step (e.g. for _PWD marker (variable) which has a value of Pword=, it will create a _PWDCONTENTS variable with the content of Pword=ABC).
  • It goes through File.txt content, searching for the same markers, if one marker found, the corresponding CONTENTS variable is dumped in the OutFile.txt, else the original line. Because that happens in the inner for loop, I had to add some extra logic (the _WROTE var) to avoid writing the same lines more than once.

Notes:

  • It is supposed (well, besides doing what it's supposed to) to be "configurable" (the code is complicated, it's heading towards meta :) if you will), meaning that if there are changes between the markers the code shouldn't change (well there would be code changes, but not in the functional part only in variable definitions). Let me detail:

    • If you no longer need to replace the Town= string, then all you have to do is removing _TOWN from _ALL: set _ALL=_PWD _ACCT _POST _LOC.
    • The reverse: if you want to add some other tag (let's call it Name), you have to create a new environment variable: set _NAME=Name= and add it to _ALL: set _ALL=_PWD _ACCT _TOWN _POST _LOC _NAME.
  • As an indirect consequence, I didn't focus on performance, so it might run slow. Anyway I tried to keep the disk accesses (which are painfully slow) to a minimum (one example is when having 2 for loops the one that iterates on a file contents - assuming that each iteration takes a disk access; this might not be true, and Win has IO buffering - it's the outer one).

  • I "commented" out the last line in the file, to avoid overwriting the original file. If that behavior is needed, simply remove the rem at the beginning.

Here's the batch code:

@echo off
setlocal enabledelayedexpansion

set _INFILE="File.txt"
set _OUTFILE="NewFile.txt"
set _OLDFILE="OldFile.txt"


set _PWD=Pword=
set _ACCT=Account=
set _TOWN=Town=
set _POST=Postcode=
set _LOC=LocationChanged=
set _ALL=_PWD _ACCT _TOWN _POST _LOC

echo Parsing old file contents...

for /f "tokens=*" %%f in ('type !_OLDFILE!') do (
    for %%g in (!_ALL!) do (
        echo %%f | findstr /b /c:!%%g! 1>nul
        if "!errorlevel!" equ "0" (
            set %%gCONTENTS=%%f
        )
    )
)

copy nul %_OUTFILE%
echo Merging the old file contents into the new file...
set _WROTE=0

for /f "tokens=*" %%f in ('findstr /n "^^" !_INFILE!') do (
    set _TMPVAR0=%%f
    set _TMPVAR0=!_TMPVAR0:*:=!
    for %%g in (!_ALL!) do (
        echo !_TMPVAR0! | findstr /b /c:!%%g! 1>nul
        if "!errorlevel!" equ "0" (
            echo.!%%gCONTENTS!>>!_OUTFILE!
            set _WROTE=1
        )
    )
    if "!_WROTE!" equ "0" (
        echo.!_TMPVAR0!>>!_OUTFILE!
    ) else (
        set _WROTE=0
    )
)

rem copy /-y %_OUTFILE% %_INFILE%

@EDIT0: Using @StevoStephenson suggestion (as part of the question snippet), I replaced the (2nd) outer for loop to ('findstr /n "^^" !_INFILE!') in order to include the empty lines, so the 3rd remark no longer applies (deleting). Also did some small changes to allow files that contain SPACE s in their paths.

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • I have given this a run and on first attempt it has removed anything after a white space in a string - I am currently working through it to see if I can make the changes required to retrieve the empty lines and keep the string after the white space. –  Sep 14 '16 at 09:57
  • So, it's the behavior that I described in my 3rd note? – CristiFati Sep 14 '16 at 10:00
  • I noted your 3rd note but it removes any string after a white space on a line. And reading back through my question I have made a small error in my sample text, #Password string should in fact be # Password. All the lines starting with a # should have a space after them before the other text, I will edit the question to reflect. Bu after running your sample that line just becomes #. Because all the text after the whitespace has been removed. –  Sep 14 '16 at 10:10
  • So, the _SPACE_ s are present on both files. Please look for any other errors, as the files format is quite important :). – CristiFati Sep 14 '16 at 10:17
  • I'm done with this change. It was a mistake which was not encountered with the old files format, but only by luck. Should I also look into the empty lines stripping? – CristiFati Sep 14 '16 at 10:31
  • did you add `"delims="` in the `for /f` loops? I have and its resolved the white space issue. I know the `for /f` strips empty lines as read many times in other Q&A and a work around is to use `findstr /n` and to then remove the numbers from the lines in the loop. From my example `set "line=%%L" setlocal EnableDelayedExpansion set "line=!line:*:=!"` does exactly that. –  Sep 14 '16 at 10:36
  • I added `"tokens=*"`. – CristiFati Sep 14 '16 at 10:40
  • Change `for /f %%f in (!_INFILE!) do (` to `for /f "delims=" %%f in ('findstr /n "^" "%_INFILE%"') do (`. Add `set "line=%%f" set "line=!line:*:=!"` in the loop. and then replace `%%f` with `!line!` and it keeps the empty lines. –  Sep 14 '16 at 11:24
  • Sorry, but this code has many limitations. Try to add `\..\..\windows\system32\calc.exe"` into your oldfile.txt. And it fails also with a line like `Hello^ world!`. Read [Batch files: How to read a file?](http://stackoverflow.com/a/4531090/463115) – jeb Sep 14 '16 at 11:44
  • I added the 2 lines you mentioned at the beginning of _OldFile.txt_, and I didn't notice any difference (in the way that it generated _NewFile.txt_ like wo those lines). But I am reading your post and there are a lot of nice things (for error proofing)! – CristiFati Sep 14 '16 at 11:51
  • @jeb I would appreciate a example from yourself to solve my question. Diversity in methods always helps others out. –  Sep 14 '16 at 12:45
  • @CristiFati You should see that `Hello ^ wordl!` is converted to `Hello world` without caret and bang. The `\..\..\windows\system32\calc.exe` starts the calculator, but only when your batch isn't to deep in your directory, else you need a longer prefix like `..\..\..\..\..\..\..\windows\system32\calc.exe` (and you should be on `C:` drive already) – jeb Sep 14 '16 at 13:04
0

Maybe it works like this

set CopyFile=oldfile.txt
set InputFile=newfile.txt

set str_search="Pword"
for /f "delims=" %%i in ('findstr %str_search% %copyfile%') do set str_replace=%%i
set str_replace="%str_replace%"
echo %str_search%
echo %str_replace%
pause

CALL :far %InputFile% %str_search% %str_replace%
EXIT /B 0

:far
setlocal enableextensions disabledelayedexpansion

set "search=%2"
set "replace=%3"
::remove quotes
set search=%search:"=%
set replace=%replace:"=%
echo %search%
echo %replace%

set "textFile=%1"

for /f "delims=" %%i in ('type "%textFile%" ^& break ^> "%textFile%" ') do (
    set "line=%%i"
    setlocal enabledelayedexpansion
    set "line=!line:%search%=%replace%!"
    >>"%textFile%" echo(!line!
    endlocal
)
EXIT /B 0

At for /f "delims=" %%i in ('findstr %str_search% %copyfile%') do set str_replace=%%i you write the line with the variable that has the needed info to str_replace. After that you the program calls an embeded find-and-replace-function (:far) whitch i shemelessly stole from Batch script to find and replace a string in text file without creating an extra output file for storing the modified file This function finds the string "Pword" and replaces it by the line find in the old file.

Attention: This doesn't solve your problem completely since your new file has to be s.th like this.

#Password
Pword

so if you loose the = it works otherwise it doesn't. I hope this helps you with your problem.

Community
  • 1
  • 1
jan-seins
  • 1,253
  • 1
  • 18
  • 31
  • Thank you for the attempt at solving my issue, although I have not tried the solution, I need to keep the `=` in my file. –  Sep 13 '16 at 16:01
  • ok maybe you will find s.th. to keep it in File.txt... When you have keep the = it will turn out like this with the code above: `Pword=ABC=`. This is because iI could net get the = get recognized as part of the string. – jan-seins Sep 13 '16 at 16:03
0

It's not perfect but this may be okay for you:

@Echo Off
Setlocal EnableExtensions DisableDelayedExpansion

(Set InputFile=F:\EXCHANGE\3\Machine\File.txt)
(Set OutputFile=F:\EXCHANGE\3\File-New.txt)
(Set CopyFile=F:\EXCHANGE\3\OldMachine\OldFile.txt)

For /F "Delims=" %%I In (
    'FindStr/B "Pword= Account= Town= LocationChanged= Postcode=" "%CopyFile%"'
    ) Do Set %%I

(For /F "Tokens=1-2* Delims=]=" %%I In ('Find /V /N ""^<"%InputFile%"') Do (
    Echo(%%J|FindStr/B # || (If Defined %%J (Call Echo=%%J=%%%%J%%) Else (
            If "%%J" NEq "" (Echo=%%J=%%K) Else (Echo=)))))>%OutputFile%
Timeout -1
EndLocal
Exit/B

I've left the delete and rename for you to add at the end.

Compo
  • 36,585
  • 5
  • 27
  • 39
  • I have given this a run and has produced some interesting results, it has added empty lines in some places and removed them from others. It has also removed the = sign from empty parameters from the original file.txt.-I'm going to work through this and see what I can find. reading through I'm unsure what `Echo=%%I=%%%%I%%&Echo=` is doing, could you explain? –  Sep 14 '16 at 10:04
  • This works a great over the first draft. But I have a further issue in that if the line starts with # and has a = at any point in the string it removes the = and everything after it. I think this is down to the `delimiter=]=` but if I remove the = from the delimiter then it does not replace the 5 required strings and it adds a = onto the end of every string without a # at the start. any further suggestions? –  Sep 15 '16 at 11:25
  • My solution as you have agreed worked with the supplied data. Since you did not supply the full content of OldFile.txt or File.txt I could have been expected to guess all of the combinations of data you kept from me when formulating my solution. – Compo Sep 15 '16 at 12:59
  • I do apologise for that, the file is quite large so I only put in what I thought was relevant to keep it compact –  Sep 15 '16 at 14:24
0

This solution should be much faster than the other solutions.
It will also preserve empty lines and lines containing ! and ^.

It only needs one findstr call for collecting the old values for all words.
A second findstr determines all lines (by line number) in the infile which needs an update.

@echo off
setlocal EnableDelayedExpansion

set "_INFILE=File.txt"
set "_OUTFILE=NewFile.txt"
set "_OLDFILE="OldFile.txt"

set "_WORDS=Pword= Account= Town= Postcode= LocationChanged="

REM *** get all values for the key words
for /F "tokens=1,* delims==" %%L in ('findstr "!_WORDS!" "!_OLDFILE!"') do (
    for /F %%S in ("%%L") do (
        set "word[%%S]=%%M"
    )
)

REM *** Find all lines which needs an update
set wordIdx=0
for /F "tokens=1,2,* delims=:= " %%1 in ('findstr /n "!_WORDS!" "!_INFILE!"') do (
    set "lines[!wordIdx!].line=%%1"
    set "lines[!wordIdx!].word=%%2"
    set "replace=!word[%%2]!"
    set "lines[!wordIdx!].replace=!replace!"
    set /a wordIdx+=1
)

REM *** copy the infile to the outfile
REM *** Replace only the lines which are marked by line numbers
echo Parsing old file contents...
set nextWordIdx=0
set /a searchLine=lines[!nextWordIdx!].line
set lineNo=0
setlocal DisableDelayedExpansion
(
    for /f "tokens=*" %%L in ('findstr /n "^" "%_INFILE%"') do (
        set "line=%%L"
        set /a lineNo+=1
        setlocal EnableDelayedExpansion
        set "line=!line:*:=!"
        if !lineNo! equ !searchLine! (
            (echo(!line!!lines[0].replace!)
            set /a nextWordIdx+=1
            for /F %%R in ("!nextWordIdx!") do (
                endlocal
                set /a nextWordIdx=%%R
                set /a searchLine=lines[%%R].line
            )
        ) ELSE (
            (echo(!line!)
            endlocal
        )
    )
) > "!_OUTFILE!"
jeb
  • 78,592
  • 17
  • 171
  • 225