Batch file Renamer - batch-file

#echo off
ren "(1) Rare Imports Exclusive.txt" "1RareImportsExclusive.txt"
set "replace="unid": 0,"
set "replaced="unid": 1,"
set "source=1RareImportsExclusive.txt"
set "target=1RareImportsExclusive1.txt"
setlocal enableDelayedExpansion
(
for /F "tokens=1* delims=:" %%a in ('findstr /N "^" %source%') do (
set "line=%%b"
if defined line set "line=!line:%replace%=%replaced%!"
echo(!line!
)
) > %target%
ren "1RareImportsExclusive1.txt" "(1) Rare Imports Exclusive.txt"
del "1RareImportsExclusive.txt"
endlocal
I have update my Original Post
I have over 1000 .txt files, and in the content of each file has a UNID code
with random numbers
My goal is to have 1 bat file that can ignore all random numbers and
rename the unid based on the file number
Original files
(1) Rare Imports Exclusive.txt
------- "unid": 2548,"
Outcome files
(1) Rare Imports Exclusive.txt
------- "unid": 1,"
I can only get this to work if I change all unid to 0,
and if I make 1000 .bat files
So I make as many batch files and merge them
This works for now

#ECHO Off
setlocal ENABLEDELAYEDEXPANSION
set "replacemain="unid": "
SET "sourcedir=u:\sourcedir\t w o"
SET "destdir=u:\destdir"
FOR /f "delims=" %%q IN ('dir /b /a-d "%sourcedir%\(*)*.txt"') DO (
(
FOR /f "tokens=1 delims=()" %%j IN ("%%q") DO (
rem %%j now has sequence number
for /F "tokens=1* delims=:" %%a in ('findstr /N "^" "%sourcedir%\%%q"') do (
set "line=%%b"
if defined line IF "%%b" neq "!line:unid=!" CALL :subs %%j
echo(!line!
)
)
)>"%destdir%\%%q"
)
GOTO :eof
:: substitute
:subs
SET "original=%line%"
FOR /L %%s IN (0,1,9) DO set "line=!line:%replacemain%%%s=%replacemain%!"
IF "%original%" neq "%line%" goto subs
set "line=!line:%replacemain%=%replacemain%%1!"
GOTO :eof
As I read it, this problem has nothing to do with renaming files, but changing their contents.
You would need to change the settings of sourcedir and destdir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that they are processed correctly.
So, assuming that the filenames in the source directory follow the pattern (?) filename.txt and the required work is to replace the string "unid": ##, with "unid": ?, (where ## is a random number and ? is the number from the filename...
Grab the filenames to %%q and derive the filenumber to %%j.
Read and regurgitate. Detect whether the line contains unid etc. and further process these using :subs.
In :subs, replace the unid,etc. string followed by a digit by the string without the digit. If any change was made, repeat. Then substitute %%j passed as %1 to the :subs routine.
--- Revision for including subdirectories.
#ECHO Off
setlocal ENABLEDELAYEDEXPANSION
:: Significant part of string
set "replacemain="unid": "
SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
FOR /f "delims=" %%q IN ('dir /b /s /a-d "%sourcedir%\(*)*.txt"') DO (
rem calculate new destination directory
SET "newdest=%%~dpq"
SET "newdest=!newdest:%sourcedir%=%destdir%!"
SET "newdest=!newdest:~0,-1!"
MD "!newdest!" 2>nul
(
FOR /f "tokens=1 delims=()" %%j IN ("%%~nxq") DO (
rem %%j now has sequence number
for /F "tokens=1* delims=:" %%a in ('findstr /N "^" "%%q"') do (
set "line=%%b"
if defined line IF "%%b" neq "!line:unid=!" CALL :subs %%j
echo(!line!
)
)
)>"!newdest!\%%~nxq"
)
GOTO :eof
:: substitute
:subs
SET "original=%line%"
FOR /L %%s IN (0,1,9) DO set "line=!line:%replacemain%%%s=%replacemain%!"
IF "%original%" neq "%line%" goto subs
set "line=!line:%replacemain%=%replacemain%%1!"
GOTO :eof
You would need to change the values assigned to sourcedir and destdir to suit your circumstances. The listing uses a setting that suits my system.
I deliberately include spaces in names to ensure that the spaces are processed correctly.
First, addition of /s in the dir command to cause scanning of subdirectories. This means that %%q will contain the absolute filename of each file that fits the mask (*)*.txt.
Since the destination directory may now change, newdest is calculated by replacing the value of sourcedir with the value of destdir and removing the final \ character since %%~dpq will deliver a \-terminated string.
The directory thus calculated is then created with a md instruction, and any objection from the system (as it's likely the directory already exists) is discarded with 2>nul (send error messages nowhere).
Since %%q originally contained the filename only, we need to specifically select the name and extension of %%q (%%~nxq) when calculating %%j.
Since %%q contains the absolute filename, we remove the %sourcedir% from the file-read loop using %%a.
Finally, the destination file needs to be constructed from the calculated newdest and the name+extension of the sourcefile.
--- Re-revision
#ECHO Off
setlocal ENABLEDELAYEDEXPANSION
:: Significant part of string
set "replacemain="unid": "
SET "sourcedir=u:\your files"
SET "destdir=u:\your results"
:: for tracking directory-changes
SET "lastdir="
SET /a serial=1
FOR /f "delims=" %%q IN ('dir /b /s /a-d "%sourcedir%\(*)*.txt"') DO (
rem use following line to restart serial number for each file
REM SET /a serial=1
if "!lastdir!" neq "%%~dpq" (
rem use following line to restart serial number for each directory
REM SET /a serial=1
rem calculate new destination directory
SET "newdest=%%~dpq"
SET "lastdir=%%~dpq"
SET "newdest=!newdest:%sourcedir%=%destdir%!"
SET "newdest=!newdest:~0,-1!"
MD "!newdest!" 2>NUL
)
(
rem %%j will acquire sequence number for filename pattern (*)*.txt
REM FOR /f "tokens=1 delims=()" %%j IN ("%%~nxq") DO SET "serial=%%j"
rem use following line to select a random serial number for each file
REM CALL :randsn
for /F "tokens=1* delims=:" %%a in ('findstr /N "^" "%%q"') do (
set "line=%%b"
if defined line IF "%%b" neq "!line:unid=!" CALL :subs
echo(!line!
)
)>"!newdest!\%%~nxq"
)
GOTO :eof
:: substitute
:subs
SET "original=%line%"
FOR /L %%s IN (0,1,9) DO set "line=!line:%replacemain%%%s=%replacemain%!"
IF "%original%" neq "%line%" goto subs
set "line=!line:%replacemain%=%replacemain%%serial%!"
SET /a serial+=1
GOTO :eof
:randsn
SET /a serial=%RANDOM%
:: Ignore selections 30000..32767 for even distribution
IF %serial% geq 30000 GOTO randsn
:: Use 10000 for 4-digit maximum, 1000 for 3, 100 for 2
SET /a serial=serial %% 10000
:: Use the following line if 0 is not allowed
IF %serial%==0 GOTO randsn
GOTO :eof
This provides a choice of how the number is calculated.
To activate any particular option, change the REM in the line following the comment to (nothing). It would probably be insane to try to activate more than one option at any one time. To deselect an option, put the REM keyword back in place.
The random option will generate a random 1-4-digit number. There is absolutely no guarantee whatever that the numbers inserted in this case will be unique.
To allow the routine to operate on any file in the tree, simply change the filemask (*)*.txt to some other filemask, noting that the derivation of sequence number for filenames that do not start (number) via %%j would then become nonsense, so the setting of serial from %%j should be de-activated with a rem statement (as posted)

Related

IS this possible to modify multiple variable from same file using a .batch file without effecting other things like space, special char?

I have a file rev.ini having multiple variable to update:
s1=10
s2=20
s3=30
Here I am using separate loop for finding string in a file. there are 3 times loop are running for same file. Is it possible to find these three string in same loop?
#Echo Off
cd /d D:\xyz
setlocal enabledelayedexpansion
set s1=10
set s2=11
set s3=12
set "file=rev.ini"
for /F "tokens=1,* delims==" %%i in ('findstr "s1= " rev.ini') do (
set "versionVar=%%~i"
set "versionVal=%%~j"
set sequence=%s1%
)
for /f "tokens=1,*delims=]" %%i in ('type "%file%" ^| find /v /n "" ^& break^>%file%') do (
set "line=%%j"
if "!line!" == "!versionVar!=!versionVal!" set line=!versionVar!=!sequence!
echo(!line!>>!file!
)
for /F "tokens=1,* delims==" %%i in ('findstr "s2= " rev.ini') do (
set "versionVar=%%~i"
set "versionVal=%%~j"
set sequence=%s2%
)
for /f "tokens=1,*delims=]" %%i in ('type "%file%" ^| find /v /n "" ^& break^>%file%') do (
set "line=%%j"
if "!line!" == "!versionVar!=!versionVal!" set line=!versionVar!=!sequence!
echo(!line!>>!file!
)
for /F "tokens=1,* delims==" %%i in ('findstr "s3= " rev.ini') do (
set "versionVar=%%~i"
set "versionVal=%%~j"
set sequence=%s3%
)
for /f "tokens=1,*delims=]" %%i in ('type "%file%" ^| find /v /n "" ^& break^>%file%') do (
set "line=%%j"
if "!line!" == "!versionVar!=!versionVal!" set line=!versionVar!=!sequence!
echo(!line!>>!file!
)
Goto :EOF
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory and filename are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q74276740.txt"
SET "outfile=outfile.txt"
set "s1=10"
set "s2=11"
set "s3=12"
REM (
FOR /f "usebackqtokens=1*delims==" %%b IN ("%filename1%") DO (
SET "#keep=Y"
FOR /f "tokens=1*delims==" %%u IN ('set') DO (IF /i "%%b"=="%%u" SET "#keep="&ECHO %%b=%%v)
IF DEFINED #keep IF "%%c"=="" (ECHO %%b) ELSE (ECHO %%b=%%c)
)
REM )>"%outfile%"
GOTO :EOF
Always verify against a test directory before applying to real data.
Note that if the filename does not contain separators like spaces, then both usebackq and the quotes around %filename1% can be omitted.
Simply read each line. If the line contains = and the part before the = contains any variablename in the environment (I made it case-insensitive with /i) then generate a line using the matching value. If #keep remains set after the for..%%u has been executed, then there is no match, so either reproduce the original x=y or the original line, if it had no =.
The output file can be generated by removing the rem before the ( and ).
The output file must be different from the input file, and should be moved over the input file when the batch finishes (not shown)
--- After comment ---
So, if you follow the instructions provided in the last two paragraphs, you get
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory and filename are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q74276740.txt"
SET "outfile=outfile.txt"
set "s1=10"
set "s2=11"
set "s3=12"
(
FOR /f "usebackqtokens=1*delims==" %%b IN ("%filename1%") DO (
SET "#keep=Y"
FOR /f "tokens=1*delims==" %%u IN ('set') DO (IF /i "%%b"=="%%u" SET "#keep="&ECHO %%b=%%v)
IF DEFINED #keep IF "%%c"=="" (ECHO %%b) ELSE (ECHO %%b=%%c)
)
)>"%outfile%"
move "%outfile%" "%filename1%"
GOTO :EOF
== revision of processing section in light of new requirement to retain empty lines ==
REM (
FOR /f "tokens=1,2*delims=:=" %%g IN ('findstr /n ^^^^ "%filename1%"') DO IF "%%h"=="" (ECHO.) ELSE (
SET "#keep=Y"
FOR /f "tokens=1*delims==" %%u IN ('set') DO (IF /i "%%h"=="%%u" SET "#keep="&ECHO %%h=%%v)
IF DEFINED #keep IF "%%i"=="" (ECHO %%h) ELSE (ECHO %%h=%%i)
)
REM )>"%outfile%"
Once again, the rem keywords need to be removed to output to the nominated file.
The changes simply feed the file through a findstr command which produces a listing of the file with a prefix of linenumber:. The extra : in the delims causes the line number to be parsed to %%g. I've changed the metavariable letters because three are required for the processing, and I prefer to not use letters that are also modifiers.
If findstr produces only a line number then it was a blank line, so %%h will be empty and we simply produce an empty line, otherwise, processing as before.

batch command to search and output the content between the first and last occurrence of the search key

I have a log file and I have a keyword to search the log file. Now I have to extract all the contents from that log file between the first and the last occurrence of the search key. ie everything in between the first and last occurrence. the findstr command only lists out the lines that are having the search key and not the other contents in between. I need batch commands to do that.
eg.
log.txt
[mave123]sddasnsdaskdasddansnmdmsmdasdasda
[mave123]dfdfdf
fsffasf
safaaf
fasfssfdfdsfdsf
[mave123]dfsfsdfsdfssdfssfd
[mave124]rdfsdfsfsfsf
[mave124]dfdfsdfsfsdfs
now the "findstr mave123 log.txt" will return as below
[mave123]sddasnsdaskdasddansnmdmsmdasdasda
[mave123]dfdfdf
[mave123]dfsfsdfsdfssdfssfd
but I want all the contents between the fist and last occurrences of mave123. ie like this. Can you please help?
[mave123]sddasnsdaskdasddansnmdmsmdasdasda
[mave123]dfdfdf
fsffasf
safaaf
fasfssfdfdsfdsf
[mave123]dfsfsdfsdfssdfssfd
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "filename1=%sourcedir%\q35976147.txt"
SET "outfile=%destdir%\outfile.txt"
SET "target=%~1"
IF NOT DEFINED target GOTO :eof
SET "startline="
FOR /f "delims=:" %%a IN ('findstr /N /L /c:"%target%" "%filename1%" ') DO (
IF NOT DEFINED startline SET /a startline=%%a
SET /a endline=%%a
)
IF NOT DEFINED startline ECHO NOT found&GOTO :EOF
(
FOR /f "tokens=1*delims=:" %%a IN ('findstr /N /r "." "%filename1%" ') DO (
IF %%a geq %startline% (
IF %%a leq %endline% (ECHO %%b) ELSE (GOTO nextstep)
)
)
)>"%outfile%"
:nextstep
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I used a file named q35976147.txt containing your data for my testing.
Produces the file defined as %outfile%
The first for prefixes each line number matching the target string with linenumber: then assigns the first line number found to startline and surprisingly the last to endline
The second for repeats the operation, this time outputting the lines in the range selected.
This will suppress any leading : on lines. I haven't tested blank lines.
Revision for "all matching files"
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "target=%~1"
IF NOT DEFINED target GOTO :eof
FOR %%j IN ("%sourcedir%\log_*.txt".) DO (
ECHO %%j
SET "startline="
FOR /f "delims=:" %%a IN ('findstr /N /L /c:"%target%" "%%j" ') DO (
IF NOT DEFINED startline SET /a startline=%%a
SET /a endline=%%a
)
IF DEFINED startline (
SETLOCAL enabledelayedexpansion
(
FOR /f "tokens=1*delims=:" %%a IN ('findstr /N /r "." "%%j" ') DO (
IF %%a geq !startline! IF %%a leq !endline! (ECHO %%b)
)
ENDLOCAL
)>"%destdir%\%%~nj.out"
) ELSE (
ECHO "%target%" NOT found IN %%j
)
)
GOTO :EOF
Here, %%j receives the name of the file to be processed. The destination file is constructed from the destination directory name, the name part of the source file (%%~nj) and .out A SETLOCAL/ENDLOCAL bracket is established to allow the run-time values of startline and endline to be accessed.
always best to state the complete problem as it often radically changes the approach.

Find & Replace string using for /f with if statement and variables

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
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.
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.
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.
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!"

Extract substring from filename and count

Example below - 5 files will be located in the same folder.
Sales-fid1000-f100.dat
Revenue-fid1000-f100.dat
Sales-fid2000-f200.dat
Revenue-fid2000-f200.dat
Income-fid2000-f200.dat
I need to read the filename and get the number after "fid", in this case 1000 and 2000 and count the number of files associated with each "fid".
So for fid1000, there are 2 files and for fid2000, there are 3 files.
I need to write the output into a .txt file as below with first field being the fid number and second field being the count.
1000|2
2000|3
How can I generate output text file with fid and count using a Windows batch file?
#echo off
setlocal EnableDelayedExpansion
rem Process all file names
for /F "tokens=2 delims=-" %%a in ('dir /B /A-D *.dat') do (
rem Get FID from second dash-delimited token; format: "xxx-fid####-xxx.dat"
set "fid=%%a"
rem Accumulate it to the corresponding element of "count" array
set /A "count[!fid:~3!]+=1"
)
rem Create the output
(for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do echo %%a^|%%b) > output.txt
For further details on array management in Batch files, see: Arrays, linked lists and other data structures in cmd.exe (batch) script
#ECHO OFF
SETLOCAL
:: remove variables starting $
FOR /F "delims==" %%a In ('set $ 2^>Nul') DO SET "%%a="
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
SET "outfile=%destdir%\outfile.txt"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*-fid*" '
) DO (
SET "filename=%%a"
CALL :process
)
(
FOR /F "tokens=1,2delims=$=" %%a In ('set $ 2^>Nul') DO ECHO(%%a^|%%b
)>"%outfile%"
GOTO :EOF
:process
SET "filename=%filename:*-fid=%"
FOR /f "delims=-" %%q IN ("%filename%") DO SET /a $%%q+=1
GOTO :eof
You would need to change the settings of sourcedir and destdir to suit your circumstances.
Produces the file defined as %outfile%
After clearing all the $ variables (for safety's sake), perform a directory listing without directorynames and in basic form of files in the source directory matching *-fid*.
For each name found, assign the name to filename and execute the :process routine, which first removes the characters up to and including -fid from filename then uses the delims=- option to assign the part originally between -fid and the following - to %%q.
setthe variable $%%q up by 1 (if $?? is undefined, assign 1)
Finally, when all the names have been processed, list the variables named $... using set which produces a report of the style
$1000=2
$2000=3
Using $ and = as delimiters puts token 1 (eg 2000) into %%a and token 2 (eg 3) into %%b. Write these to the output using echo, remembering to escape the pipe (|) with a caret (^) to suppress the interpretation as a redirector.
The parentheses around the for...$... ensures the output is directed to the destination file specified.
Extract the numbers into a temporary file, then count the occurrences of each number in that file.
#echo off
setlocal EnableDelayedExpansion
>temp.txt type nul
set "unique_num="
for /f "tokens=2 delims=-" %%a in ('dir /b *.dat') do (
set "fid=%%a"
set "num=!fid:~3!"
>>temp.txt echo !num!
echo " !unique_num! " | find " !num! " >nul
if !errorlevel! neq 0 set "unique_num=!unique_num! !num!"
)
for %%n in (%unique_num%) do (
for /f "delims=: tokens=2" %%c in ('find /c "%%n" temp.txt') do (
set "count=%%c"
echo %%n^|!count: =!
)
)
del /f /q temp.txt
Pipe the result into sort if you need the output sorted.

compare file content in folder using windows batch

I've been struggling with this for several days... there is one folder with a lot txt files with random names that are generated from server timestamps, but content of files must not be identical for two files in that folder! any ideas? my only option is using windows batch
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
PUSHD "%sourcedir%"
FOR %%a IN (*.*) DO (
FOR %%c IN (*.*) DO IF /i "%%~nxa" lss "%%~nxc" IF "%%~za"=="%%~zc" (
FC "%%a" "%%c" >NUL
IF NOT ERRORLEVEL 1 ECHO "%%a" and "%%c" are identical
)
)
GOTO :EOF
You would need to change the setting of sourcedir and of the filemask *.* to suit your circumstances.
Revision for only-one-mention-of-a-duplicate-file
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
PUSHD "%sourcedir%"
FOR %%a IN (*.*) DO (
SET "reported="
FOR %%c IN (*.*) DO IF NOT DEFINED reported IF /i "%%~nxa" lss "%%~nxc" IF "%%~za"=="%%~zc" (
FC /b "%%a" "%%c" >NUL
IF NOT ERRORLEVEL 1 ECHO "%%a" and "%%c" are identical&SET reported=Y
)
)
GOTO :EOF
I've also added /b to fc to allow for non-text files.
The solution below process the list of file names just once, so it should run faster.
#echo off
setlocal EnableDelayedExpansion
for %%a in (*.txt) do (
if not defined size[%%~Za] (
set size[%%~Za]="%%a"
) else (
set newName="%%a"
for %%b in (!size[%%~Za]!) do (
fc "%%a" %%b >NUL
if not errorlevel 1 (
echo "%%a" and %%b are identical
set "newName="
)
)
if defined newName set "size[%%~Za]=!size[%%~Za]! !newName!"
)
)
If two files are identical, the name of the second one is not saved in the lists, so it is not compared again vs. other same size files. If no more than two files may be identical, then this method could be modified so the name of the first file be also removed from the lists (below the echo ... are identical command), so the method be even faster.
If you may download a third party program that calculate the MD5 checksum, then it may be used to check if two files are identical instead of fc command as foxidrive suggested. This would be faster because the MD5 checksum of each file would be calculated just once and stored in another array (with the file name as index).
As this code uses certutil, this will work only for windows Vista or later versions of the OS. This will check for duplicates in files of the same size and will only read each involved file only once.
#echo off
rem Configure environment
setlocal enableextensions disabledelayedexpansion
rem Where to search for files
set "folder=%cd%"
rem We need a temporary file to hold the size sorted list of files
set "tempFile=%temp%\%~nx0.%random%%random%%random%.tmp"
rem Change to target folder and work from here
pushd "%folder%"
rem Retrieve the list of files with its size and set a environment variable
rem named as the size of the file. The value of this variable will hold the
rem number of files with this size
(for /f "delims=" %%a in ('dir /a-d /b /os *') do (
echo \%%~za\%%a\
set /a "sz_%%~za+=1"
)) >"%tempFile%"
rem Retrieve the list of sizes that happens more than one time
for /f "tokens=2,3 delims=_=" %%a in ('set sz_') do if %%b gtr 1 if %%a gtr 0 (
rem Retrive the list of files with the indicated size
setlocal
for /f "tokens=1,2 delims=\" %%c in ('findstr /l /b /c:"\%%a\\" "%tempFile%"') do (
set "hash="
for /f "skip=1 delims=" %%e in ('certutil -hashfile "%%d"') do if not defined hash (
rem For each file, compute its hash. This hash is used as a variable name.
rem If the variable is defined, a previous file has the same size and hash
rem so it is a duplicate
set "hash=1"
if defined "%%e" (
<nul set /p ".=%%d = "
setlocal enabledelayedexpansion
echo(!"%%e"!
endlocal
) else (
rem Store the name of the file in a variable named as the hash of the file
set ""%%e"=%%d"
)
)
)
endlocal
rem This inner setlocal/endlocal ensures there is no collision between hashes for
rem files with different sizes
)
rem Cleanup
popd
del /q "%tempFile%" >nul 2>nul
endlocal
edited For a simplified version with no temporary file (the list is created in memory) while still reading only the needed files only once each file, AND as demanded a more readable output
edited again to correct a problem with the output of different groups of duplicated for the same file size
#echo off
setlocal enableextensions disabledelayedexpansion
set "folder=%~1"
if not defined folder set "folder=%cd%"
pushd "%folder%"
for /f "delims=" %%a in ('dir /a-d /b /os *') do (
set /a "sz_%%~za+=1"
setlocal enabledelayedexpansion
for /f "delims=" %%b in ("!fl_%%~za! ") do (endlocal & set "fl_%%~za=%%b "%%a"")
)
for /f "tokens=2,3 delims=_=" %%a in ('set sz_') do if %%b gtr 1 (
setlocal & setlocal enabledelayedexpansion
for /f "delims=" %%c in ("!fl_%%a!") do (
endlocal
for %%d in (%%~c) do (
if %%a equ 0 ( set "hash=0" ) else (
set "hash="
for /f "skip=1 delims=" %%e in ('certutil -hashfile "%%~d"') do if not defined hash set "hash=%%e"
)
setlocal enabledelayedexpansion
for /f "delims=" %%e in ("!hash!") do if defined hash_"%%~e" (
for /f "delims=" %%z in ("!hash_"%%~e"!") do (endlocal & set "hash_"%%~e"=%%z"%%~d";")
) else (
endlocal & set "hash_"%%~e"="%%~d"="
)
)
)
for /f "tokens=1,* delims==" %%c in ('set hash_ 2^>nul^|find ";"') do (
set "first=1"
for %%e in (%%d) do if defined first (set "first=" & echo(%%e) else (echo( = %%e)
)
endlocal
)
popd
endlocal
exit /b

Resources