1

I've got quite a lot of files I am trying to archive for the company I work for. I am a little familiar with Batch scripts, but I do not quite understand enough to fully get what I am after.

I am trying to copy some files based on the beginning of their file name into folders on our NAS. The files are 7z files and their structure is going to be like so:

  • 5476 BMW Handle-A.7z
  • 5487 Chevy-Imp.7z
  • 5986 Honda Lid-Upper.7z

etc.

The file structure works like this: the four numbers at the beginning is our company job number. On our NAS we have an archive directory with folders in it like this:

  • _5000-5999
  • _6000-6999

And inside of those folders are folders that will contain 250 archive files each. They are formatted like this:

  • _5000-5249
  • _5250-5499
  • _5500-5749
  • _5750-5999

I am looking to create a Batch file that I can drag these 7z files onto and it will read the first four numbers of the file name, and copy it to the proper folder on our NAS.

So for instance the files:

  • 5476 BMW Handle-A.7z
  • 5487 Chevy-Imp.7z

Would copy into

\nas01\archive\ _5000-5999\ _5250-5499

etc.

The main code I was messing with was this:

@echo off 
for /f "delims=" %%i in ('dir /b /a-d *.7z') do (
set "filename1=%%~i"
setlocal enabledelayedexpansion
set "folder1=!filename1:~0,4!"
mkdir "!folder1!" 2>nul
copy "!filename1!" "!folder1!" >nul
)

What is not working for me is this line:

set "folder1=!filename1:~0,4!"

How can I create some sort of variable to check the file name, create the folders if necessary and copy it to the correct folder? I would appreciate any help on this!

-Dustin

DustinH
  • 41
  • 9
  • Consider breaking your problem up into smaller more manageable chunks. For instance at the beginning you should probably convert the filename into a number, then use that number to determine where it goes, then move the file based on that. So your first question would probably be how to get each filename and get the number into a batch variable. – Adam Davis Dec 07 '17 at 17:41
  • @DustinH, I don't see anything wrong with that SET command. You enabled delayed expansion and you are referencing the variable correctly. So you will have to explain in more detail how it is not working. – Squashman Dec 07 '17 at 18:43
  • @Squashman Sorry, I just meant this isn't doing what I need it to. It is just creating a folder the same as the file number. So 5768 BWM.7z would be moved to a folder named 5768. I would like it to go into _5000-5999\ _5750-5999 – DustinH Dec 07 '17 at 18:53
  • Give you a little hint. `IF !folder1! GEQ 5000 IF !folder1! LEQ 5999 SET "subfolder1=_5000-5999"` – Squashman Dec 07 '17 at 22:10
  • Thanks for your help guys! I figured it out and will post the answer. – DustinH Dec 08 '17 at 12:22

2 Answers2

1

Thanks to some help from commenters, I was able to figure it out. I was having a problem with how to structure the script, but I got it!

@echo off
for /f "delims=" %%i in ('dir /b /a-d *.7z') do (
set "filename=%%~i"
setlocal enabledelayedexpansion
set "folder1=!filename:~0,4!"
set "subfolder1=_!folder1:~0,1!000-!folder1:~0,1!999"
set "firstdigit=!filename:~0,1!"
set "parent=\\nas01\The_Archives"

REM CONDITIONAL STATEMENTS
IF !folder1! GEQ !firstdigit!000 IF !folder1! LEQ !firstdigit!249 SET "subfolder2=_!firstdigit!000-!firstdigit!249"
IF !folder1! GEQ !firstdigit!250 IF !folder1! LEQ !firstdigit!499 SET "subfolder2=_!firstdigit!250-!firstdigit!499"
IF !folder1! GEQ !firstdigit!500 IF !folder1! LEQ !firstdigit!749 SET "subfolder2=_!firstdigit!500-!firstdigit!749"
IF !folder1! GEQ !firstdigit!750 IF !folder1! LEQ !firstdigit!999 SET "subfolder2=_!firstdigit!750-!firstdigit!999"

mkdir "!parent!\!subfolder1!\!subfolder2!" 2>nul
copy "!filename!" "!parent!\!subfolder1!\!subfolder2!" >nul
)

I learned more about Batch today, so it was a day well spent!

Is there a way I can make this to where you just click and drag the files into this Batch script instead of grabbing all of the 7z files in the folder?

