This directory creation and file moving task could be done with following batch code:
@echo off
if "%~1" == "" (pushd "%~dp0") else (
pushd "%~1"
if errorlevel 1 (
echo ERROR: Directory "%~1" does not exist.
echo(
pause
exit /B
)
)
setlocal EnableExtensions DisableDelayedExpansion
set "FolderName=\"
for /F "eol=| delims=" %%I in ('dir /A-D /B /O-N 2^>nul') do if not "%%~fI" == "%~f0" (
for /F "eol=| tokens=1 delims=[]" %%J in ("%%~nI") do (
set "FileName=%%J"
set "FullName=%%I"
setlocal EnableDelayedExpansion
set "DiskAddon=!FileName:*(Disk =(Disk !"
if not "!DiskAddon!" == "!FileName!" for /F "delims=" %%V in ("!DiskAddon!") do set "FileName=!FileName:%%V=!"
for /F "eol=| delims=" %%K in ("!FolderName!") do (
if "!FileName:%%K=!" == "!FileName!" (
md "!FileName!" 2>nul
move /Y "!FullName!" "!FileName!\"
for /F "eol=| delims=" %%V in ("!FileName!") do (
endlocal
set "FolderName=%%V"
)
) else (
move /Y "!FullName!" "!FolderName!\"
endlocal
)
)
)
)
endlocal
popd
The batch file can be stored in the directory with the files to process or is called with path of the directory to process. The batch file directory or the specified directory is made temporarily the current directory.
The environment variable FolderName
stores the last created folder name depending on file name. The environment variable FolderName
is defined with invalid name \
to always run in true branch of the most inner IF condition on first file.
The first FOR runs in background one more command process started with %ComSpec% /c
and the command line between '
appended as additional arguments. So executed is with Windows installed into C:\Windows
:
C:\Windows\System32\cmd.exe /c dir /A-D /B /O-N 2>nul
The command DIR executed by started background command process outputs to handle STDOUT (standard output) of the background command process
- just the file names because of option
/A-D
(attribute not directory)
- in bare format which means just file name with file extension, but without file path, because of option
/B
- ordered reverse by name because of
/O-N
- of all files in current directory matching the default wildcard pattern
*
.
The reverse order is necessary here to get the shorter file names output first respectively those with [...]
output before those with (Disk x of y)
.
It could be that no file is found at all. In this case an error message would be output by DIR to handle STDERR (standard error). This not important error message is suppressed by redirecting it to device NUL. The FOR loop does nothing on no file found in specified directory.
Read the Microsoft article about Using command redirection operators for an explanation of 2>nul
. The redirection operator >
must be escaped with caret character ^
on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir
command line with using a separate command process started in background.
FOR with the used option /F
and a command line enclosed in '
captures everything output to handle STDOUT of the started background command process and processes this output line by line after started cmd.exe
terminated itself after finishing execution of the command line.
Empty lines are by default ignored by FOR which do not occur here.
FOR would split up each line into substrings by default using normal space and horizontal tab as string delimiter and would assign just the first space/tab separated substring to specified loop variable I
. This line splitting behavior is not wanted here because of file names can contain one or more spaces. For that reason an empty list of delimiters is defined with option delims=
to disable completely the line splitting behavior.
FOR would ignore also lines on which the first substring after line splitting is a semicolon which is the default end of line character. A file name could start with a semicolon. Therefore eol=|
redefines end of line character to a vertical bar which no file name can have in its name.
So each file name with file extension is assigned completely one after the other to loop variable I
for further processing.
The batch file currently processed by cmd.exe
is ignored on processing the file names because of the IF condition on first FOR command line.
The next FOR processes just the file name without file extension as string. The options eol=| tokens=1 delims=[]
are for splitting the file name up into substrings using the square brackets as delimiters with just first substring assigned to specified loop variable J
and without ignoring file names starting with ;
. This is necessary to get for a file name like 3D Pool (1989)(Firebird Software)[cr Steel McKraken - Exocet].dsk
just 3D Pool (1989)(Firebird Software)
assigned to loop variable J
for further processing.
Variables are not behaving as expected describes that Windows command processor replaces all %variable%
in a command block starting with (
and ending with matching )
by current value of the environment variable before the command is executed making use of the command block. That means delayed expansion is required which cannot be enabled above the FOR loop as otherwise each !
in a file name would be interpreted also as beginning/end of an environment variable reference expanded delayed on execution. For that reason delayed expansion is enabled and disabled inside the loop.
Read this answer for details about the commands SETLOCAL and ENDLOCAL.
The environment variable DiskAddon
is defined either with the (Disk x of y)
part at end of file name if the string (Disk
is somewhere in file name, or with the unmodified file name if not containing (Disk
at all.
So if string assigned to DiskAddon
is different to the file name string, the string (Disk x of y)
is also removed from the file name using a delayed expanded string substitution. The current string assigned to DiskAddon
must be temporarily assigned to a loop variable to be able to use this string in a delayed expanded string substitution. It is not possible to specify a delayed expanded environment variable reference inside a delayed expanded environment variable substitution.
The current folder name must be assigned temporarily also to a loop variable to be able to use this string in a delayed expanded string substitution on next IF condition. So the next FOR is just for assigning the current folder name as assigned to environment variable FolderName
to loop variable K
.
The IF condition is a case-sensitive comparison of current (truncated) file name with all occurrences of current folder name case-insensitively removed with the current (truncated) file name. In other words the IF condition checks if the current (truncated) file name does not contain the current folder name. In this case the current file must be moved into a new folder.
Therefore the folder is created with the current (truncated) file name with suppressing the error message with a redirection to device NUL on folder already existing and moving current file into this folder. Next it is necessary to disable delayed expansion and restore previous environment. But it is necessary to passed the folder name of just created folder to previous environment. So once again a FOR loop is used with current folder name respectively truncated file name assigned to loop variable V
to get the folder name assigned to the environment variable FolderName
in previous environment.
Otherwise the current file is moved into the same folder as the previous file before disabling delayed expansion and restoring previous environment.
Finally the batch file restores initial environment and also initial current directory.
Here is also an alternative solution using a subroutine which makes the string substitutions easier because of no command line is within a command block.
@echo off
if "%~1" == "" (pushd "%~dp0") else (
pushd "%~1"
if errorlevel 1 (
echo ERROR: Directory "%~1" does not exist.
echo(
pause
exit /B
)
)
setlocal EnableExtensions DisableDelayedExpansion
set "FolderName=\"
for /F "eol=| delims=" %%I in ('dir /A-D /B /O-N 2^>nul') do if not "%%~fI" == "%~f0" call :ProcessFile "%%I"
goto EndBatch
:ProcessFile
for /F "eol=| tokens=1 delims=[]" %%J in ("%~n1") do set "FileName=%%J"
setlocal EnableDelayedExpansion
set "DiskAddon=!FileName:*(Disk =(Disk !"
if not "!DiskAddon!" == "!FileName!" set "FileName=!FileName:%DiskAddon%=!"
if not "!FileName:%FolderName%=!" == "!FileName!" endlocal & goto MoveFile
endlocal & set "FolderName=%FileName%"
md "%FolderName%" 2>nul
:MoveFile
move /Y %1 "%FolderName%\"
goto :EOF
:EndBatch
endlocal
popd
See single line with multiple commands using Windows batch file for an explanation of operator &
used two times in this batch file to avoid the usage of a command block.
To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.
call /?
... explains %~dp0
, %~f0
, %1
, %~1
, %~n1
dir /?
echo /?
endlocal /?
exit /?
for /?
goto /?
if /?
md /?
move /?
pause /?
popd /?
pushd /?
set /?
setlocal /?
PS: The usage of a PowerShell script for the entire task would be much better. PowerShell supports built-in string functions like searching for a string in a string or modifying a string with a regular expression to get the base file name. The Windows command processor executing a batch file is designed for execution of commands and executables and not for string manipulations as needed for this task.