Comparing last modified timestamps to update a file using batch script - windows

I want to create a batch file which will read a .log file, then picks up errors from it and append to a .txt file.
But I don't want the batch to rewrite .txt every time, so now I am looking to compare the last modification timestamps of both these files and then append the latest updation only.
Following is the batch file
#echo off
color 3
cls
#FOR %%A IN ("%ProgramFiles(x86)%\Apache Software Foundation\Apache2.2\logs\error.log") DO #(ECHO=%%~tA& set timestamp=%%~tA)
echo %timestamp%
#FOR %%A IN ("D:\error.txt") DO #(ECHO=%%~tA& set timestamp2=%%~tA)
echo %timestamp2%
if %timestamp% gtr %timestamp2% (
set DirToSearch="C:\Program Files (x86)\Apache Software Foundation\Apache2.2\logs"
set LineToRead="error"
pushd %DirToSearch%
for /r %%f in (error.log) do (
For /F "tokens=*" %%l in ('Findstr /L /I %LineToRead% "%%f"') do (
if %%l equ " " (
echo File:"%%f" is Not OK >> D:\FileStatus.txt
) else (
echo Line: %%l>>D:\error.txt
)
)
)
Goto :End
:End
popd
)
pause
exit
Now here I am unable to compare the timestamps
It would be great help if anyone contributes to achieve this.

#echo off
SETLOCAL
set "DirToSearch=C:\Program Files (x86)\Apache Software Foundation\Apache2.2\logs"
set "processedfile=D:\error.txt"
set "LineToRead=error"
pushd %DirToSearch%
COPY "%processedfile%" ".\processedfile.log" >nul 2>nul
for /f "delims=" %%a in ('dir /b /a-d /od *.log') do (
if /i "%%a"=="error.log" goto end
if /i "%%a"=="processedfile.log" goto process
)
:process
for /r %%f in (error.log) do (
For /F "tokens=*" %%l in ('Findstr /L /I /C:"%LineToRead%" "%%f"') do (
if %%l equ " " (
echo File:"%%f" is Not OK >> D:\FileStatus.txt
) else (
echo Line: %%l>>D:\error.txt
)
)
)
:end
del "processedfile.log"
popd
pause
exit
I've removed the fluff from the code.
Note that after an #echo off statement, # at the start of a command is redundant (#command means "don't echo the command)
The setlocal command ensures that changes made to the environment are discarded when the batch terminates.
Note the new position of the quotes - to ensure that the value assigned to the variable does not include any possible stray trailing spaces.
After switching directory, the processed file is copied to the current directory in a unique name (whatever name you choose, as long as it has a .log extension) - this places a copy of the file in the log directory, so that the magic can be performed.
The dir command reports the names of the .log files found; /od provides the list in timestamp order. Therefore, whichever of the two files error.log and processedfile.log occurs first in the list will cause the code to branch to either process to process the data (the processedfile.log file is earlier than the error.log so new data has been added) or end (error.log is earlier, so no new data has been added)
I've made only one minor change to your findstr - added /c: and quoted the target string. This is largely redundant, but if you change the string to include a space, it provides a verbatim target for findstr. The remainder of that processing as as it was as I don't know the exact details of the processing required.
Note that in your code, DirToSearch and LineToRead were being re-assigned within the code block (series of lines enclosed in parentheses). This would not work because the entire code block is parsed then executed, and the parsing process replaces any %var% with its value at parse time. Your code failed to fail - because you were not using a setlocal, once the code had run, the variables remained assigned, and the code on future runs would use the value assigned in the prior run.
Note that : is not required in a goto except in the specific goto :eof which means "go to end-of-file".
Your goto end is redundant, as end directly follows the goto - but again, it fails to fail. A label within a code block terminates the block. In this case, it was irrelevant since nothing important followed the label.
Having reached the label :end, the process now deletes the copy of the processed file and pops the original directory back with a popd instruction.

Related

How to run a batch file with a FOR loop with two file names with two parameters?

