Avertheus
Avertheus

Reputation: 137

Batch Move based on filename without Delimiters, String Only

Okay, Windows 7 Enterprise x64 here with a Windows batch file question. I am a somewhat basic user with little knowledge of batch file creation. I've modified existing scripts I've found online to my use, that is about it.

I have a piece of software that dumps it's output to a folder with file names concatenated from the project title, current system date, current system time, and project settings.

It will export two files in this format every time the project is saved:

PROJECTTITLE_2016_10_07__09_45_11__A_B_C.iges
PROJECTTITLE_2016_10_07__09_45_11__A_B_C.step

The A, B, and C representing switches used in the software that are specific to the project. These may exist or may not exist as in the file name can be _R_F or _R_F_Z etc. "PROJECTTITLE" can literally be anything. This is where my problem arises using delimiters. You could potentially have file names like all of the following (using real examples from users):

11475shacklebody_2016_10_07__09_45_11__R_F.iges
11475shacklebody_2016_10_07__09_45_11__R_F.step
test_EFMflow_2016_10_07__09_45_11__R_Z.iges
test_EFMflow_2016_10_07__09_45_11__R_Z.step
untitled16_2016_10_07__09_45_11__R.iges
untitled16_2016_10_07__09_45_11__R.step
#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.iges
#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.step
prooftestwithoutupperlandsimproved-4_2016_10_07__09_45_11.iges
prooftestwithoutupperlandsimproved-4_2016_10_07__09_45_11.step

What I want to do is move both the IGES and STEP files from the output folder on the C drive to a new folder on a networked drive with name based on "PROJECTTITLE," creating that folder if needed. I have already task scheduled a -delim based batch to run every night, but found delimiters to not be sufficient for my use.

While it seems like an easy job to do with delimiters, looking for the first underscore, I have had projects with underscores in their names that screws up the folder naming. Since delimiters only work for individual characters, not strings, I have been looking for examples of batch files I can adapt to my use with no luck finding any similar to what I want to do that do not use delimiters.

It seems simple, search for string "_2016," in filename, and take all characters before "_2016" and create a new directory, placing files containing those characters in that folder. I am lost as to how to do this without delimiters though.

What I have working so far, using delimiters looking for underscores, creating folders and moving to those folders on a mapped drive:

for /f "delims=_" %%V in ('dir /b /a-d C:\Output\*_*.iges') do (
 mkdir "I:\ENG\PARTS\%%V" 2>nul
 move "C:\Output\%%V_*.iges" "I:\ENG\PARTS\%%V" >NUL 2>nul
 move "C:\Output\%%V_*.step" "I:\ENG\PARTS\%%V" >NUL 2>nul
)

It is a very simple batch file for what it does, but falls flat on it's face with titles containing underscores.

A few things:

Searching for _2016 would mean the batch file would need yearly maintenance. Could we either search for system %YEAR% or somehow search for "_####_##_##_" with something like a regular expression to get away from searching for system date or a specific year?

EDIT: While not ideal as the switches are useful, I found a way in the software to not export the project specific switches at the end of the file name. So now the export would be:

PROJECTTITLE_2016_10_07__09_45_11.iges
PROJECTTITLE_2016_10_07__09_45_11.step

Which I know I can trim from right, what, 21 characters, use that name to make a folder and search the directory for all files containing that trimmed string.

It still would be nice to figure out how to search for the specific string though with the switches intact. I am willing to learn, so throw all explanations of your code you can at me.

Double (Late) EDIT: First, thank you all for answering this, I deeply appreciate your help.

Double underscores in the the filename are quite rare, but do happen, probably due to typos. It becomes more of an issue with the volume of files I'm planning to move with this. The software runs on maybe 100 user machines with 20 or so being heavy users, each saving up to maybe 1000 files a day. The software will automatically save in increments, as the user runs the simulation. I'm trying to give them a way to collaborate and view each other's simulation results over the network (that we actively encourage them to use) and it figures that the software is hard coded to output to the C drive (for "performance" as the vendor tells me). I'm looking into scheduling the files to move every 15 minutes on every PC in their OU instead of nightly to give a near-real-time view on what they are working on.

Another option I explored was symbolically linking the output folder on each machine to the software's folder network drive, but found that this really doesn't solve the user's "organization" issue, putting like project runs in the same folder. This also has the issue of not allowing them to save a project out of the office without VPN, which some users do do, then move their files over when they get back in the office.

