Windows batch FOR processing files for strings - windows

I have the following code to determine if a file has a string on the second line (::echo1, ::echo2 and ::echo3). However, when I run my code for some reason in the subroutine :_verify in the IF command inside the FOR command it completely skips the else section of IF that increases the variable %chk% and returns to :_verify, which in turn increases the number of ::echo that it is looking for in the file and searches the same file again (it needs to search each file for all 3). I tried reversing the IF command to a IF NOT and switching the else to be in front and the call :_redeem last but the same error happened. (Note: It did complete call :_verify correctly for the file containing ::echo1). It only checks each file for ::echo1 because it doesn't increase %chk% and go to :_verify again. Instead, it goes directly to goto :eof and returns to :identify to find another file that it would again, only process for ::echo1. I have added comments to help explain my script.
set gt1=1
setLocal EnableDelayedExpansion
::Identifies all files meeting the criteria (name being tmp*.tmp) and sets cap%gt1% equal the filename. Also checks to see if there are no files left (if the filename doesn't exist (i.e. it's blank because there are none left)).
:identify
set chk=1
if %gt1%==4 goto :restore
for %%A in (tmp*.tmp) do (set cap%gt1%=%%A) & call :_verify
if not exist !cap%gt1%! goto :error
goto :identify
:_verify
::Verifies that the specific file it's looking at (set as cap%gt1% in :identify) has the string ::echo1, 2 or 3 as the second line.
if %chk%==4 call :_reserve & goto :eof
for /f "skip=1" %%B in (!cap%gt1%!) do if %%B==::echo%chk% (call :_redeem) else (set /a chk=%chk%+1) & (goto :_verify)
goto :eof
:_redeem
::Renames files that are confirmed to have the string to their string name (minus the ::).
ren !cap%gt1%! echo%chk%.tmp
set /a gt1=%gt1%+1
goto :eof
:_reserve
::Subroutine used to temporairly discard files that do not meet the requirements so they will not be processed again in :identify during loopback.
if not exist temp50 mkdir temp50
move !cap%gt1%! temp50
goto :eof
:restore
::Restores files that were put in :_reserve to their previous location.
if exist %~dp0\temp50 cd temp50 & for %%C in (tmp*.tmp) do move %%C %~dp0 & cd .. & rmdir temp50
pause
:error
::Error in case it can't find all three files containing the strings.
echo Unable to find program files. Please reinstall.
echo.
pause
quit

Instead, it goes directly to goto :eof
This is the case, as you write your code to do so.
You want to write
if %chk%==4 (call :_reserve & goto :eof)
But your code works as
if %chk%==4 call :_reserve
goto :eof
You should avoid using the & separator, better use multiple lines with parenthesis.
for %%A in (tmp*.tmp) do (
set cap%gt1%=%%A
call :_verify
)
....
if %chk%==4 (
call :_reserve
goto :eof
)

Related

Multiple For Loops in Batch Script on same Content