DustinH
  • 41
  • 9
  • 1
    Congratulations! Good work for numbers in range 1000 to 9999 in file name. – Mofi Dec 08 '17 at 12:34
  • @Mofi Thanks for your reply! Is there a way to make this just a click and drag script? Currently it pulls all the .7z files in the folder. I know I need to modify line #2 `for /f "delims=" %%i in ('dir /b /a-d *.7z') do (` – DustinH Dec 08 '17 at 12:37
  • 1
    Replace `for /f "delims=" %%i in ('dir /b /a-d *.7z')` by `for %%I in (%*) do` to process all files specified as arguments on starting a batch file which happens when one or more files are dragged & dropped on a batch file. But test in your batch file if being started at all with at least 1 argument, see my updated answer. And after command `copy` you need to add `endlocal` command or you run into a stack overflow on too many files to process. Read [this answer](https://stackoverflow.com/a/38676582/3074564) for details about the commands __SETLOCAL__ and __ENDLOCAL__. – Mofi Dec 08 '17 at 12:49
1

Here is a complete batch code for this task using mainly arithmetic expressions and string concatenations to determine the target folder path for each *.7z file in source folder, or alternatively for each file of which name is passed via an argument to the batch file.

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem Source path is the directory of the batch file ending with a backslash.
set "SourcePath=%~dp0"
rem Target path is a UNC path also ending with a backslash.
set "TargetPath=\\nas01\archive\"

rem Run subroutine ProcessFile for each *.7z file in source folder
rem if not at least 1 file was specified on command line to process.
rem Otherwise process the files of which names are passed as arguments
rem to this batch file on starting it. 
if "%~1" == "" (
    for %%I in ("%SourcePath%*.7z") do call :ProcessFile "%%I"
) else (
    for %%I in (%*) do call :ProcessFile "%%~I"
)

rem Restore previous environment and exit this batch file.
endlocal
goto :EOF


rem The subroutine ProcessFile determines the target folders based on
rem first part of the file name separated with one or more spaces from
rem rest of the file name which should be a positive integer number.

rem File names not starting with a valid number are copied (or moved)
rem to the folder _0000-0249\_0000-0249 in specified target folder.

rem For file numbers less than 1000 an extra code is added to copy (or move)
rem those files into folders also having at least 4 digit numbers in name.

:ProcessFile
for /F %%J in ("%~n1") do set "FileNumber=%%J"
set /A FolderNumber1=(FileNumber / 1000) * 1000
set /A FolderNumber2=FolderNumber1 + 999

set /A FolderNumber3=FileNumber - FolderNumber1
if %FolderNumber3% LSS 250 (
    set "FolderNumber3=%FolderNumber1%"
    set /A FolderNumber4=FolderNumber1 + 249
    goto BuildFolderPath
)
if %FolderNumber3% LSS 500 (
    set /A FolderNumber3=FolderNumber1 + 250
    set /A FolderNumber4=FolderNumber1 + 499
    goto BuildFolderPath
)
if %FolderNumber3% LSS 750 (
    set /A FolderNumber3=FolderNumber1 + 500
    set /A FolderNumber4=FolderNumber1 + 749
    goto BuildFolderPath
)
set /A FolderNumber3=FolderNumber1 + 750
set "FolderNumber4=%FolderNumber2%"

:BuildFolderPath
if %FolderNumber1% == 0 (
    set "FolderNumber1=0000"
    set "FolderNumber2=0%FolderNumber2%"
    set "FolderNumber4=0%FolderNumber4%"
    if %FolderNumber3% == 0 (
        set "FolderNumber3=0000"
    ) else (
        set "FolderNumber3=0%FolderNumber3%"
    )
)
set "TargetFolder=%TargetPath%_%FolderNumber1%-%FolderNumber2%\_%FolderNumber3%-%FolderNumber4%"

mkdir "%TargetFolder%" 2>nul
copy /Y "%~1" "%TargetFolder%\" >nul
rem move /Y "%~1" "%TargetFolder%\" >nul
goto :EOF

A subroutine is used instead of doing everything within FOR loop as this makes the task much easier to code because of GOTO can be used in the subroutine. And with a subroutine it is not necessary to use delayed environment variable expansion which is helpful in case of a *.7z file ever contains an exclamation mark.

The main advantage of this solution: it works with numbers in range 0 to 2147482999 in file name.

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 /?
  • copy /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • mkdir /?
  • move /?
  • rem /?
  • setlocal /?

Read also the Microsoft article about Using Command Redirection Operators explaining >nul and 2>nul used to redirect success and error messages to device NUL to suppress them.

Mofi
  • 46,139
  • 17
  • 80
  • 143