Thank you for your assistance.

Upvotes: 2

Views: 445

Answers (4)

aschipfl
aschipfl

Reputation: 34989

Supposing the PROJECTTITLE part of the file names does not contain two consecutive underscores, you could use the following script, which splits off the first occurrence of __ and everything after (so the time part and the optional switches are removed), using a standard for loop rather than for /F; then it splits off another 11 characters (hence the remaining date part), using sub-string expansion:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_SOURCE=."
set "_TARGET=."

for /F delims^=^ eol^= %%F in ('
    pushd "%_SOURCE%" ^&^& ^(
    dir /B /A:-D ^
        "*_????_??_??__??_??_??*.iges" ^
        "*_????_??_??__??_??_??*.step" ^
    ^& popd^)
') do (
    call :PROCESS "%%F"
)

endlocal
exit /B


:PROCESS  val_file
setlocal DisableDelayedExpansion
set "FILE=%~1"
setlocal EnableDelayedExpansion
for %%I in ("!FILE:__=";"!") do (
    endlocal
    set "ITEM=%%~I"
    setlocal EnableDelayedExpansion
    goto :NEXT
)
:NEXT
if defined ITEM (
    set "ITEM=!ITEM:~,-11!"
    md "%_TARGET%\!ITEM!" 2> nul
    if not exist "%_TARGET%\!ITEM!\!FILE!" (
        move /Y "%_SOURCE%\!FILE!" "%_TARGET%\!ITEM!\" > nul
    )
)
endlocal
endlocal
exit /B

Finally I come up with a script that is able to handle even files whose PROJECTTITLE part contain __ on their own. It splits off the last occurrence of __ and everything after (so the optional switches are removed, if any, or the time part is removed otherwise), using a standard for loop rather than for /F; then it splits off another 21 characters (hence the remaining date and time parts), or, if no optional switches were present, just another 11 characters (hence the remaining date part), using sub-string expansion:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_SOURCE=."
set "_TARGET=."

for /F delims^=^ eol^= %%F in ('
    pushd "%_SOURCE%" ^&^& ^(
    dir /B /A:-D ^
        "*_????_??_??__??_??_??*.iges" ^
        "*_????_??_??__??_??_??*.step" ^
    ^& popd^)
') do (
    call :PROCESS "%%F"
)

endlocal
exit /B


