Windows batch script to renumber files in folder - windows

I have a folder with files that are (ideally) sequentially named. But sometimes I want to add a new file into the sequence, which I do by appending a letter, so that it still sorts in the right order, e.g.
I want a batch that renames these back into a proper sequence, i.e. P01.svg, P02.svg, P03.svg, etc. Of course, the correct order must be preserved in the process.
I've tried various things, but can't find a solution that preserves the order... sometimes the renaming appears to be done in the wrong order so that the files get out of sequence. My latest attempt is:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
REM first rename with xxx prefix to avoid name clashes, then do a second loop to remove the xxx prefix
set /a i=1
for /f "delims=" %%a in ('dir *.svg /b /a-d-h-s') do (
set "p=0!i!"
ren "%%a" "xxxP!p:~-2!.svg"
set /a i = i + 1
)
REM second loop...
set /a i=1
for /f "delims=" %%a in ('dir *.svg /b /a-d-h-s') do (
set "p=0!i!"
ren "%%a" "P!p:~-2!.svg"
set /a i = i + 1
)
Why are the renamed files not in the correct order every time?

You don't need you first loop. A simple ren command is sufficient.
For the correct order (by name), just expand the dircommand with the /on option ("Order by Name")
#echo off
setlocal enabledelayedexpansion
ren *.svg *.svg.tmp
set nr=100
for /f "delims=" %%a in ('dir /b /a-d-h-s /on *.svg.tmp') do (
set /a nr+=1
ECHO ren "%%a" "P!nr:~-2!.svg"
)
Note: I disabled the ren command for security reasons. When the output fits your needs, just remove the ECHO

You do not need to use two loops and neither do you have to rename the files twice. If you simply count the number of files in advance and then rename then in descending order (hence from highest to lowest) there should not be any collisions possible, given that the files have got consecutive numbers (no gaps allowed).
Here is a script that does exactly this (see all the explanatory rem remarks in the code):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=" & rem /* (root directory; empty or `.` is current,
rem `%~dp0.` is location of this script) */
set "_PREF=P" & rem // (desired file name prefix)
set "_MASK=%_PREF%*.svg" & rem // (search pattern for files)
set /A "_DIGS=2" & rem // (number of digits in the new names)
rem // Get limit of number of files that can be handled:
set /A "CNT=1" & for /L %%D in (1,1,%_DIGS%) do set /A "CNT*=10"
rem // Change into root directory:
pushd "%_ROOT%" && (
rem // Count number of matching files, quit if there are too many:
for /F %%C in ('dir /B /A:-D-H-S "P*.svg" ^| find /C /V ""') do (
set /A "CNT+=%%C" & if %%C geq %CNT% exit /B 2
)
rem /* List files sorted in descending order by name and prepend
rem with an ascending index number: */
for /F "tokens=1* delims=:" %%E in ('
dir /B /A:-D-H-S /O:-N "P*.svg" ^| findstr /N "^"
') do (
rem /* Split off the index number and determine the new number
rem to be used in the new file name: */
set /A "FNUM=CNT-%%E+1"
rem // Store the current file base name and extension:
set "FILE=%%~nF" & set "FEXT=%%~xF"
setlocal EnableDelayedExpansion
rem // Actually rename the file:
ECHO ren "!FILE!!FEXT!" "!_PREF!!FNUM:~-%_DIGS%!!FEXT!"
endlocal
)
popd
)
endlocal
exit /B
After having tested for the correct output, remove the upper-case ECHO command!
This is an approved variant that can even handle collisions (meaning a file with a new name already exists), which may occur when there are gaps in the original (numeric) sequence of file names; in such cases, an additional suffix .tmp is temporarily appended and after having processed all files that suffix becomes removed by a single ren command:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=" & rem /* (root directory; empty or `.` is current,
rem `%~dp0.` is location of this script) */
set "_PREF=P" & rem // (desired file name prefix)
set "_MASK=%_PREF%*.svg" & rem // (search pattern for files)
set "_SUFF=.tmp" & rem // (temporary suffix to handle collisions)
set /A "_DIGS=2" & rem // (number of digits in the new names)
rem // Get limit of number of files that can be handled:
set /A "CNT=1" & for /L %%D in (1,1,%_DIGS%) do set /A "CNT*=10"
rem // Change into root directory:
pushd "%_ROOT%" && (
rem // Terminate if there are files with the temporary suffix:
if defined _SUFF if exist "%_MASK%%_SUFF%" popd & exit /B 3
rem // Count number of matching files, quit if there are too many:
for /F %%C in ('dir /B /A:-D-H-S "P*.svg" ^| find /C /V ""') do (
set /A "CNT+=%%C" & if %%C geq %CNT% popd & exit /B 2
)
rem /* List files sorted in descending order by name and prepend
rem with an ascending index number: */
for /F "tokens=1* delims=:" %%E in ('
dir /B /A:-D-H-S /O:-N "P*.svg" ^| findstr /N "^"
') do (
rem /* Split off the index number and determine the new number
rem to be used in the new file name: */
set /A "FNUM=CNT-%%E+1"
rem // Store the current file base name and extension:
set "FILE=%%~nF" & set "EXTF=%%~xF" & set "EXTT="
setlocal EnableDelayedExpansion
rem /* Actually rename the file; in case of collisions, append
rem another suffix to the name and rename again later: */
set "NAME=!_PREF!!FNUM:~-%_DIGS%!!EXTF!"
if /I not "!FILE!!EXTF!"=="!NAME!" (
if exist "!NAME!" set "EXTT=!_SUFF!"
ECHO ren "!FILE!!EXTF!" "!NAME!!EXTT!"
)
endlocal
)
rem // Resolve potential collisions, so remove additional suffixes:
setlocal EnableDelayedExpansion
if defined _SUFF if exist "!_MASK!!_SUFF!" ren "!_MASK!!_SUFF!" "*."
endlocal & popd
)
endlocal
exit /B
Again remove the upper-case ECHO command to actually rename files!

