Back in DOS 5 (LOL) I thought I understood batch files, but I'm at a loss.
I have a series of files:
disc51.mp3
disc52.mp3
disc53.mp3
disc54.mp3
disc55.mp3
disc56.mp3
disc57.mp3
disc58.mp3
disc59.mp3
disc510.mp3
disc511.mp3
...etc
I need them to be renamed:
disc501.mp3
disc502.mp3
disc503.mp3
disc504.mp3
disc505.mp3
disc506.mp3
disc507.mp3
disc508.mp3
disc509.mp3
disc510.mp3
disc511.mp3
So I need to only rename the first 9 files in the sequence and do so by adding a '0' between char 5 and 6. How do I do this?
This is my first stab which assumes that each file will begin with 'disc5'. However, I'd like something more generic, ie. that would work properly regardless of the initial naming convention. The only thing I'd like to assume is that after that 'name' the numbers would be 1,2,3,4,5,6,7,8,9,10,11,12, etc.
echo off
cls
setlocal enabledelayedexpansion
FOR %%G IN (*.MP3) DO ( call :strlen %%G)
exit /b
)
:strlen
set myvar=%~n1
rem now compute the length of the string
set #=!myvar!
set length=0
:loop
if defined # (
rem shorten string by one character
set #=!#:~1!
rem increment the string count variable %length%
set /A length += 1
rem repeat until string is null
goto loop
)
rem assuming file name starts with disc. I'd like to make this a more general case
if %length%==6 ren %1 !myvar:~0,5!0!myvar:~5,1!.mp3
exit /b
#echo off
for /L %%i in (1,1,9) do ren "%1%%i.mp3" "%10%%i.mp3"
Previous Batch file requires the initial name in the first parameter, for example:
test.bat disc5
You may also use previous method directly in the command-line:
for /L %i in (1,1,9) do ren "disc5%i.mp3" "disc50%i.mp3"
Related
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.
In Windows batch, I have a for loop like so:
for /l %%a in (0,1,337) do (
for /F "tokens=*" %%b IN ("tile%%a.jpg") DO set size=%%~zb
if !size! GTR 0 (
echo Size is greater than 0
) ELSE (
)
)
I know this code doesn't make much sense right now, but I'm going to develop it further. I just want to know how to subtract 1 from %%a in the ELSE statement. Basically I want to be able to "redo" a loop number when the IF isn't true, if that makes sense. Thanks.
You can't modify the value of a loop variable. You can only modify the value of an environment variable.
But why using for /L %%a in (0,1,337) do at all?
Better would be for example:
#echo off
for %%A in (tile*.jpg) do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo File size of %%A is greater than 0.
)
)
This loop processes simply all tile*.jpg in current directory.
But this loop can't be used if files with 0 bytes are deleted in current directory. Processing the list of tile*.jpg files in current directory and change the files list in the same loop is no good idea because simply not working. The solution is using command DIR to get first the list of all files matching the file name pattern and next process the output of DIR line by line using FOR.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%A in ('dir /A-D /B /OS tile*.jpg 2^>nul') do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo First file with more than 0 bytes is: %%A
goto ExitLoop
)
)
:ExitLoop
endlocal
The command DIR is executed to output the list of files matching the pattern tile*.jpg with ignoring directories which by chance would be matched also by this wildcard pattern because of option /A-D in bare format (only file name) because option /B in order sorted by file size because of option /OS from smallest to largest file.
2^>nul redirects the error message output by command DIR to handle STDERR on not finding any file matching the wildcard pattern to device NUL to suppress this error message. The redirection operator > must be escaped here with caret character ^ to be interpreted as literal character on parsing the FOR command line and interpreted as redirection operator on execution of DIR command line by FOR.
The loop is immediately exited once a file with more than 0 bytes is found as all further files have surely also more than 0 bytes.
One more loop can be used after label ExitLoop which should be renamed to something more suitable in this case for example to renumber the remaining files using command REN when first loop deletes files with 0 bytes.
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.
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
setlocal /?
See also the Microsoft article Using command redirection operators for an explanation of 2>nul.
You cannot modify the loop variable %%a. Only the loop itself can modify it.
If you want to calculate a new value you can do:
set /A NEW_VALUE=%%a-1
echo %NEW_VALUE% (prove that NewValue is now 1 smaller than %%a)
You cannot modify a for variable reference like %%a, but you can store its value into a standard environment variable (like index) and modify this. For this to work you need to enable and use delayed expansion, because the variable is modified and read within the same block of code, namely the loop body, so read it like !index!; using normal expansion like %index% returned the value present before the loop has even started:
#echo off
setlocal EnableDelayedExpansion
for /L %%a in (0,1,337) do (
set /A "index=%%a-1"
echo %%a - 1 = !index!
)
endlocal
A nice alternative that avoids need of delayed expansion is to use an embedded for /F loop that gets the output of the subtraction and iterates once only per iteration of the surrounding for /L loop, like this:
#echo off
for /L %%a in (0,1,337) do (
for /F %%b in ('set /A "%%a-1"') do (
echo %%a - 1 = %%b
)
)
This works because the for /F loop executes the set /A command in cmd context, in which it returns the resulting value -- in contrast to the aforementioned approach, where set /A is executed in batch-file context, in which it does not output anything.
I am trying to do some string comparison and extraction in a batch file. The operation takes place on a set of folder names from a SVN repository.
for /f %%f in ('svn list https://dev_server/svn/product/branches') do (
set folder=%%f
echo Folder: %folder%
:: get substring from %folder% starting at 0 with a length of %length%
:: if this substring is equal to %folderStart% then get substring from %folder% starting at position %length%
)
There are a couple of problems here:
The value of %%f is not assigned to %folder% for some reason.
Even though I have searched the web extensively, I didn't find a solution to doing substring with variable length. The batch file substring function :~ only seems to take fixed integer values.
Does anyone have an idea how I could implement the functions in the commented section in the code above?
Dynamic substring is easy with delayed expansion.
setlocal enableDelayedExpansion
set "string=1234567890"
::using a normal variable
set len=5
echo !string:~0,%len%!
::using a FOR variable
for /l %%n in (1 1 10) do echo !string:~0,%%n!
It can also work with search and replace
Your line
echo %folder%
needs to be
echo !folder!
Hopefully the following shows hows to do the substring
#echo off
setlocal ENABLEDELAYEDEXPANSION
set theString=abcd
for %%f in (1 2 3 4) do (
set pos=%%f
call :Resolve theString:~0,!pos!
echo !retval!
)
goto :eof
:Resolve
for %%a in ("^!%*^!") do set retval=%%~a
goto :eof
which gives
a
ab
abc
abcd
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).
I have as command-line parameters to my batch script a list of filenames and a folder. For each filename, I need to print all subfolders of the folder where the file is found (the path of that file). The subfolder names should be sorted in descending order of the file sizes (the file can have various sizes in different subfolders).
I have done this so far, but it doesn't work:
::verify if the first parameter is the directory
#echo off
REM check the numbers of parameters
if "%2"=="" goto err1
REM check: is first parameter a directory?
if NOT EXIST %1\NUL goto err2
set d=%1
shift
REM iterate the rest of the parameters
for %%i in %dir do (
find %dir /name %i > temp
if EXIST du /b temp | cut /f 1 goto err3
myvar=TYPE temp
echo "file " %i "is in: "
for %%j in %myvar do
echo %j
echo after sort
du /b %myvar | sort /nr
)
:err1
echo Two parameters are necessary
goto end
:err2
echo First parameter must be a directory.
goto end
:err3
echo file does not exist.
goto end
:end
I don't feel guilty answering this homework question now that the semester is long past. Print folders and files recursively using Windows Batch is a closed duplicate question that discusses the assignment.
My initial solution is fairly straight forward. There are a few tricks to make sure it properly handles paths with special characters in them, but nothing too fancy. The only other trick is left padding the file size with spaces so that SORT works properly.
Just as in the original question, the 1st parameter should be a folder path (.\ works just fine), and subsequent arguments represent file names (wildcards are OK).
#echo off
setlocal disableDelayedExpansion
set tempfile="%temp%\_mysort%random%.txt"
set "root="
for %%F in (%*) do (
if not defined root (
pushd %%F || exit /b
set root=1
) else (
echo(
echo %%~nxF
echo --------------------------------------------
(
#echo off
for /f "eol=: delims=" %%A in ('dir /s /b "%%~nxF"') do (
set "mypath=%%~dpA"
set "size= %%~zA"
setlocal enableDelayedExpansion
set "size=!size:~-12!"
echo !size! !mypath!
endlocal
)
) >%tempfile%
sort /r %tempfile%
)
)
if exist %tempfile% del %tempfile%
if defined root popd
I had hoped to avoid creation of a temporary file by replacing the redirect and subsequent sort with a pipe directly to sort. But this does not work. (see my related question: Why does delayed expansion fail when inside a piped block of code?)
My first solution works well, except there is the potential for duplicate output depending on what input is provided. I decided I would write a version that weeds out duplicate file reports.
The basic premise was simple - save all output to one temp file with the file name added to the front of the sorted strings. Then I need to loop through the results and only print information when the file and/or the path changes.
The last loop is the tricky part, because file names can contain special characters like ! ^ & and % that can cause problems depending on what type of expansion is used. I need to set and compare variables within a loop, which usually requires delayed expansion. But delayed expansion causes problems with FOR variable expansion when ! is found. I can avoid delayed expansion by calling outside the loop, but then the FOR variables become unavailable. I can pass the variables as arguments to a CALLed routine without delayed expansion, but then I run into problems with % ^ and &. I can play games with SETLOCAL/ENDLOCAL, but then I need to worry about passing values across the ENDLOCAL barrier, which requires a fairly complex escape process. The problem becomes a big vicious circle.
One other self imposed constraint is I don't want to enclose the file and path output in quotes, so that means I must use delayed expansion, FOR variables, or escaped values.
I found an interesting solution that exploits an odd feature of FOR variables.
Normally the scope of FOR variables is strictly within the loop. If you CALL outside the loop, then the FOR variable values are no longer available. But if you then issue a FOR statement in the called procedure - the caller FOR variables become visible again! Problem solved!
#echo off
setlocal disableDelayedExpansion
set tempfile="%temp%\_mysort%random%.txt"
if exist %tempfile% del %tempfile%
set "root="
(
for %%F in (%*) do (
if not defined root (
pushd %%F || exit /b
set root=1
) else (
set "file=%%~nxF"
for /f "eol=: delims=" %%A in ('dir /s /b "%%~nxF"') do (
set "mypath=%%~dpA"
set "size= %%~zA"
setlocal enableDelayedExpansion
set "size=!size:~-12!"
echo(!file!/!size!/!mypath!
endlocal
)
)
)
)>%tempfile%
set "file="
set "mypath="
for /f "tokens=1-3 eol=/ delims=/" %%A in ('sort /r %tempfile%') do call :proc
if exist %tempfile% del %tempfile%
if defined root popd
exit /b
:proc
for %%Z in (1) do (
if "%file%" neq "%%A" (
set "file=%%A"
set "mypath="
echo(
echo %%A
echo --------------------------------------------
)
)
for %%Z in (1) do (
if "%mypath%" neq "%%C" (
set "mypath=%%C"
echo %%B %%C
)
)
exit /b