I am using an alass tool to synchronize two subtitles. It is simple to use with one file at a time but I want to use it on multiple files using a loop.
The usage of the tool is like this:
alass.bat correct_subtitle.srt incorrect_subtitle.srt output.srt
I want to do a simple for loop with two parameters with this command:
FOR %i IN (*g.srt) DO FOR %n IN (*t.srt) DO alass.bat %i %n %n
The script is working but I want the command works one time with the second file not looping the first file with all the second files.
I want the script to do like this:
C:\Users\user\Downloads\alass-windows64\alass.bat Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt
C:\Users\user\Downloads\alass-windows64\alass.bat Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt
etc.
All the subtitles are in one folder the correct and incorrect subtitles are like this:
Correct sub (Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt)
incorrect sub (Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt)
Correct sub (Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt)
incorrect sub (Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt)
etc.
A solution for revision 17 of the question is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "eol=| delims=" %%I in ('dir *.eng.srt /A-D-L /B /ON 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R "\.S[0123456789][0123456789]*E[0123456789][0123456789]*\."') do call :ProcessFile "%%I"
endlocal
exit /B
:ProcessFile
echo Correct file: %1
set "FileNameBegin=%~n1"
:GetMatchingPart
for %%J in ("%FileNameBegin%") do (
echo %%~xJ| %SystemRoot%\System32\findstr.exe /I /R "^\.S[0123456789][0123456789]*E[0123456789][0123456789]*$" >nul
if errorlevel 1 set "FileNameBegin=%%~nJ" & goto GetMatchingPart
)
for /F "eol=| delims=" %%J in ('dir "%FileNameBegin%.*-FGT.srt" /A-D /B 2^>nul') do (
echo Incorrect file: "%%J"
call alass.bat %1 "%%J" "%%J"
)
goto :EOF
That code was run on a FAT32 drive with following files in current directory:
Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt
Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt
Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt
Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt
Exa..mple!.S01E01.anotherName.DTS-FGT.srt
Exa..mple!.S01E01.name.eng.srt
example.S01E02.anotherName-FGT.srt
example.S01E02.name.eng.srt
The output without really calling alass.bat is:
Correct file: "Batman.Beyond.S01E01.1080p.BluRay.Remux.eng.srt"
Incorrect file: "Batman.Beyond.S01E01.Rebirth.Part.1.1080p.BluRay.x264.DTS-FGT.srt"
Correct file: "Batman.Beyond.S01E02.1080p.BluRay.Remux.eng.srt"
Incorrect file: "Batman.Beyond.S01E02.Rebirth.Part.2.1080p.BluRay.x264.DTS-FGT.srt"
Correct file: "Exa..mple!.S01E01.name.eng.srt"
Incorrect file: "Exa..mple!.S01E01.anotherName.DTS-FGT.srt"
Correct file: "example.S01E02.name.eng.srt"
Incorrect file: "example.S01E02.anotherName-FGT.srt"
The main FOR loop runs in background one more cmd.exe with option /c the command line within ' appended as additional arguments.
The command DIR executed by this second command processor outputs all names of files in current directory matching the wildcard pattern *.eng.srt.
This list is redirected to FINDSTR which filters the list of file names based on the regular expression \.S[0123456789][0123456789]*E[0123456789][0123456789]*\.. So a file name to process must contain a string consisting of
a dot
case-insensitive the letter S
one or more digits in range 0 to 9
case-insensitive the letter E
one or more digits in range 0 to 9
on more dot.
All the file names ending case-insensitive with .eng.srt and matching the regular expression filter criteria are output by FINDSTR to handle STDOUT of background command process and captured by cmd.exe processing the batch file.
The main FOR loop processes the list of file names line by line after the started cmd.exe process closed itself. File names can contain spaces characters which is the reason for using the option delims= to define an empty list of delimiters to turn off the default line splitting behavior on spaces/tabs. File names can start with a semicolon and for that reason the option eol=| is used to define the vertical bar as end of line character which no file name can contain ever. So each file name is assigned completely to the specified loop variable I
For each file name is called the subroutine ProcessFile which first outputs the current file name with correct subtitles.
Next a FOR loop is used to remove from the file name the string after last dot which is the file extension according to the definition of Microsoft. The "file extension" string is tested with FINDSTR on being the part which is used as identifier and also as separator string between film title and the meta data of the film in file name. If regular expression does not return a positive match on the current "file extension" string, the file name is truncated at end by removing the current "file extension".
Finally after one or more loop runs the beginning of the file name is found consisting of film name with zero or more dots inside and the string matched by the regular expression. So the environment variable FileNameBegin is for the four examples:
Batman.Beyond.S01E01
Batman.Beyond.S01E02
Exa..mple!.S01E01
example.S01E02
That string part is now used to find the matching file with incorrect subtitles ending case-insensitive with the string -FGT.srt. That is again done starting one more cmd.exe to run DIR to find that file.
The usage of the command DIR to get a list of matching file names first loaded into memory can be important depending on what alass.bat does with the passed file names. That is important especially on FAT file systems like FAT32 or exFAT which do not store the file names in an local specific alphabetic order. The file tables of the file system can be changed on each call of alass.bat if this batch file modifies the srt files and that is not good on using FOR directly to process the files. It can result in skipping some srt files or processing some srt files more than once or in worst case even in an endless running loop. That is the reason for using DIR executed by a command process in background to always get a list of matching file names which does not change anymore while the main FOR loop as well as the last FOR loop run the commands which perhaps result in changing the file tables of the file system.
That solution is definitely not the fasted possible, but a very fail-safe solution and should work for all film titles and all file systems independent on what alass.bat does as long as this batch file does not change the current directory.
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 /?
dir /?
echo /?
endlocal /?
exit /?
findstr /?
for /?
goto /?
if /?
set /?
setlocal /?
Here's my approach to v17
#ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following setting for the source directory is a name
rem that I use for testing and deliberately includes 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\t w o"
FOR %%s IN (0 1) DO FOR /L %%t IN (0 1 9) DO FOR %%e IN (0 1) DO FOR /L %%f IN (0 1 9) DO (
FOR /f "delims=" %%o IN (
'dir /b /a-d "%sourcedir%\*.S%%s%%tE%%e%%f.*T.srt" 2^>nul'
) DO (
SET "name=%%~no"
FOR /f "tokens=1,2delims=/" %%p IN ("!name:.S%%s%%tE%%e%%f.=/!") DO (
FOR %%b IN ("%sourcedir%\%%p.S%%s%%tE%%e%%f.*g.srt") do ECHO CALL alass.bat "%%~fb" "%%~fo" "%sourcedir%\%%p.S%%s%%tE%%e%%f.%%q.srt"
)
)
)
GOTO :EOF
Always verify against a test directory before applying to real data.
As ever, the generated lines are simply echoed.
Essentially, for each S and E 00..19, locate the "T" file (otherwise, no point) split the name on the .SssEee. string and find the {1}.SssEee.*g.srt file
mix and match the parts.
...but I still have problems understanding the destination filename...
EDIT: New better version
I added a new version that I think is the fastest and most convenient way to solve this problem:
#echo off
setlocal EnableDelayedExpansion
rem Process "correct" files with eng.srt extension
for %%i in (*.eng.srt) do (
echo Correct sub: %%i
set "correct=%%i"
rem Search for the prefix of this file
set "name="
set "prefix="
for %%k in ("!correct:.=" "!") do if not defined prefix (
set "part=%%~k"
set "name=!name!.!part!"
rem Check if this part have the "S##E##" end of prefix format
if "!part:~0,1!!part:~3,1!!part:~6!" equ "SE" ( rem "S__E__" letters and length match
set /A "S=E=0, S=1!part:~1,2!-100, E=1!part:~4,2!-100" 2>nul
if !S! gtr 0 if !E! gtr 0 ( rem Both ## numbers match: end of prefix found
rem Process the companion "incorrect" *FGT.srt file
set "prefix=!name:~1!"
set "name="
for %%n in (!prefix!.*FGT.srt) do (
echo Incorrect sub: %%n
echo/
REM call alass.bat %%i %%n %%n
set "name=%%n"
)
if not defined name (
echo Warning: Incorrect sub not found
echo/
)
)
)
)
if not defined prefix (
echo Warning: Bad filename format
echo/
)
)
EDIT: New version for the last OP´s revision
This Batch file should solve this question:
#echo off
setlocal EnableDelayedExpansion
rem Count files with same prefix
for %%a in (*.srt) do (
set "name=%%a"
set "prefix="
for %%b in ("!name:.=" "!") do (
set "prefix=!prefix!%%~b."
set /A "count[!prefix:-=_!]+=1"
)
)
rem Process pairs of files
for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do (
if %%b equ 2 (
set "right="
for %%c in ("%%a*.srt") do (
if not defined right (
set "right=%%c"
) else (
set "wrong=%%c"
)
)
call alass.bat !right! !wrong! !wrong!
)
)
Accordingly to your description: "The files have different names like this: Correct sub (example.S01E01.name.ybg.srt). Incorrect sub (differentExample.S01E01.anotherName.wrt.srt)." That is: correct and incorrect names of the same set have the second dot-separated token the same, like S01E01 or S01E02 in the examples shown.
The Batch file below solve such problem:
#echo off
setlocal
for %%i in (*g.srt) do for /F "tokens=2 delims=." %%k in ("%%i") do (
for %%n in (*.%%k.*t.srt) do (
call alass.bat %%i %%n %%n
)
)
NOTE: This part of the answer relates to Revision 17 of the question.
I would do it with the following batch-file, assuming that the first .-separated parts up to the S??E?? pattern of the file names of a pair of files are the same:
#echo off
setlocal EnableExtensions DisableDelayedexpansion
rem // Define constants here:
set "_ROOT=%~dp0." & rem // (target directory)
set "_PREF=*" & rem // (prefix of base file names)
set "_MASK=S??E??" & rem // (middle part of file names without `.`)
set "_FILT=S[0123456789][0123456789]E[0123456789][0123456789]"
set "_SUFF1=*g.srt" & rem // (suffix of 1st file name with extension)
set "_SUFF2=*T.srt" & rem // (suffix of 2nd file name with extension)
set "_TOOL=%~dp0alass.bat"
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop over 1st files:
for %%I in ("%_PREF%.%_MASK%.%_SUFF1%") do (
rem // Reset left part of file name, store currently iterated base name:
set "LEFT=" & set "NAME=%%~nI"
setlocal EnableDelayedExpansion
rem // Loop as many times as there are `.`-separated parts in the base name:
for %%K in ("!NAME:.=" "!") do (
rem // Do the following only as long as the left part is still not found:
if not defined LEFT (
rem // Utilise a `for` loop on the base name to yield `~`-modifiers:
for %%L in ("!NAME!") do (
rem /* Split base name into last part and the rest, the latter
rem of which is going to be used for the next iteration: */
endlocal & set "LAST=%%~xL" & set "NAME=%%~nL"
rem // Determine whether the last part matches the given pattern:
cmd /D /V /C echo(!LAST:~1!| findstr /R /X /I /C:"%_FILT%" > nul && (
rem // Match encountered, so store currently processed path:
set "LEFT=%%~nxL"
)
setlocal EnableDelayedExpansion
)
)
)
rem // Procede further only if a suitable left part of file has been found:
for %%L in ("!LEFT!") do endlocal & if not "%%~L"=="" (
rem // Search for respective 2nd file:
for %%J in ("%%~L.%_SUFF2%") do (
rem /* Store names of both 1st and 2nd file, then call the sub-script
rem utilising the second `%`-expansion established by `call` to
rem avoid doubling of `^`-symbols as well as loss of `%`-signs: */
set "FILE1=%%~I" & set "FILE2=%%~J"
call "%_TOOL%" "%%FILE1%%" "%%FILE2%%" "%%FILE2%%"
rem /* Erase 2nd file to prevent reprocessing of same file pairs in
rem case of re-execution of this script (remove `ECHO` first!): */
ECHO del "%%~I"
)
)
)
rem // Return from target directory:
popd
)
endlocal
exit /B
The trick herein is to use the ~-modifiers (namely ~x and ~n in particular) of for-loop meta-variables to split the file names at . from the back within a loop that iterates as many times as there are .-separated parts in the base names.
This approach correctly handles file names with characters !, ^ and %. You can prove that when you create an interim sub-script alass.bat with the following contents:
#echo off
setlocal DisableDelayedExpansion
echo(%0 %*
endlocal
exit /B
In case the tool alass.bat overwrites the original *T.srt files, which is what I assume, the script deletes the *g.srt files (when removing the upper-case ECHO in front of the related command) in order not to reprocess the same pair of files upon re-execution of the script.
NOTE: This part of the answer relates to Revision 9 of the question.
I would do it with the following batch-file:
#echo off
setlocal EnableExtensions DisableDelayedexpansion
rem // Define constants here:
set "_ROOT=%~dp0." & rem // (target directory)
set "_SUFF1=g" & rem // (suffix for base names of 1st files)
set "_SUFF2=T" & rem // (suffix for base names of 2nd files)
set "_MASK=*%_SUFF1%" & rem // (name search pattern for 1st files)
set "_EXT=.srt" & rem // (extensions for 1st and 2nd files)
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop over 1st files:
for %%I in ("%_MASK%%_EXT%") do (
rem // Store base name of currently iterated 1st file:
set "NAME=%%~nI"
setlocal EnableDelayedExpansion
rem /* Build base name of respective 2nd file; temporarily appending `|` to the
rem name (which cannot occur in a file name) ensures to just replace the very
rem last occurrence of the suffix: */
set "REPL=!NAME!|" & set "REPL=!REPL:%_SUFF1%|=%_SUFF2%!"
rem // Skip in case there is no respective 2nd file:
if exist "!REPL!!_EXT!" (
rem /* Call sub-script with 1st and 2nd file as input files and 2nd one also
rem as output file, preventing delayed expansion but utilising the second
rem `%`-expansion phase established by `call` in order to avoid doubling
rem of `^`-symbols as well as loss of `%`-signs: */
REM call "%~dp0alass.bat" "!NAME!!_EXT!" "!REPL!!_EXT!" "!REPL!!_EXT!"
call "%~dp0alass.bat" "%%NAME%%%%_EXT%%" "%%REPL%%%%_EXT%%" "%%REPL%%%%_EXT%%"
rem /* Erase 2nd file to prevent reprocessing of same file pairs in case of
rem re-execution of the script: */
ECHO del "!NAME!!_EXT!"
)
endlocal
)
rem // Return from target directory:
popd
)
endlocal
exit /B
This approach correctly handles file names with characters !, ^ and %. You can prove that when you create an interim sub-script alass.bat with the following contents:
#echo off
setlocal DisableDelayedExpansion
echo(%0 %*
endlocal
exit /B
If you used the commented-out call command line (with the upper-case REM in front), ^-symbols would become doubled and %-signs would become lost.
In case the tool alass.bat (which is assumed to reside in the same location as this script) overwrites the original *T.srt files, which is what I assume, the script deletes the *g.srt files (when removing the upper-case ECHO in front of the related command) in order not to reprocess the same pair of files upon re-execution of the script.

How to make a batch file loop resume from where it was by looping through the files listed on a .txt?

I want to start down mixing hundreds of surround media files in a folder to stereo, but this is a process that will take a lot of time. I'd like to create a batch file that executes my ffmpeg command to this set of files (probably listed in a .txt with dir /s /b) that I can run whenever my PC is on, but also keeps a record of already processed files to be excluded on the next run.
I know I can easily keep track of already processed files by simply adding something like if errorlevel 0 echo "%%~fg">>processed.txt to my loop, but I'm finding it challenging to come up with a way to ignore these files when running the script the next time.
Of course I could always manually edit the file list to be looped and remove the ones already processed, but I wonder if there is a clever way to do it programatically
An example of using a log with findstr. replace the definition of Set "SourceList=%~dp0list.txt" with the filepath of the file used to store the list of files for processing, or modify the for /f loop options to iterate over the output of your Dir command.
#Echo off
If not exist "%TEMP%\%~n0.log" break >"%TEMP%\%~n0.log"
Set "SourceList=%~dp0list.txt"
For /f "usebackq delims=" %%G in ("%sourceList%")Do (
%SystemRoot%\System32\Findstr.exe /xlc:"%%G" "%TEMP%\%~n0.log" >nul && (
Rem item already processed and appended to log. do nothing.
) || (
Rem item does not exist in log. Proccess and append to log.
Echo(Proccess %%G
>>"%TEMP%\%~n0.log" Echo(%%G
)
)
I managed to produce a way that doesn't rely on external tools and executes the job only with for loops. As it was needed to use UTF-8, extra steps were taken to properly revert the codepage after the batch file ended:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "tokens=*" %%G in ('%SystemRoot%\System32\chcp.com') do for %%H in (%%G) do set /A "CodePage=%%H" 2>nul
%SystemRoot%\System32\chcp.com 65001 >nul 2>&1
if not exist "%~dpn0.log" break >"%~dpn0.log"
set "FileList=%~dp0FileList.txt"
set "LogFile=%~dpn0.log"
for /f "usebackq delims=" %%G in ("%FileList%") do (
set "SkipFile="
for /f "usebackq delims=" %%H in ("%LogFile%") do (
if "%%G" == "%%H" (
set "SkipFile=1"
)
)
if not defined SkipFile (
echo --^> Processing file "%%~nG"
rem file is processed
) else (
echo "%%~nG" has already been processed
rem file is not processed
)
)
%SystemRoot%\System32\chcp.com %CodePage% >nul
endlocal
As can be seen, part of this answer is inspired by #T3RROR's!

How to rename multiple images with an incrementing integer?

Let's say I have a couple of images and I need to rename them and on every iteration add an incremented number.
For this situation I have three images no matter how they name is and I want to rename them like this.
1239.jpg => file1.jpg
file.jpg => file2.jpg
image.jpg => file3.jpg
My commands executed in a command prompt window for this task are:
setlocal EnableDelayedExpansion
set filename=file
set counter=1
for /f "usebackq delims=*" %i in ('dir /b *.jpg') do (set /a counter+=1 ren "%i" "%filename%!counter!.jpg")
But this results in the error message Missing operator.
Can anyone help me with this?
The commands SETLOCAL and ENDLOCAL can be used only in a batch file. Please read this answer for details about the commands SETLOCAL and ENDLOCAL. These two commands do nothing on being executed in a command prompt window. It is necessary to start cmd.exe with option /V:ON to use delayed expansion in a command prompt window as explained by the help output on running cmd /? in a command prompt window.
The usage of usebackq requires enclosing the command line to be executed in ` instead of ' as usual. usebackq is mainly used for processing the lines of a text file of which name is specified in the round brackets enclosed in ".
The following command line with the two commands SET and REN is not of valid syntax. The command SET interprets everything after /a as arithmetic expression to evaluate. In this case the expression misses an operator between 1 and ren whereby ren would be interpreted here as name of an environment variable and not as command to execute next after set.
(set /a counter+=1 ren "%i" "%filename%!counter!.jpg")
The valid command line would be:
set /A "counter+=1" & ren "%i" "%filename%!counter!.jpg"
Enclosing the arithmetic expression in double quotes makes it clear for command SET where the arithmetic expression starts and where it ends. The conditional execution operator & is interpreted by Windows command processor before executing the command SET and results in execution of command REN after command SET even on SET would fail to evaluate the arithmetic expression.
A file renaming task done with Windows command processor is no easy to achieve if
the file extension of the files should not change and
files with any name including those with one or more &()[]{}^=;!'+,`~ should be supported and
there can be already files in the directory with one of the new file names.
For testing the batch file below I created first in a directory following files:
file.jpg
file1.jpg
file2.jpg
file3.jpg
file 4.jpg
File8.jpg
hello!.jpg
image.jpg
The directory was on a FAT32 drive. The file systems FAT16, FAT32 and exFAT return a list of matching directory entries not sorted by name as NTFS which means the list output by command DIR in the main FOR loop in code below is in an unsorted and therefore unpredictable order.
It would be of course possible to append the DIR option /ON to get the list of file names ordered by DIR according to name, but in fact that is not real help in this case, especially because of DIR makes a strict alphabetical sort and not an alphanumeric sort.
A strict alphabetic sort returns a list of ten file names as file1.jpg, file10.jpg, file2.jpg, file3.jpg, ..., file9.jpg while an alphanumeric sort returns a list of ten file names as file1.jpg, file2.jpg, file3.jpg, ..., file9.jpg, file10.jpg.
So here is the commented batch file for this file rename task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileName=file"
rem The user can run this batch file with a folder path in which all *.jpg
rem files should be renamed with an incremented number. Otherwise the
rem directory of the batch file is search for *.jpg files to rename.
if not "%~1" == "" (
pushd "%~1" || exit /B
) else (
pushd "%~dp0" || exit /B
)
set "FileCount=0"
set "DelayedLoopCount=0"
set "DelayedRenameCount=0"
rem Remove all existing environment variables in local environment of which
rem name starts with DelayedRename_ whereby the underscore is very important
rem because there is also the environment variable DelayedRenameCount.
for /F "delims==" %%I in ('set DelayedRename_ 2^>nul') do set "%%I="
rem Get a captured list of all *.jpg files in current directory and then
rem rename one file after the other if that is possible on no other file
rem has by chance already the new file name for the current file.
for /F "eol=| delims=" %%I in ('dir *.jpg /A-D /B 2^>nul') do call :RenameFile "%%I"
goto DelayedRenameLoop
:RenameFile
set /A FileCount+=1
set "NewName=%FileName%%FileCount%%~x1"
rem Has the file case-sensitive already the right name?
if %1 == "%NewName%" goto :EOF
rem Is the new file name the same as the current name
rem with exception of the case of one or more letters?
if /I %1 == "%NewName%" (
echo Rename %1 to "%NewName%"
ren %1 "%NewName%"
goto :EOF
)
rem Is there no other file which has already the new name?
if not exist "%NewName%" (
echo Rename %1 to "%NewName%"
ren %1 "%NewName%"
goto :EOF
)
rem Another file or folder has already the new name. Remember the name
rem of this file and the new file name with an environment variable for
rem a delayed rename after all other files have been renamed as far as
rem possible.
set /A DelayedRenameCount+=1
set "DelayedRename_%DelayedRenameCount%=|%~1|%NewName%"
goto :EOF
rem It could happen that "file15.jpg" should be renamed to "file3.jpg"
rem while "file3.jpg" exists already which should be renamed to "file12.jpg"
rem while "file12.jpg" exists already which should be renamed to "file20.jpg".
rem This extra loop is used for such worst case scenarios which is executed
rem in a loop until all files have been renamed with a maximum of 50 loop
rem runs in case of one file cannot be renamed and therefore blocking
rem renaming of another file. An endless running loop should be avoided.
rem A file cannot be renamed if a folder has by chance the new file name.
rem A file cannot be renamed if an application has opened the file with
rem a sharing access mode preventing the rename of the file as long as
rem being opened by this application.
:DelayedRenameLoop
if %DelayedRenameCount% == 0 goto EndBatch
for /F "tokens=1-3 delims=|" %%I in ('set DelayedRename_ 2^>nul') do if not exist "%%K" (
echo Rename "%%J" to "%%K"
ren "%%J" "%%K"
set "%%I"
set /A DelayedRenameCount-=1
)
set /A DelayedLoopCount+=1
if not %DelayedLoopCount% == 50 goto DelayedRenameLoop
:EndBatch
popd
endlocal
This batch file output on execution:
Rename "file3.jpg" to "file4.jpg"
Rename "file 4.jpg" to "file5.jpg"
Rename "File8.jpg" to "file6.jpg"
Rename "hello!.jpg" to "file7.jpg"
Rename "image.jpg" to "file8.jpg"
Rename "file2.jpg" to "file3.jpg"
Rename "file1.jpg" to "file2.jpg"
Rename "file.jpg" to "file1.jpg"
The files in the directory were finally:
file1.jpg
file2.jpg
file3.jpg
file4.jpg
file5.jpg
file6.jpg
file7.jpg
File8.jpg
What about the last file?
It has the file name File8.jpg instead of file8.jpg although executed was ren "image.jpg" "file8.jpg". Well, FAT32 is a bit problematic regarding to updates of the file allocation table on a table entry changes only in case of one or more letters.
The solution is using this batch file with two extra FOR loops with # as loop variable and optimized by removing the comments.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FileName=file"
if not "%~1" == "" (pushd "%~1" || exit /B) else (pushd "%~dp0" || exit /B)
set "FileCount=0"
set "DelayedLoopCount=0"
set "DelayedRenameCount=0"
for /F "delims==" %%I in ('set DelayedRename_ 2^>nul') do set "%%I="
for /F "eol=| delims=" %%I in ('dir *.jpg /A-D /B 2^>nul') do call :RenameFile "%%I"
goto DelayedRenameLoop
:RenameFile
set /A FileCount+=1
set "NewName=%FileName%%FileCount%%~x1"
if %1 == "%NewName%" goto :EOF
if /I %1 == "%NewName%" (
echo Rename %1 to "%NewName%"
ren %1 "%NewName%"
goto :EOF
)
if not exist "%NewName%" (
echo Rename %1 to "%NewName%"
ren %1 "%NewName%"
for %%# in ("%NewName%") do if not "%%~nx#" == "%NewName%" ren "%%~nx#" "%NewName%"
goto :EOF
)
set /A DelayedRenameCount+=1
set "DelayedRename_%DelayedRenameCount%=|%~1|%NewName%"
goto :EOF
:DelayedRenameLoop
if %DelayedRenameCount% == 0 goto EndBatch
for /F "tokens=1-3 delims=|" %%I in ('set DelayedRename_ 2^>nul') do if not exist "%%K" (
echo Rename "%%J" to "%%K"
ren "%%J" "%%K"
for %%# in ("%%K") do if not "%%~nx#" == "%%K" ren "%%~nx#" "%%K"
set "%%I"
set /A DelayedRenameCount-=1
)
set /A DelayedLoopCount+=1
if not %DelayedLoopCount% == 50 goto DelayedRenameLoop
:EndBatch
popd
endlocal
The result of this enhanced batch file is even on FAT32:
file1.jpg
file2.jpg
file3.jpg
file4.jpg
file5.jpg
file6.jpg
file7.jpg
file8.jpg
The reason for using | as string separator on execution of
set "DelayedRename_%DelayedRenameCount%=|%~1|%NewName%"
resulting, for example, in execution of
set "DelayedRename_1=|file.jpg|file1.jpg"
set "DelayedRename_2=|file1.jpg|file2.jpg"
set "DelayedRename_3=|file2.jpg|file3.jpg"
is that the vertical bar is not allowed in a file folder name. So it is a very good character to separate the name of the environment variable with the equal sign appended from current file name and from new file name. This makes it possible to use later delims=| for renaming the file and deleting the environment variable.
See also the Microsoft documentations:
Naming Files, Paths, and Namespaces
Using command redirection operators
The equal sign is allowed in a file name. It is even possible that a *.jpg file has as file name =My Favorite Picute=.jpg which is another reason for using | to get executed for example
set "DelayedRename_4=|=My Favorite Picute=.jpg|file9.jpg"
which later results in assigned DelayedRename_4= to loop variable I, =My Favorite Picute=.jpg to loop variable J and file9.jpg to loop variable K in the FOR loop doing the delayed file renames.
Note: Each FOR loop with '...' in the round brackets results
in starting in background one more command process with %ComSpec% /c '...' and
capturing the output written to handle STDOUT like the output of the cmd.exe internal commands DIR and SET
while cmd.exe processing the batch file waits until started cmd.exe terminated (closed) itself after execution of the command line
and then processing the captured lines one after the other by FOR with ignoring empty lines and lines starting with the defined end of line character after doing the string delimiting which is the reason why eol=| is used on main FOR loop as a file name can start with default end of line character ; and which of course should not be ignored here.
The redirection operator > must be escaped with caret character ^ on those FOR command lines to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir or set command line in the separate command process started in background.
The batch file does not use delayed expansion as this would cause troubles on a file name having one or more exclamation marks which would be interpreted as beginning/end of a delayed expanded environment variable reference on command lines like ren "%%J" "%%K". Therefore a subroutine is used for the main file rename loop on which it is necessary to access the two incremented counter values.
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 /?
dir /?
echo /?
endlocal /?
exit /?
goto /?
if /?
popd /?
pushd /?
rem /?
ren /?
set /?
setlocal /?
I suggest further to look on:
Microsoft documentation for the Windows Commands
SS64.com - A-Z index of Windows CMD commands
Where does GOTO :EOF return to?
Single line with multiple commands using Windows batch file
Symbol equivalent to NEQ, LSS, GTR, etc. in Windows batch files
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Why is no string output with 'echo %var%' after using 'set var = text' on command line?

How to replace segment delimiter or unwrap one-liner using Windows command line?

our system is going to be migrated from Linux to Windows machine so I'm preparing a batch file equivalent to our existing script. I already have created the batch file but I need to unwrap first the file before processing its next line of codes.
Example. Here is a one-liner wherein the delimiter is "{".
Note: Delimiter can be any or variable character except element delimiter ("~" in this case).
ISA~00~ ~00~ ~ZZ~SAMSUNGSND ~14~181087842 ~130214~2300~U~00401~000000003~0~T~>{GS~FA~181087842TEST~SYNNTEST~20130214~2300~810~X~004010{ST~997~131250001{AK1~SC~1809{AK9~A~1~1~1{SE~4~131250001{GE~1~810{IEA~1~000000001
I need it to be unwrapped like this (equivalent to tr "{" "\n" < FileName.txt ):
ISA~00~ ~00~ ~ZZ~SAMSUNGSND ~14~181087842 ~130214~2300~U~00401~000000003~0~T~>
GS~FA~181087842TEST~SYNNTEST~20130214~2300~810~X~004010
ST~997~131250001
AK1~SC~1809
AK9~A~1~1~1
SE~4~131250001
GE~1~810
IEA~1~000000001
EDIT:
Once unwrapped, I need to search fixed values of third field if equal to "1145837" under GS segment (2nd line) and replace it with "1119283" (which is equivalent to sed '/^GS/ s/1145837/1119283/').
Below is my batch file. I need the code to be inserted somewhere inside :WriteToLogFile subroutine
#echo on
::This ensures the parameters are resolved prior to the internal variable
SetLocal EnableDelayedExpansion
rem Get current date and time as local time.
for /f "delims=" %%a in ('wmic OS Get localdatetime ^| %SystemRoot%\System32\Find.exe "."') do set dt=%%a
rem Reformat the date and time strong to wanted format.
set "YYYY=%dt:~0,4%"
set "MM=%dt:~4,2%"
set "DD=%dt:~6,2%"
set "HH=%dt:~8,2%"
set "Min=%dt:~10,2%"
set "Sec=%dt:~12,2%"
set "TimeStamp=%YYYY%-%MM%-%DD%_%HH%-%Min%-%Sec%"
rem Define name of the list file containing current date and time in name.
set "ListFile=FLIST_%TimeStamp%.lst"
rem Change directory (and drive).
cd /D "C:\VP"
rem Create the list file which is good here as the list of files changes
rem while running this batch file and therefore it is better to work with
rem a list file instead of running a FOR directly for each file in the
rem directory. The list file is not included in this list as it either does
rem not exist at all or it has wrong file extension as only *.txt files are
rem listed by command DIR. The log file EDI.log has also wrong file extension.
dir *.txt /A:-D /B /O:D >"C:\VP\TEST\%ListFile%"
rem It might be useful to delete the log file from a previous run.
if exist EDI.log del EDI.log
rem Process each file in the list file.
cd /D "C:\VP\TEST"
for /F "delims=" %%F in ( %ListFile% ) do call :ProcessFile "%%F"
cd /D "C:\VP"
::rem Delete the list file as not needed anymore. It could be also kept.
::del %ListFile%
rem Exit batch file.
endlocal
goto :EOF
:ProcessFile
rem The parameter passed from first FOR is the file name in double quotes.
set "FileName=%~1"
rem Ignore the files CNtable.txt and Dupfile.txt in same directory.
rem Command goto :EOF just exits here from subroutine ProcessFile.
if "%FileName%"=="CNtable.txt" goto :EOF
if "%FileName%"=="Dupfile.txt" goto :EOF
if "%FileName%"=="VanPointAS2in.bat" goto :EOF
if "%FileName%"=="VP.bat" goto :EOF
rem Get 7th, 9th and 14th element from first line of current file.
cd /D "C:\VP"
for /f "usebackq tokens=7,9,14 delims=~*^" %%a in ( "%FileName%" ) do (
set "ISAsender=%%a"
set "ISAreceiver=%%b"
set "ISActrlnum=%%c"
goto WriteToLogFile
)
:WriteToLogFile
rem Remove all spaces as ISAsender and ISAreceiver have
rem usually spaces appended at end according to example
rem text. Then write file name and the 3 values to log file.
set "ISAsender=%ISAsender: =%"
set "ISAreceiver=%ISAreceiver: =%"
set "ISActrlnum=%ISActrlnum: =%"
echo %FileName%,%ISAsender%,%ISAreceiver%,%ISActrlnum%>>"C:\VP\TEST\EDI.log"
set "FLAG=N"
if "%ISAsender%"=="APPLESND" (
if "%ISAreceiver%"=="MANGO" (
set "FLAG=Y"
set "VW=AP"
call :DupCheck
echo %errorlevel%>>"C:\VP\TEST\EDI.log"
if errorlevel 1 move /Y "%FileName%" "APPLE"
echo Moved %FileName% to directory APPLE.
)
)
if "%ISAsender%"=="APPLESND" (
if "%ISAreceiver%"=="MANGOES" (
set "FLAG=Y"
set "VW=AP"
call :DupCheck
echo %errorlevel%>>"C:\VP\TEST\EDI.log"
if errorlevel 1 move /Y "%FileName%" "APPLE"
echo Moved %FileName% to directory APPLE.
)
)
if "%ISAsender%"=="SAMSUNGSND" (
if "%ISAreceiver%"=="MANGO" (
set "FLAG=Y"
set "VW=SS"
call :DupCheck
echo %errorlevel%>>"C:\VP\TEST\EDI.log"
if errorlevel 1 move /Y "%FileName%" "SAMSUNG"
echo Moved %FileName% to directory SAMSUNG.
)
)
rem Move to directory BYPASS if all else not satisfied.
if "%FLAG%"=="N" (
move /Y "%FileName%" "BYPASS"
echo Moved %FileName% to directory BYPASS
)
rem Exit the subroutine WriteToLogFile.
goto :EOF
:DupCheck
rem Check for ISA control number in file %VW%_table.txt.
%SystemRoot%\System32\Findstr.exe /X /M /C:%ISActrlnum% "C:\VP\TEST\%VW%_table.txt" >nul
if errorlevel 1 goto NewControl
rem This ISA control number is already present in file %VW%_table.txt.
echo Duplicate control %ISActrlnum% found in file %FileName%.
echo %ISActrlnum%,%FileName%>>"C:\VP\TEST\Dupfile.txt"
move /Y "%FileName%" "DUPLICATES"
echo Moved %FileName% to directory DUPLICATES.
rem Exit the subroutine DupCheck.
goto :EOF
:NewControl
echo %ISActrlnum%>>"C:\VP\TEST\%VW%_table.txt"
Any help is appreciated.
Manipulating text files with native batch commands is rather tricky, and quite slow. Most tasks can be done, but it requires quite a few advanced batch techniques to make the solution robust.
You will probably be most happy with GnuWin32 - a free collection of unix utilities for Windows. You could then manipulate file content with familiar tools.
Another good alternative (my favorite - no surprise since I wrote it) is to use REPL.BAT - a hybrid JScript/batch utility that performs a regex search/replace operation on stdin and writes the result to stdout. It is pure script that will run natively on any Windows machine from XP forward. Full documentation is embedded within the script.
I recommend replacing your line delimiter with \r\n rather than \n, as that is the Windows standard for newlines.
Assuming REPL.BAT is in your current directory, or somewhere within your PATH, then the following will make your needed changes:
set "file=fileName.txt"
type "fileName.txt" | repl "{" "\r\n" lx >"%file%.new"
move /y "%file%.new" "%file%" >nul
:GS_replace
<"%file%" call repl "^(GS~.*)1145837" "$11119283" >"%file%.new"
set "rtn=%errorlevel%"
move /y "%file%.new" "%file%" >nul
if %rtn% equ 0 goto GS_replace
I'm concerned that your string of numeric digits could be embedded within a larger number, leading to an unwanted substitution. You might want to refine your search term to prevent this.
The following would only replace an entire field:
:GS_replace
<"%file%" call repl "^(GS~(?:.*~)*)1145837(~|$)" "$11119283$2" >"%file%.new"
set "rtn=%errorlevel%"
move /y "%file%.new" "%file%" >nul
if %rtn% equ 0 goto GS_replace
The following would only replace an entire number that may be embedded within a larger alpha-numeric string:
:GS_replace
<"%file%" call repl "^(GS~(?:.*\D)*)1145837(\D|$)" "$11119283$2" >"%file%.new"
set "rtn=%errorlevel%"
move /y "%file%.new" "%file%" >nul
if %rtn% equ 0 goto GS_replace
In your comment below, you say you want to restrict the number change to the 3rd field of GS lines. (This is quite different than what you stated in your original question.) This is much simpler - no loop is required:
type "%file%" | repl "^(GS~(.*?~){2})1145837(~|$)" "$11119283$2" >"%file%.new"
move /y "%file%.new" "%file%" >nul

issues with enabledelayedexpansion for file renaming batch script

i am writing a batch script monotonic file renamer. basically, it makes the titles of all the files 1 2 3 4 .... and so on. i have since expanded it to be able to handle files of different types (txt, doc, flv, etc) but not everything is working out.
my main concern is i have broken the delayed expansion calls i was making before. now using !var1! is never expanded, or never recognized as a variable.
here is a verbosely commented version of my script
::a monotonic file renamer
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET tempfile=temp.txt
SET exttemp=exttemp.txt
if [%1] == [] goto usage
::make sure your dont overwrite something useful
if EXIST %tempfile% (
ECHO Temp file already exists, are you sure you want to delete?
del /P %tempfile%
)
if EXIST %exttemp% (
ECHO EXT Temp file already exists, are you sure you want to delete?
del /P %exttemp%
)
::initialize
SET /a counter=0
SET type=
SET /a ender=%1
::write filenames to tempfile
DIR /B /ON > %tempfile%
::read lines one by one
for /f "usebackq delims=" %%a in (%tempfile%) do (
REM make sure we do not rename any of the working files
if NOT "%%a"=="renamer.bat" (
if NOT "%%a"=="temp.txt" (
if NOT "%%a"=="exttostr.bat" (
SET /a counter+=1
REM get file extension
exttostr %%a > %exttemp%
SET /P type= < %exttemp%
REM housekeeping
del /F %exttemp%
REM rename
ren %%a !counter!.!type!
ECHO Renamed "%%a" to "!counter!.!type!"
)))
REM exit when we have run enough
if "!counter!"=="!ender!" goto exit
)
goto exit
:usage
echo Usage: renamer NUMFILES
:exit
::final housekeeping
DEL temp.txt
the idea is i drop my two files, renamer.bat(this file) and exttostr.bat(helper to get the file extension) into the folder and run it, it will rename files sorted alphabetically from 1 to how ever many files i specify.
when i run the code, it never uses the variables marked for delayed expansion appropriately, always leaving them as "!varname!", so it renames the first file "!counter!.!type!" and throws errors for the rest because there is already a file in the directory with that name.
this brings me to a secondary issue. sorting the dir list alphabetically results in a poor handling of numbered files. for example the list:
"1 7 15 75 120"
is sorted:
"1 120 15 7 75"
i have not been able to find a way around this yet, only that it is indeed the intended result of the dir sort. the only workaround i have is padding numbers with enough zeroes in the front.
thanks in advance for any insight!
everything is sorted but the second problem. i think i have not spoken well. i have this issue when i take IN the directory file names, not when writing out. so they already need to be padded. i has hoping there was some other way to read the directory and have it be sorted appropriately.
the most promising thing i have found is here: http://www.dostips.com/DtCodeBatchFiles.php#Batch.SortTextWithNumbers
#ECHO OFF
if "%~1"=="/?" (
echo.Sorts text by handling first number in line as number not text
echo.
echo.%~n0 [n]
echo.
echo. n Specifies the character number, n, to
echo. begin each comparison. 3 indicates that
echo. each comparison should begin at the 3rd
echo. character in each line. Lines with fewer
echo. than n characters collate before other lines.
echo. By default comparisons start at the first
echo. character in each line.
echo.
echo.Description:
echo. 'abc10def3' is bigger than 'abc9def4' because
echo. first number in first string is 10
echo. first number in second string is 9
echo. whereas normal text compare returns
echo. 'abc10def3' smaller than 'abc9def4'
echo.
echo.Example:
echo. To sort a directory pipe the output of the dir
echo. command into %~n0 like this:
echo. dir /b^|%~n0
echo.
echo.Source: http://www.dostips.com
goto:EOF
)
if "%~1" NEQ "~" (
for /f "tokens=1,* delims=," %%a in ('"%~f0 ~ %*|sort"') do echo.%%b
goto:EOF
)
SETLOCAL ENABLEDELAYEDEXPANSION
set /a n=%~2+0
for /f "tokens=1,* delims=]" %%A in ('"find /n /v """') do (
set f=,%%B
(
set f0=!f:~0,%n%!
set f0=!f0:~1!
rem call call set f=,%%%%f:*%%f0%%=%%%%
set f=,!f:~%n%!
)
for /f "delims=1234567890" %%b in ("!f!") do (
set f1=%%b
set f1=!f1:~1!
call set f=0%%f:*%%b=%%
)
for /f "delims=abcdefghijklmnopqrstuwwxyzABCDEFGHIJKLMNOPQRSTUWWXYZ~`##$*_-+=:;',.?/\ " %%b in ("!f!") do (
set f2=00000000000000000000%%b
set f2=!f2:~-20!
call set f=%%f:*%%b=%%
)
echo.!f1!!f2!!f!,%%B
rem echo.-!f0!*!f1!*!f2!*!f!*%%a>&2
)
this code can sort the filenames with one number in them (i.e. video100.mov is fine, video100video10.mov would break it)
the issue i have is i think adding a call to this helper fn will break it again, so i will be trying to include this in my modified renamer.bat now. any help is appreciated.
Probably the batch for extracting the extension reset the local environment.
But, you don't need it. You may extract the extension with the ~x option. Something similar to this ....
:monotonicrename
set /a counter = 0
for %%a in (%1\*.*) do (
if exist %%~fa (
set /a counter += 1
echo ren %%~fa !counter!%%~xa
)
)
goto :eof
to include leading zeroes in the counter, so that the directory sorts correctly, replace the previous rename command with three lines
set zcounter=0000!counter!
set zcounter=!zcounter:~-4!
echo ren %%~fa !counter!%%~xa
So putting all pieces together, add the monotonicrename function you just created in the batch file that can be as simpler as...
#echo off
setlocal enabledelayedexpansion
call :monotonicrename %1
goto :eof
:monotonicrename
set /a counter = 0
for %%a in (%1\*.*) do (
if exist %%~fa (
set /a counter += 1
set zcounter=0000!counter!
set zcounter=!zcounter:~-4!
echo ren %%~fa !zcounter!%%~xa
)
)
goto :eof
I didn't experience any issues with delayed expansion, everything worked fine for me (except, of course, for the fact that I didn't have the exttostr.bat helper script.)
Anyway, there are several things that could be improved about your script:
You don't need to store the result of DIR into a file to read it afterwards. You can read the output directly in the FOR loop.
You don't need the helper batch script. The extension can be extracted from %%a by using the ~x modifier with the loop variable: %%~xa. You can read more about modifiers by issuing HELP FOR from the command prompt.
The renamer batch file's own name can be referenced in the script as %0. You can apply the ~n modifier where you only need to use the name without the extension. The combined modifier of ~nx will give you the name with the extension.
So, here's how your script might look like with the above issues addressed:
::a monotonic file renamer
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
IF [%1] == [] GOTO usage
::initialize
SET /A counter=0
SET type=
SET /A ender=%1
::read lines one by one
FOR /F "usebackq delims=" %%a IN (`DIR /B /ON`) DO (
REM make sure we do not rename any of the working files
IF NOT "%%~a"=="%~nx0" (
SET /A counter+=1
RENAME "%%~a" "!counter!%%~xa"
ECHO Renamed "%%~a" to "!counter!%%~xa"
)
REM exit when we have run enough
IF "!counter!"=="!ender!" GOTO :EOF
)
GOTO :EOF
:usage
ECHO Usage: %~n0 NUMFILES
As for your secondary issue, it can be easily resolved like this:
Use something like 100000 as counter's initial value. (Use however many 0s you like, but possibly no more than nine.) Add the same value to ender as well.
When renaming files, instead of !counter! use the expression that removes the first character (the 1): !counter:~1! (in fact, this is not about removal, but about extracting a substring starting from the offset of 1, learn more about it with the HELP SET command).
Here's the modified version of the above script:
::a monotonic file renamer
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
IF [%1] == [] GOTO usage
::initialize
SET /A counter=1000
SET type=
SET /A ender=%1
SET /A ender+=counter
::read lines one by one
FOR /F "usebackq delims=" %%a IN (`DIR /B /ON`) DO (
REM make sure we do not rename any of the working files
IF NOT "%%~a"=="%~nx0" (
SET /A counter+=1
RENAME "%%~a" "!counter:~1!%%~xa"
ECHO Renamed "%%~a" to "!counter:~1!%%~xa"
)
REM exit when we have run enough
IF "!counter!"=="!ender!" GOTO :EOF
)
GOTO :EOF
:usage
ECHO Usage: renamer NUMFILES
You can also see that I made some other enhancements, like making sure the file name is enclosed in double quotes, and using GOTO :EOF instead of GOTO exit (:EOF is a special pre-defined label that points at the end of the batch script so you don't need to define your own).

Resources