Related

Windows Batch Select All files in folder with highest versionnumber

I'm trying to write a windows batch that reads files in a Release folder with multiple Versions for a program.
I have a Release folder like that:
Program01
1.0.0
ivy.xml
1.0.1
ivy.xml
Program02
1.0.0
ivy.xml
2.0.0
ivy.xml
I need a windows batch that only reads in the ivy.xml for each program with the highest versionnumber. So for the example above it should read:
Program01/1.0.1/ivy.xml
Program02/2.0.0/ivy.xml
I only need the code that selects the ivy.xml in the newest folder.
Try this one:
#echo off
set RootFolder=Test
pushd "%RootFolder%"
For /d %%a in (*) do call :SetVersion "%%~a"
echo.
pause
:SetVersion
for /f "delims=" %%b in ('dir /b /ad /o-n "%~1"') do (
if /i exist "%~1\%%b\ivy.xml" echo %~1\%%b\ivy.xml
goto :EOF
)
If the version number folders always consist of three numerals separated by a dot, the following simple approach should do what you want:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=Release" & rem // (path to root directory)
set "_SDIR=Program*" & rem // (pattern for first-level sub-directories)
set "_MASK=*.*.*" & rem // (pattern for second-level sub-sirectories)
set "_FILE=ivy.xml" & rem // (name of particular file to be found)
rem // Iterate through the immediate sub-directories of the given root directory:
for /D %%J in ("%_ROOT%\%_SDIR%") do (
rem // Loop through the sub-sub-directories sorted in alphabetic order:
for /F "delims= eol=|" %%I in ('dir /B /A:D /O:N "%%~J\%_MASK%"') do (
rem // Ensure there is a specific file but not a sub-directory:
if exist "%%~J\%%I\%_FILE%" if not exist "%%~J\%%I\%_FILE%\*" (
rem /* Store currently iterated item; since this value becomes overwritten during
rem each iteration, the eventual value constitutes the highest version number: */
set "LATEST=%%~J\%%I\%_FILE%"
)
)
rem // Return the latest version directory per each sub-directory:
call echo/%%LATEST%%
)
endlocal
exit /B
If version number components may consist of multiple figures (like 2.13.685), it becomes more complicated because you must implement alphanumeric sorting yourself because standard alphabetic sort would return false results (since 10 comes before 2 then):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=Release" & rem // (path to root directory)
set "_SDIR=Program*" & rem // (pattern for first-level sub-directories)
set "_MASK=*.*.*" & rem // (pattern for second-level sub-sirectories)
set "_FILE=ivy.xml" & rem // (name of particular file to be found)
set "_TMPF=%TEMP%\%~n0_%RANDOM%.tmp" & rem // (path to temporary file)
rem // Iterate through the immediate sub-directories of the given root directory:
for /D %%J in ("%_ROOT%\%_SDIR%") do (
rem // Open a temporary file for writing:
> "%_TMPF%" (
rem // Loop through sub-sub-directories:
for /D %%I in ("%%~J\%_MASK%") do (
rem // Ensure there is a specific file but not a sub-directory:
if exist "%%~I\%_FILE%" if not exist "%%~I\%_FILE%\*" (
rem // Store currently iterated item:
set "ITEM=%%~I\%_FILE%"
rem // Split the version number into sot-separated portions:
for /F "tokens=1-3* delims=. eol=." %%A in ("%%~nxI") do (
rem // Precede these portions with zeros:
set "AA=0000%%A" & set "BB=0000%%B" & set "CC=0000%%C" & set "DD=0000%%D"
rem /* Write the portions padded with zeros to the left plus a `|`
rem plus the original name of the currently iterated item; the
rem part left to the `|` is just needed for sorting; due to the
rem padding alphabetic sorting equals alphanumeric sorting: */
setlocal EnableDelayedExpansion
echo(!AA:~-4!.!BB:~-4!.!CC:~-4!.!DD:~-4!^|!ITEM!
endlocal
)
)
)
)
rem // Read from the temporary file and iterate through alphabetically sorted lines:
for /F "tokens=2 delims=| eol=|" %%I in ('sort "%_TMPF%"') do (
rem /* Store the part right to the `|` of the currently iterated item,
rem which constitutes its original name; the variable eventually
rem contains the highest version number: */
set "LAST=%%I"
)
rem // Return the latest version directory of the current sub-directory:
setlocal EnableDelayedExpansion
echo(!LAST!
endlocal
)
rem // Clean up temporary file:
del "%_TMPF%"
endlocal
exit /B

Get a variable from filename string in windows batch

good day, i have a folder with 400 files and i want to print a "name" from the filenames
this is the structure
ej:
20201323223_vendo.perfil01_17872513294967257_1601950878_live.mp4
20201323223__vvcastrillon_12_17949951031375250_1601939874_live.mp4
2020323123_yessromero.g_17849208194340047_1601945592_live.mp4
2020323223_ziizii_08_17979840166310477_1601929868_live.mp4
and what i need is
vendo.perfil01
_vvcastrillon_12
yessromero.g
ziizii_08
Im try to loop in the files and separate whit the _ and extract the 2 and 3 token numeral conditioning but the result is wrong and missing variables
#echo off
setlocal EnableDelayedExpansion
:loop
SET max=5000
for /F "delims=" %%I in ('dir "*_*_*.mp4" /A-D /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /L /V "\\outdir\\"') do (for /F "eol=| tokens=2,3 delims=_" %%J in ("%%~nI") do (SET "var="&for /f "delims=0123456789" %%a in ("%%K") do SET var=%%a
if defined var ( set nam=%%J_%%K ) else ( set nam=%%J )
)
echo/!nam!
)
timeout 10 > nul
goto loop
i think the answer is remove the first number before the _ then the string _xxxxxx_xxxxxxx_live.mp4 at the end but i dont know how read in reverse the tokens
tanks for any help
Since you have got different numbers of _-separated items in your file names and even adjacent _, so using for /F to split them into specific tokens with delims=_ is not the best choice.
I would use a standard for-loop instead, which receives modified file names, namely with each _ replaced by " " and enclosed within "", which leads to SPACE-separated partial name items. So:
20201323223_vendo.perfil01_17872513294967257_1601950878_live.mp4
is changed to:
"20201323223" "vendo.perfil01" "17872513294967257" "1601950878" "live.mp4"
before looping. Within the loop implement an index counter and just append those items to a buffer whose index numbers lie within a certain range that depends on the total number of items.
Here is an example script that demonstrates what I mean:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=%~dp0." & rem // (full path to target directory)
set "_MASK=*_*_*_*_live.mp4" & rem // (pattern to match file names)
set "_FILT=^[0123456789][0123456789]*_..*_[0123456789][0123456789]*_[0123456789][0123456789]*_live\.mp4$"
rem // (additional `findstr` filter for file names)
set /A "_POS=1" & rem /* (index of first `_`-separated item to extract;
rem `0` is the first item, `-1` is the last) */
set /A "_NUM=-4" & rem /* (number of `_`-separated items to extract;
rem `-1` means all up to the last item) */
rem // Change into target directory:
pushd "%_ROOT%" && (
rem // Loop through all matching non-hidden, non-system files:
for /F "delims= eol=|" %%F in ('
dir /B /A:-D-H-S "%_MASK%" ^| findstr /I /R /C:"%_FILT%"
') do (
rem // Store current file name, initialise item index, counter and buffer:
set "FILE=%%F" & set /A "IDX=-1, CNT=-1" & set "BUFF=_"
rem // Toggle delayed expansion to avoid troubles with `!`:
setlocal EnableDelayedExpansion
rem // Count number of items and store one less:
rem set "TEST=%FILE:_=" & set /A "CNT+=1" & set "TEST=%"
for %%I in ("!FILE:_=" "!") do set /A "CNT+=1"
rem // Determine item index position from given index and number:
if !_POS! lss 0 (set /A "BEG=CNT+_POS+1") else set /A "BEG=_POS"
if !_NUM! lss 0 (set /A "END=CNT+_NUM+1") else set /A "END=_POS+_NUM-1"
rem // Transport numbers over `endlocal` barrier:
for %%C in (!CNT!) do for %%B in (!BEG!) do for %%A in (!END!) do (
rem // Loop through `_`-separated items of file name:
for %%I in ("!FILE:_=" "!") do (
rem // Store current item, increment item index:
endlocal & set "ITEM=%%~I" & set /A "IDX+=1"
setlocal EnableDelayedExpansion
rem // Append current item to buffer if in range:
if !IDX! geq %%B if !IDX! leq %%A (
rem // Transport buffer over `endlocal` barrier:
for /F "delims=" %%E in ("BUFF=!BUFF!_!ITEM!") do (
endlocal & set "%%E"
setlocal EnableDelayedExpansion
)
)
)
)
rem // Return buffer:
echo(!BUFF:~2!
endlocal
)
rem // Return from target directory:
popd
)
endlocal
exit /B
Something like this should help:
#echo off
setlocal enabledelayedexpansion
for /f "tokens=1* delims=_." %%i in ('dir /b /s /a-d "*_*_*.mp4"2^>nul ^| findstr.exe /ILV "\\outdir\\"') do (
set "var=%%j"
for /f "tokens=2,* delims=_." %%a in ("%%j") do echo !var:_%%b=!
)
Keep in mind that using delims will also split on consecutive characters like double underscore. for those you need to predetermine which has double underscore and let the script add it for you.
#ECHO OFF
SETLOCAL
for %%a in (
20201323223_vendo.perfil01_17872513294967257_1601950878_live.mp4
20201323223__vvcastrillon_12_17949951031375250_1601939874_live.mp4
2020323123_yessromero.g_17849208194340047_1601945592_live.mp4
2020323223_ziizii_08_17979840166310477_1601929868_live.mp4
) do set "filename=%%a" &call :process&echo --------------------------------------
GOTO :EOF
:process
echo stage 1 %filename%
:: step 1 : delete all characters up to and including the first underscore
set "filename=%filename:*_=%"
echo stage 2 %filename%
:: step 2 : find all numeric strings of length 4 or more in remainder
call :strsgt4 %filename:_= %
:: step 3 : replace each numeric string of length 4 or more + preceding underscore with "/" (invalid filename character)
echo stage 3 %filename%
:proc3lp
if "%zapstrings%" neq " " for %%v in (%zapstrings%) do call set "filename=%%filename:_%%v=/%%"&call set "zapstrings=%%zapstrings: %%v=%%"&goto proc3lp
echo stage 4 %filename%
:: step 5 : Remove all charactersincluding and after the first "/"
for /f "delims=/" %%v in ("%filename%") do echo result %%v
goto :eof
:strsgt4
set "zapstrings= "
:strsgt4loop
set "test=%1"
if not defined test goto :eof
set "test=%test:~4%"
if defined test call :isnum %test%&if not defined notnumber set "zapstrings=%zapstrings% %1"
shift
goto strsgt4loop
:: Determine whether %1 is purely numeric
:isnum
SET "notnumber=9%~1"
FOR /l %%z IN (0,1,9) DO CALL SET "notnumber=%%notnumber:%%z=%%"
GOTO :eof
Really a question of working out what your rules are.
I decided that your rules were: string after the first underscore, until before the first subsequent underscore that precedes a numeric string or length greater than 3
The %%a loop simply submits a sequence of sample strings to :process
The inline comments should explain the remainder.

File/Folder sorting by semi-complex names

I need to sort a series of images into folders by part of their names.
The only thing consistent is a series of numbers after an underscore.
I would like to use the content before the underscore as the folder name.
The problem is that some of the images have an underscore in the name.
I'm just not getting the variables right...
Currently using:
#echo off
setlocal enabledelayedexpansion
for %%A in (*.jpg) do (
echo file found %%A
for /f "delims=" %%B in ("%%A") do set fname=%%~nB
for /f "delims=" %%C in ("%%A") do set fextn=%%~xC
for /f "tokens=1* delims=_" %%D in ("!fname!") do set folname=%%D
echo folder name !folname!
if not exist "!folname!" (
echo Folder !folname! does not exist, creating
md "!folname!"
) else (
echo Folder !folname! exists
)
echo Moving file %%A to folder !folname!
move "%%A" "!folname!"
)
echo Finished
pause
Examples of problematic names:
a_t_r_a_v__a_83563614_2994781403891160_6736610473828214002_n.jpg
alexa_vn10s1ty429_81834750_1570208686488587_1442450484794514255_n.jp
More common names:
shurra.m.lance_96141088_342661823370490_6342263806980952485_n.jpg
anu.m0111_104134441_573701159868969_589828829350351913_n.jpg
Currently hoping to get these turned into folders as:
a_t_r_a_v__a
alexa_vn10s1ty429
shurra.m.lance
anu.m0111
Just not getting it to ignore the right underscores.
Thanks for your time in advance. I am learning so much here but I guess I'm just missing the last piece.
This is not a trivial task, but I think I have found a solution (see all the explanatory rem remarks):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem /* Loop through matching files; `findstr` additionally filters them, so only
rem items containing an underscore followed by a decimal digit are processed;
rem exclamation marks are prohibited in the portion behind the prefix: */
for /F "delims= eol=|" %%F in ('
dir /B /A:-D-H-S "*_*.jpg" ^| findstr /RIC:"[^_]_[0123456789][^!]*\.jpg$"
') do (
rem // Store current file name:
set "FILE=%%F" & set "TEST=%%F"
rem // Toggle delayed expansion to avoid problems with exclamation marks:
setlocal EnableDelayedExpansion
rem // Define ascending and descending number sequences for the next loop:
for %%J in ("0,1,9" "9,-1,0") do (
rem // Loop through all decimal digits, increasing, then decreasing:
for /L %%I in (%%~J) do (
rem // Find `_` plus the current digit, remove everything in front:
if not "!TEST:*_%%I=!"=="!TEST!" set "NAME=!TEST:*_%%I=_%%I!"
)
rem /* Remove previously end of file name to get the desired prefix
rem (at this point exclamation marks could cause problems): */
for /F "delims= eol=|" %%E in ("!NAME!") do set "TEST=!TEST:%%E=!"
)
rem // Create desired directory and move current file into it:
ECHO md "!TEST!" 2> nul
ECHO move /Y "!FILE!" "!TEST!\"
endlocal
)
endlocal
exit /B
After having tested for the correct output, remove the upper-case ECHO commands.
This is a simpler (and faster) method:
#echo off
setlocal EnableDelayedExpansion
set "digits=0123456789"
for %%A in (*.jpg) do (
set "fname=%%A"
rem Initialize "first digit after an underscore"
set "firstDigit="
rem Split name at underscores
for %%a in ("!fname:_= " "!") do (
if not defined firstDigit (
set "part=%%~a"
rem Check if first char in part is in "digits"
for /F %%b in ("!part:~0,1!") do if "!digits:%%b=X!" neq "%digits%" set "firstDigit=%%b"
)
)
rem Split name at "underscore+firstDigit"
for /F %%a in ("!firstDigit!") do for /F %%b in ("!fname:_%%a= !") do set "folname=%%b"
echo Moving file %%A to folder !folname!
)
Output example:
Moving file a_t_r_a_v__a_83563614_2994781403891160_6736610473828214002_n.jpg to folder a_t_r_a_v__a
Moving file alexa_vn10s1ty429_81834750_1570208686488587_1442450484794514255_n.jp to folder alexa_vn10s1ty429
Moving file shurra.m.lance_96141088_342661823370490_6342263806980952485_n.jpg to folder shurra.m.lance
Moving file anu.m0111_104134441_573701159868969_589828829350351913_n.jpg to folder anu.m0111

Move files into a subfolder of a destination directory

I started using batch commands last week and I've reached a real obstacle with a script I made.
What I want to do
Move a PDF file from C:\Users\JK\Documents\reports PDFs into pre-made subfolders in the destination W:\Departments\QA\cases.
For example the script would move 2223 report.pdf to W:\Departments\QA\cases\2201 - 2300\2223
What I tried
I made a script based off the answer in this thread
cls
#pushd %~dp0
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceDir=C:\Users\JK\Documents\reports PDFs"
set "DestDir=W:\Departments\QA\cases\"
for /F "eol=| delims=" %%A in ('dir /B /A-D-H "%SourceDir%\*.pdf" 2^>nul') do (
for /F "eol=| tokens=1" %%B in ("%%~nA") do (
for /D %%C in ("%DestDir%\%%B*") do move /Y "%SourceDir%\%%A" "%%C\"
)
)
endlocal
popd
pause
Where I am stuck
How could I add subfolders or account for them in the destination directory?
FYI, I also tried adding a wildcard symbol at the end of the destination directory by changing %DestDir%\%%B to %DestDir%\*\%%B*.
I would probably accomplish the task with the following script (see all the explanatory rem remarks):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_SOURCE=C:\Users\JK\Documents\reports PDFs" & rem // (source directory; `.` or `` is current, `%~dp0.` is script parent)
set "_TARGET=W:\Departments\QA\cases" & rem // (target directory; `.` or `` is current, `%~dp0.` is script parent)
set "_SUBDIR=#" & rem // (set to non-blank value in order to create individual sub-directories)
set "_FMASK=????*.pdf" & rem // (pattern to match source files)
set "_DMASK=???? - ????*" & rem // (pattern to match target directories)
set "_FFIL1=^[0123456789][0123456789][0123456789][0123456789] .*\.pdf$" & rem // (filter 1 for source files)
set "_FFIL2=^[0123456789][0123456789][0123456789][0123456789]\.pdf$" & rem // (filter 2 for source files)
set "_DFIL1=^[0123456789][0123456789][0123456789][0123456789] - [0123456789][0123456789][0123456789][0123456789] .*$"
set "_DFIL2=^[0123456789][0123456789][0123456789][0123456789] - [0123456789][0123456789][0123456789][0123456789]$"
rem // Change into source directory:
pushd "%_SOURCE%" && (
rem // Iterate through all files matching the specified pattern and filters:
for /F "eol=| delims=" %%F in ('
dir /B /A:-D-H-S "%_FMASK%" ^| findstr /I /R /C:"%_FFIL1%" /C:"%_FFIL2%"
') do (
rem // Store full path of currently iterated file:
set "FILE=%%~fF"
rem // Extract the leading numeric part of the file name:
for /F "eol=| delims= " %%N in ("%%~nF") do (
rem // Store the numeric part:
set "NUM=%%N"
rem /* Remove any leading zeros from the numeric part of the file name, because
rem such cause the number to be unintentionally interpreted as octal: */
set /A "INT=0" & for /F "eol=| tokens=* delims=0" %%Z in ("%%N") do 2> nul set /A "INT=%%Z"
)
rem // Change into target directory:
pushd "%_TARGET%" && (
rem // Iterate through all directories matching the specified pattern and filters:
for /F "eol=| delims=" %%D in ('
cmd /V /C 2^> nul dir /B /A:D-H-S "!_DMASK:|=%%I!" ^| findstr /I /R /C:"%_DFIL1%" /C:"%_DFIL2%"
') do (
rem // Store name of currently iterated directory:
set "TDIR=%%D"
rem // Extract first (from) and second (to) numeric parts of directory names:
for /F "eol=| tokens=1-2 delims=- " %%S in ("%%D") do (
rem // Remove any leading zeros from first (from) numeric part:
set /A "FRM=0" & for /F "eol=| tokens=* delims=0" %%Z in ("%%S") do set /A "FRM=%%Z"
rem // Remove any leading zeros from second (to) numeric part:
set /A "TOO=0" & for /F "eol=| tokens=* delims=0" %%Z in ("%%T") do set /A "TOO=%%Z"
)
rem // Toggle delayed variable expansion to avoid trouble with `!`:
setlocal EnableDelayedExpansion
rem /* Do integer comparisons to check whether the numeric part of the file name
rem lies within the range given by the directory name (from/to): */
if !INT! geq !FRM! if !INT! leq !TOO! (
rem // Numeric part of file name lies within range, hence try to move file:
if defined _SUBDIR (
2> nul md "!TDIR!\!NUM!"
ECHO move "!FILE!" "!TDIR!\!NUM!\"
) else (
ECHO move "!FILE!" "!TDIR!\"
)
)
endlocal
)
rem // Return from target directory:
popd
)
)
rem // Return from source directory:
popd
)
endlocal
exit /B
You may need to adapt a few constants to your situation in the section commented with Define constants here: at the top.
After having tested the script for the correct output, remove the upper-case ECHO commands in front of the move commands! In order to avoid multiple 1 file(s) moved. messages, replace these ECHO commands by > nul.

Fix moving and consolidating folders when use batch file process

I have this code
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "SPLITCHAR=-" & rem // (a single character to split the file names)
set "SEARCHSTR=_" & rem // (a certain string to be replaced by another)
set "REPLACSTR= " & rem // (a string to replace all found search strings)
set "OVERWRITE=" & rem // (set to non-empty value to force overwriting)
rem // Get file location and pattern from command line arguments:
set "LOCATION=%~1" & rem // (directory to move the processed files into)
set "PATTERNS=%~2" & rem // (file pattern; match all files if empty)
rem /* Prepare overwrite flag (if defined, set to character forbidden
rem in file names; this affects later check for file existence): */
if defined OVERWRITE set "OVERWRITE=|"
rem // Continue only if target location is given:
if defined LOCATION (
rem // Create target location (surpress error if it already exists):
2> nul md "%LOCATION%"
rem /* Loop through all files matching the given pattern
rem in the current working directory: */
for /F "eol=| delims=" %%F in ('dir /B "%PATTERNS%"') do (
rem // Process each file in a sub-routine:
call :PROCESS "%%F" "%LOCATION%" "%SPLITCHAR%" "%SEARCHSTR%" "%REPLACSTR%"
)
)
endlocal
exit /B
:PROCESS
rem // Retrieve first argument of sub-routine:
set "FILE=%~1"
rem // Split name at (first) split character and get portion in front:
for /F "delims=%~3" %%E in ("%~1") do (
rem // Append a split character to partial name:
set "FOLDER=%%E%~3"
)
setlocal EnableDelayedExpansion
rem // Right-trim partial name:
if not "%~4"=="" set "FOLDER=!FOLDER:%~4%~3=!"
set "FOLDER=!FOLDER:%~3=!"
rem /* Check whether partial name is not empty
rem (could happen if name began with split character): */
if defined FOLDER (
rem // Replace every search string with another:
if not "%~4"=="" set "FOLDER=!FOLDER:%~4=%~5!"
rem // Create sub-directory (surpress error if it already exists):
2> nul md "%~2\!FOLDER!"
rem /* Check if target file already exists; if overwrite flag is
rem set (to an invalid character), the target cannot exist: */
if not exist "%~2\!FOLDER!\!FILE!%OVERWRITE%" (
rem // Move file finally (surpress `1 file(s) moved.` message):
1> nul move /Y "!FILE!" "%~2\!FOLDER!"
)
)
endlocal
exit /B
I use Command Prompt in this way to create folders and move files inside from folder1 to folder2
cd /D "C:\Users\Administrator\Downloads\"
"C:\Users\Administrator\Downloads\test1\build-folder-hierarchy.bat" "C:\Users\Administrator\Downloads\test2" "*.mkv"
What is the problem ?
But I want to get a folder consolidation from files moving, not generates same number of folders from files
The.Race.Corsa.Mortale.2019.S1E02.Episodio2.HDTV.AAC.iTA.X264-ARSENAL.mkv
The.Race.Corsa.Mortale.2019.S1E01.Episodio1.HDTV.AAC.iTA.X264-ARSENAL.mkv
The.Feed.1x05.Episodio.5.ITA.DLMux.x264-UBi.mkv
The.Feed.1x04.Episodio.4.ITA.DLMux.x264-UBi.mkv
The.Feed.1x03.Episodio.3.ITA.DLMux.x264-UBi.mkv
The.Feed.1x02.Episodio.2.ITA.DLMux.x264-UBi.mkv
The.Feed.1x01.Episodio.1.ITA.DLMux.x264-UBi.mkv
Swamp.Thing.1x10.La.Resa.Dei.Conti.ITA.DLMux.x264-UBi.mkv
Volevo.Fare.La.Rockstar.1x11.Confusione.ITA.WEBRip.x264-UBi.mkv
Volevo.Fare.La.Rockstar.1x07.Tabu.ITA.WEBRip.x264-UBi.mkv
Volevo.Fare.La.Rockstar.1x01.Buon.Compleanno.Olly.ITA.WEBRip.x264-UBi.mkv
Volevo Fare La Rockstar 1x12 La Tribu Ita Webrip x264-Ubi.mkv
Virgin.River.1x10.Finali.inattesi.720p.iTA.AAC.DLRip.x265.-.T7.mkv
Virgin.River.1x07.A.dire.il.vero.720p.iTA.AAC.DLRip.x265.-.T7.mkv
Virgin.River.1x04.Un.Cuore.Ferito.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
Virgin.River.1x01.La.Vita.Continua.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
Tre.Giorni.Di.Natale.1x03.Episodio.3.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
Tre.Giorni.Di.Natale.1x01.Episodio.1.iTA.AC3.WEBMux.x264-ADE.CreW.mkv
But I want to get folders and move files in this way
├─The Race Corsa Mortale [folder]
│ ├─The.Feed.1x05.Episodio.5.ITA.DLMux.x264-UBi [file]
│ ├─The.Feed.1x04.Episodio.4.ITA.DLMux.x264-UBi [file]
└─ ....
├─Virgin River [folder]
│ └─Virgin.River.1x07.A.dire.il.vero.720p.iTA.AAC.DLRip.x265 [file]
:
I try also to use this batch script but it doesn't work: I click on in via explorer but is like disactivated (I use Windows Server 2012)
#echo off
setlocal EnableDelayedExpansion
rem Change current directory to the one where this .bat file is located
cd "%~P0"
set "digits=0123456789"
rem Process all *.mkv files
for %%f in (*.mkv) do (
rem Get the folder name of this file
call :getFolder "%%f"
rem If this file have a properly formatted name: "headS##Etail"
if defined folder (
rem Move the file to such folder
if not exist "!folder!" md "!folder!"
move "%%f" "!folder!"
)
)
goto :EOF
:getFolder file
set "folder="
set "file=%~1"
set "head="
set "tail=%file%"
:next
for /F "delims=%digits%" %%a in ("%tail%") do set "head=%head%%%a"
set "tail=!file:*%head%=!"
if not defined tail exit /B
if /I "%head:~-1%" equ "S" goto found
:digit
if "!digits:%tail:~0,1%=!" equ "%digits%" goto noDigit
set "head=%head%%tail:~0,1%"
set "tail=%tail:~1%"
goto digit
:noDigit
goto next
:found
for /F "delims=Ee" %%a in ("%tail%") do set "folder=%head%%%a"
exit /B
I accept also Powershell solutions
EDIT: Portion of the file name that I need is that before of S#E##, #x## .#x##, .#x#, .#x## and similar
I would probably use the following script (please consult all the explanatory rem remarks):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=%~1" & rem /* (target directory; `.` is current working directory, `%~dp0.` is
rem parent of this script, `%~1` is first command line argument) */
set _MASKS="*.mkv" & rem // (space-separated list of quoted file patterns)
set _SEPS=" " "." & rem // (space-separated list of quoted separators)
rem /* Specify multiple `findstr` search strings, including the prefix `/C:`, as you would
rem directly state them at the `findstr` command, which are used to match the particular
rem sub-strings of the file names that are used to find the part where to split them at
rem and to derive the name of the sub-directory where to move the respective file to: */
set _FILTERS=/C:"^S[0123456789][0123456789]*E[0123456789][0123456789]*$" ^
/C:"^[0123456789][0123456789]*x[0123456789][0123456789]*$"
rem // Change into root directory:
pushd "%_ROOT%" && (
rem // Loop through all matching files:
for /F "delims= eol=|" %%F in ('dir /B /A:-D-H-S %%_MASKS%%') do (
rem // Store current file name and extension, initialise some auxiliary variables:
set "NAME=%%~nF" & set "EXT=%%~xF" & set "SDIR= " & set "FLAG=#"
rem // Toggle delayed expansion to avoid trouble with `!` (also later on):
setlocal EnableDelayedExpansion
rem // Replace all predefined separators by spaces:
for %%S in (!_SEPS!) do set "NAME=!NAME:%%~S= !"
rem // Loop through all space-separated (quoted) items of the file name:
for %%I in ("!NAME: =" "!") do (
rem // Skip the loop body when a sub-string has already been found before:
if defined FLAG (
rem // Store current portion of the file name:
endlocal & set "ITEM=%%~I"
rem // Use `findstr` to match against the predefined sub-strings:
cmd /V /C echo(!ITEM!| > nul findstr /R /I %_FILTERS% && (
rem // Match encountered, hence skip this and the remaining items:
set "FLAG="
) || (
rem /* No match found, so append the current item to the name of the
rem sub-directory where the file is supposed to be moved then: */
setlocal EnableDelayedExpansion
for /F "delims=" %%E in ("!SDIR!!ITEM! ") do (
endlocal & set "SDIR=%%E"
)
)
setlocal EnableDelayedExpansion
)
)
rem // Process only file naes where sub-directory names could be derived from:
if not defined FLAG if not "!SDIR:~1,-1!"=="" (
rem // Create sub-directory, if not yet existing:
ECHO 2> nul mkdir "!SDIR:~1,-1!"
rem // Move current file into the sub-directory (but do not overwrite in case):
ECHO if not exist "!SDIR:~1,-1!\!NAME!!EXT!" > nul move "!NAME!!EXT!" "!SDIR:~1,-1!\"
)
endlocal
)
popd
)
endlocal
exit /B
Supposing the script is called consolidate.bat and the target directory is %UserProfile%\Downloads, call the script like this:
consolidate.bat "%UserProfile%\Downloads"
After having tested for the correct output, remove the upper-case ECHO commands in front of the mkdir and move commands!
Assuming we can split the file name on the Digits in the name, this should do the needful.
I put a pause in, inspect the output and make sure that it is giving you the right info before clicking enter.
#(SETLOCAL EnableDelayedExpansion
ECHO OFF
REM SET "_SrcFolder=C:\Users\Administrator\Downloads\test1"
REM SET "_DstFolder=C:\Users\Administrator\Downloads\test2"
REM SET "FileGlob=*.mkv"
SET "_SrcFolder=C:\Admin"
SET "_DstFolder=C:\Admin\test2"
SET "FileGlob=*.txt"
)
CALL :Main
( ENDLOCAL
EXIT /B
)
:Main
FOR %%A IN (
"%_SrcFolder%\%FileGlob%"
) DO (
CALL :Process "%%~nA" "%%~xA" "%%~fA"
)
GOTO :EOF
:Process
SET "_OLDName=%~1"
FOR /F "Tokens=1 Delims=0123456789" %%a IN ("%~1") DO (
SET "_NewFolder=%%a"
)
SET "_NewName=!_OLDName:%_NewFolder%=!"
ECHO._OLDName=%_OLDName%.%~2
ECHO._NewFolder=%_NewFolder%
ECHO._NewName=%_NewName%.%~2
REM pause
ECHO.
ECHO. We will now do the following if you press any key:
ECHO. MD "%_DstFolder%\%_NewFolder%\"
ECHO. COPY "%~3" "%_DstFolder%\%_NewFolder%\%_NewName%%~2"
ECHO.
PAUSE
MD "%_DstFolder%\%_NewFolder%\"
COPY "%~3" "%_DstFolder%\%_NewFolder%\%_NewName%%~2"
GOTO :EOF

Resources