I set up a batch (copy.bat) file in my Windows sendto Directory that is for Copying a bunch of files via rightclicking the directories and choosing sendto "copy.bat".
#echo off
setlocal enabledelayedexpansion
set cnt=0
set cur=0
:files
for /R "%~1" %%F in (*.7z, *.avi) do (set /a cnt+=1)
shift
if "%~1" NEQ "" goto :files
echo !cnt! files found for copying.
echo Processing ...
set "DEST_DIR=E:\"
:while
for /R "%~1" %%F IN (*.7z, *.avi) do (
if exist "%%F" (
set FILE_DIR=%%~dpF
set FILE_INTERMEDIATE_DIR=!%%~dpF:%%~1%=!
xcopy /E /I /Y "%%F" "%DEST_DIR%!FILE_INTERMEDIATE_DIR!"
)
set /a cur+=1
echo !cur!/!cnt! done...
)
shift
if "%~1" NEQ "" goto :while
pause
If I run the first for loop alone it counts the files.
If I run the second loop on its own it copies exatly those files the other loop is supposed to count.
However, both loops in one batch file will somehowe conflict. The Filecount works correctly but the copy process delivers an error: "The Path of is too long". Between "of" and "is" there is an additional Space. The final pause will correctly wait for button press. I assume it has to do something with the %%F variable, but renaming it in one of the loops does not help.
Please change the name of your .bat - copy is a keyword to cmd.
Your problem is that the first routine shifts out all of the parameters, so your second routine has no parameters to deal with.
The easiest way is probably:
set "DEST_DIR=E:\"
call :while %*
pause
goto :eof
:while
for /R "%~1" %%F IN (*.7z, *.avi) do (
...
if "%~1" NEQ "" goto while
goto :eof
which executes the second routine as en internal subroutine (the : in the call is required) providing that routine with a fresh copy of the parameter-list (%*)
You could also consider moving the if "~1"... to the start of the routine (just after the :while), change it to if "%~1" EQU "" goto :eof and change the final statement to goto while.
The label :eof is defined as end-of-file by cmd and should not be declared.

Comparing last modified timestamps to update a file using batch script

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.

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

Using command prompt batch files to move and automatically rename duplicate files

So, here's what I have: I created a small batch file to record the results of a ping test to a text file. What I want to do is run the batch file and move the results log to a specific folder on the Desktop automatically. Then, if the target filename exists, automatically rename accordingly. Meaning, if File1 exists, create File2, File3, so on and so forth. Here's what I have so far:
#echo off
color A
title Ping Test
:A
echo.
cls
ping google.com -n 4 > C:\Users\%username%\Desktop\pingresults.txt
cls
:Question
echo.
echo You can find the results in C:\Users\%username%\Desktop\pingresults.txt
echo Would you like to run this test again? (Y/N)
Set /p Choice=
if %choice% equ y goto :A
if %choice% equ n goto :Results
if %choice% neq y goto :Question
if %choice% neq n goto :Question
:Results
cls
echo Would you like to view the results of the test? (Y/N)
Set /p Choice=
if %choice% equ y goto :OpenResults
if %choice% equ n goto :Close
if %choice% neq y goto :Results
if %choice% neq n goto :Results
:Close
exit
:OpenResults
start C:\Users\%username%\Desktop\pingresults.txt
And the move batch file looks like this:
#echo off
echo.
cd C:\Users\Diesel\Desktop
move pingresults.txt PingResults\
if exist pingresults.txt ren pingresults.txt=+1
Exit.
I don't want to overwrite the existing file, but rename it in succession. I can't find any helpful articles anywhere using only batch files, they all say to use vbs or php, a language I'm not familiar with
To rename files to file1 [file2][...] and move it in a desktop folder:
#ECHO Off &SETLOCAL
FOR %%a IN (*.txt) DO CALL:processFile "%%~a"
goto:eof
:processFile
SETLOCAL
:loop
SET /a fileCounter+=1
SET "fileName=%~n1%filecounter%%~x1"
IF EXIST "C:\Users\%username%\Desktop\%fileName%" GOTO:loop
ECHO MOVE "%~1" "C:\Users\%username%\Desktop\%fileName%"
ENDLOCAL
goto:eof
Look at the output and remove the word echo before move if it looks good.
I have a pair of utility files that do this, so I can call from either command line, or another batch file. This manages several versions of the file in the same folder, and I would call it before moving your new file in.
Usage is:
archivefile 5 "c:\temp\songs" .txt [move]
Where 5 is the number of versions to keep.
The base file name in this example is "c:\temp\songs.txt"
and it creates (in c:\temp) 5 files: songs_1.txt songs_2.txt songs_3.txt songs_4.txt songs_5.txt
If you specify move it will move the original songs.txt otherwise it copies it.
Note, if you're using it from another batch file (which you'll probably want to), you need to use call or it won't return to your original batch file (both batch files will exit instead, which isn't what you want):
call archivefile 5 "c:\temp\songs" .txt
You need the pair of batch files to be either in the current directory, or on the windows path. Now I think about it, archivefile_b.bat could probably be a sub-routine within the main file instead, but it ain't broke, so I'm not going to fix it.
This is archivefile.bat:
#Echo off
rem archivefile.bat - keep a number of archives of a file
rem paramters: n rootfile extension [move|copy]
rem n = number of files to keep.
rem move|copy optional indicator to rename (move) the original file instead of copying it
rem archivefile 5 "c:\temp\songs" .txt [move]
rem
if "%3"=="" goto usage
set _af_n=%1
set _af_root=%2
set _af_ext=%3
set _af_move=copy
set _af_moveX=%4
if "%_af_moveX%"=="move" set _af_move=move
set _af_i=
set _af_j=
rem make sure that the first parameter (n) is numeric
set /A _af_n=%_af_n%
if "%_af_n%"=="0" echo Error: number of copies must be numeric (%1) & goto usage
if not exist %_af_root%%_af_ext% echo Error: cannot locate main file: %_af_root%%_af_ext% & goto error
rem remove the oldest file
if exist %_af_root%_%_af_n%%_af_ext% del %_af_root%_%_af_n%%_af_ext%
rem move up the remainder
for /L %%i in (%_af_n%, -1, 1) do call archivefile_b %%i %_af_root% %_af_ext%
rem copy the original
if "%_af_move%"=="move" goto moveIt
rem move not specified - make a copy
copy %_af_root%%_af_ext% %_af_root%_1%_af_ext%
goto moveCopyDone
:moveIt
rem need filename only for a rename
set _af_destfile=%_af_root%_1%_af_ext%
set _af_destfile=%_af_destfile:"=%
for %%i in (%_af_destfile%) do set _af_destfile=%%~ni%%~xi
ren %_af_root%%_af_ext% %_af_destfile%
:moveCopyDone
dir %_af_root%*%_af_ext%
goto done
:usage
echo usage: archivefile n rootfile extension
echo - n = number of archives to keep
echo - rootfile = the root filename
echo - extension = file extension
echo.
echo example:
echo archivefile 5 "c:\Documents and Settings\gregh\My Documents\Rescued document" .txt
goto done
:error
rem could maybe do something here? naa.
goto done
:done
This is archivefile_b.bat, which manages just one of the files:
rem #Echo off
rem archivefile_b.bat - helper for the archivefile routine
rem -- juggles ONE of the archive files.
rem paramters: n rootfile extension
rem n = the index to move from n to n+1
rem archivefile 5 "c:\temp\songs" .txt
rem therefore renames c:\temp\songs_5.txt to c:\temp\songs_6.txt
set _afb_n=%1
set _afb_root=%2
set _afb_ext=%3
rem make sure that the first parameter (n) is numeric
set /A _afb_n=%_afb_n%
if "%_afb_n%"=="0" echo Error: (archivefile_b) number of copies must be numeric (%1) & goto done
set /A _afb_j=%_afb_n%+1
rem define the file names
set _afb_fn=%_afb_root%_%_afb_n%%_afb_ext%
set _afb_fj=%_afb_root%_%_afb_j%%_afb_ext%
rem remove the quotes
set _afb_fn=%_afb_fn:"=%
set _afb_fj=%_afb_fj:"=%
rem can only supply a filename to the ren command (not path)
for %%i in (%_afb_fj%) do set _afb_fj=%%~ni%%~xi
rem do the rename now
if exist "%_afb_fn%" ren "%_afb_fn%" "%_afb_fj%"
:done

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