:PROCESS  val_file
setlocal DisableDelayedExpansion
set "FILE=%~1"
set "BASE=%~n1"
set "NAME=" & set "ITEM="
setlocal EnableDelayedExpansion
for %%I in ("!BASE:__=";"!") do (
    for /F "delims=" %%E in ("!NAME!!ITEM!__") do (
        endlocal
        set "NAME=%%E"
        set "ITEM=%%~I"
        setlocal EnableDelayedExpansion
    )
)
if defined NAME (
    set "NAME=!NAME:~2,-2!"
    echo("!ITEM!"| > nul findstr /R "^\"[0-9][0-9]_[0-9][0-9]_[0-9][0-9]\"$" || (
        if defined NAME set "NAME=!NAME:~,-10!"
    )
    if defined NAME set "NAME=!NAME:~,-11!"
    if defined NAME (
        md "%_TARGET%\!NAME!" 2> nul
        if not exist "%_TARGET%\!NAME!\!FILE!" (
            move /Y "%_SOURCE%\!FILE!" "%_TARGET%\!NAME!\" > nul
        )
    )
)
endlocal
endlocal
exit /B

Upvotes: 1

JosefZ
JosefZ

Reputation: 30258

Next batch script should do the job even if a file name contains cmd poisonous characters like space or % percent sign etc.

@ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion

set "_fouts=C:\Output"           your setting
set "_fouts=D:\test\39924063"    my setting 

for /F %%G in ('wmic OS get localdatetime ^|find "."') do set "_fyear=%%G"

pushd "%_fouts%"
  set "_fyear=%_fyear:~0,4%"
  call :doDir
  set /A _fyear -= 1
  call :doDir
popd

ENDLOCAL
goto :eof

:doDir
rem debugging output echo(%_fyear%
for /f "delims=" %%V in ('
  dir /b /a-d "*_%_fyear%_*.iges" "*_%_fyear%_*.step" 2^>NUL') do (
    set "_fname=%%~nV"           filename without extesion
    set "_fexte=%%~xV"           extesion only
    call :doAll
)
goto :eof

:doAll
    call set "_ftail=%%_fname:*_%_fyear%_=%%"
    call set "_fproj=%%_fname:_%_fyear%_%_ftail%=%%"
    rem debugging output echo("%_fproj%" "%_fname%" "%_fexte%"
    ECHO mkdir "I:\ENG\PARTS\%_fproj%" 2>nul
    ECHO move "%_fname%%_fexte%" "I:\ENG\PARTS\%_fproj%\"
goto :eof

Output (note that operational mkdir and move commands are merely displayed for debugging purposes using ECHO mkdir and ECHO move, respectively):

==> dir /B /S "D:\test\39924063" /S
D:\test\39924063\#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.iges
D:\test\39924063\#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.step
D:\test\39924063\%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.iges
D:\test\39924063\%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.step

==> D:\bat\SO\39924063.bat
mkdir "I:\ENG\PARTS\#14drop_wire-edm"
move "#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.iges" "I:\ENG\PARTS\#14drop_wire-edm\"
mkdir "I:\ENG\PARTS\%PROJECT TITLE"
move "%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.iges" "I:\ENG\PARTS\%PROJECT TITLE\"
mkdir "I:\ENG\PARTS\#14drop_wire-edm"
move "#14drop_wire-edm_2016_10_07__09_45_11__R_F_Z.step" "I:\ENG\PARTS\#14drop_wire-edm\"
mkdir "I:\ENG\PARTS\%PROJECT TITLE"
move "%PROJECT TITLE_2016_10_07__09_45_11__A_B_C.step" "I:\ENG\PARTS\%PROJECT TITLE\"

==>

Resources (required reading, incomplete):

Upvotes: 1

Compo
Compo

Reputation: 38719

This version assumes there will not be an instance of _YYYY_ in the PROJECTTITLE.

@Echo Off
SetLocal EnableExtensions DisableDelayedExpansion

(Set OutDir=I:\ENG\PARTS)
(Set SrcDir=C:\Output)

If Not Exist "%SrcDir%\" Exit/B 1
If Not Exist "%OutDir%\" Exit/B 1
For /F "EOL=Y" %%A In ('WMIC Path Win32_LocalTime Get Year') Do (
    For /F "Delims=" %%B In ("%%A") Do Set "ThisYr=%%B")
For /F "Delims=" %%A In ('Where "%SrcDir%:*_%ThisYr%_*.iges"') Do (
    If Exist "%%~dpnA.step" Call :Sub %%~nA)
Exit/B

:Sub
Set "BigTit=%~1"
For /F "Delims=" %%A In ('CMD /Q /C "Call Echo %%BigTit:_%ThisYr%_=&:%%"') Do (
    If Not Exist "%OutDir%\%%A\" MD "%OutDir%\%%A"
    Move "%SrcDir%\%~1*.*" "%OutDir%\%%A">Nul)

Edit, there is no need to export without the potentially useful project switches using the above code.

Upvotes: 1

Magoo
Magoo

Reputation: 80213

@ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
REM (
FOR /f "delims=" %%a IN (
 'dir /b /a-d "%sourcedir%\*__*" '
 ) DO (
IF /i "%%~xa"==".iges" CALL :process "%%a"
IF /i "%%~xa"==".step" CALL :process "%%a"
)
REM )>"%outfile%"

GOTO :EOF

:: Process filename "%1"
:process
SET "fullname=%~1"
SET "junk=%fullname:*__=%"
CALL SET "project=%%fullname:%junk%=%%"
SET "project=%project:~0,-13%
ECHO(MD "%destdir%\%project%"
ECHO(MOVE "%sourcedir%\%~1" "%destdir%\%project%\"
GOTO :eof

You would need to change the settings of sourcedir and destdir to suit your circumstances.

The required MD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MD to MD to actually create the directories. Append 2>nul to suppress error messages (eg. when the directory already exists)

The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)

This approach simply performs a directory list without directorynames (/a-d) of each file in the source directory that contains a double-underscore. Each filename matching is assigned to %%a and if the extension part (%%~xa) is one of the targets, then process the filename.

Processing consists of removing all of the characters before the double-underscore and then removing that junk part from the full name, giving project+date. Remove the last 13 characters and you have your project name.

Will have problems with any filename containing certain symbols like % or = but should be fine with underscores.

Upvotes: 1

Related Questions