0

I'm using this code to create folders and unify files with the same name, it turns out that some files worked, as was the case with 3D Pool, but other cases that files have (Disk 1 of 2), (Disk 2 of 2) did not work , could help?

@echo off
setlocal

set "basename=."
for /F "tokens=1* delims=." %%a in ('dir /B /A-D ^| sort /R') do (
   set "filename=%%a"
   setlocal EnableDelayedExpansion
   for /F "delims=" %%c in ("!basename!") do if "!filename:%%c=!" equ "!filename!" (
      set "basename=!filename!"
      md "!basename!"
   )
   move "!filename!.%%b" "!basename!"
   for /F "delims=" %%c in ("!basename!") do (
      endlocal
      set "basename=%%c
   )
)

My struture folder with files:

3D Pool (1989)(Firebird Software)(Disk 1 of 2).dsk
3D Pool (1989)(Firebird Software)(Disk 2 of 2).dsk
3D Pool (1989)(Firebird Software).dsk
3D Pool (1989)(Firebird Software)[cr Steel McKraken - Exocet].dsk
5th Axis, The (1985)(Loriciels)(fr).dsk
5th Axis, The (1985)(Loriciels)(fr)[a].dsk
5th Axis, The (1985)(Loriciels).dsk
Zolyx (1988)(Firebird Software).dsk
Zolyx (1988)(Firebird Software)[t].dsk
Zombi (1990)(Ubisoft)(fr)(Disk 1 of 2).dsk
Zombi (1990)(Ubisoft)(fr)(Disk 1 of 2)[6128 Version].dsk
Zombi (1990)(Ubisoft)(fr)(Disk 2 of 2).dsk
Zombi (1990)(Ubisoft)(fr)(Disk 2 of 2)[6128 Version].dsk
Zombi (1990)(Ubisoft)(fr)[464 Version].dsk

when using the batch it creates folders and places the files inside:

3D Pool (1989)(Firebird Software)
3D Pool (1989)(Firebird Software)[cr Steel McKraken - Exocet]
5th Axis, The (1985)(Loriciels)
Zolyx (1988)(Firebird Software)
Zolyx (1988)(Firebird Software)[t]
Zombi (1990)(Ubisoft)(fr)(Disk 1 of 2)
Zombi (1990)(Ubisoft)(fr)(Disk 1 of 2)[6128 Version]
Zombi (1990)(Ubisoft)(fr)(Disk 2 of 2)
Zombi (1990)(Ubisoft)(fr)(Disk 2 of 2)[6128 Version]
Zombi (1990)(Ubisoft)(fr)[464 Version]

what I would like was for the batch to put the files in the folder by names, leaving it as clean as possible:

3D Pool (1989)(Firebird Software)
   3D Pool (1989)(Firebird Software)(Disk 1 of 2).dsk
   3D Pool (1989)(Firebird Software)(Disk 2 of 2).dsk
   3D Pool (1989)(Firebird Software).dsk
   3D Pool (1989)(Firebird Software)[cr Steel McKraken - Exocet].dsk
5th Axis, The (1985)(Loriciels)
   5th Axis, The (1985)(Loriciels)(fr).dsk
   5th Axis, The (1985)(Loriciels)(fr)[a].dsk
   5th Axis, The (1985)(Loriciels).dsk
Zolyx (1988)(Firebird Software)
   Zolyx (1988)(Firebird Software).dsk
   Zolyx (1988)(Firebird Software)[t].dsk
Zombi (1990)(Ubisoft)(fr)
   Zombi (1990)(Ubisoft)(fr)(Disk 1 of 2).dsk
   Zombi (1990)(Ubisoft)(fr)(Disk 1 of 2)[6128 Version].dsk
   Zombi (1990)(Ubisoft)(fr)(Disk 2 of 2).dsk
   Zombi (1990)(Ubisoft)(fr)(Disk 2 of 2)[6128 Version].dsk
   Zombi (1990)(Ubisoft)(fr)[464 Version].dsk

would it be possible to do this?

Alvarez
  • 13
  • 3

3 Answers3

0

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.

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • Mofi, thanks some things happened in the generation of the folder, I reported it as another answer – Alvarez Feb 24 '20 at 10:16
  • @Alvarez You have not written anything about which rules to apply on the file names to group them and move them into a folder. For that reason I wrote the batch file based on same rule as initial code with the addition to truncate the file name on first occurrence of a square bracket. I would have written the batch file differently if the grouping rules would have been stated as: 1. Remove `(Disk x of y)` from all file names with `x` and `y` can be any positive number. 2. Remove `[z Version]` from all file names with `z` can be any positive number. – Mofi Feb 24 '20 at 16:30
  • I found a solution to remove `(Disk x and y)` from end of every file name to support also first rule of the two rules. So the batch file should work now. Please don't ask for removal of `(fr)` as there is in your example once `(fr)` to keep and once `(fr)` to remove. A clairvoyant folder name finding is possible only with usage of artificial intelligence and the result could be nevertheless different to your expectations (because of your brain is using a different artificial algorithm). – Mofi Feb 24 '20 at 19:06
0

Mofi, thank you very much for the help and explanation of the commands, I have some problems

what happened some games did not behave in that way, it was the case of games

20000 Lieus sous les Mers (1988)(Coktel Vision)(fr)(Disk 1 of 2).dsk
20000 Lieus sous les Mers (1988)(Coktel Vision)(fr)(Disk 2 of 2).dsk

they were not in a single folder, disregarding the (Disk x of y) list of folders:

20000 Lieus sous les Mers (1988)(Coktel Vision)(fr)(Disk 1 of 2)
20000 Lieus sous les Mers (1988)(Coktel Vision)(fr)(Disk 2 of 2)
3 Guerra Mundial (1989)(Pactum)(es)(Disk 1 of 2)
3 Guerra Mundial (1989)(Pactum)(es)(Disk 2 of 2)
A la Conquete de l'Orthographe (1991)(Generation 5)(fr)(Disk 1 of 2)
A la Conquete de l'Orthographe (1991)(Generation 5)(fr)(Disk 2 of 2)
A la Pursuite de Carmen Sandiego dans le Monde (1990)(Broderbund Software)(fr)(Disk 1 of 2)
A la Pursuite de Carmen Sandiego dans le Monde (1990)(Broderbund Software)(fr)(Disk 2 of 2)
Zap 't' Balls - The Advanced Edition (1992)(Elmsoft Game-Service)(Disk 1 of 2)
Zap 't' Balls - The Advanced Edition (1992)(Elmsoft Game-Service)(Disk 2 of 2)

complete folder list: https://pastebin.com/UmZFR3mb

I would like to understand why these files did not follow the bat file pattern

Alvarez
  • 13
  • 3
0
@echo off

for %%A in (*.dsk) do (
    for /f "tokens=1-3 delims=()" %%B in ("%%~A") do (
        if not exist "%%~B(%%~C)(%%~D)" md "%%~B(%%~C)(%%~D)"
        move /y "%%~A" "%%~B(%%~C)(%%~D)\" >nul
    )
)

Uses the pattern of name (year)(publisher) for folder names taken from the filenames.

michael_heath
  • 5,262
  • 2
  • 12
  • 22