Create Folder and Optional SubFolder based on file name using batch file - batch-file

There are two areas where I need some guidance and not sure if this is possible or not. Please note that below code is working as intended.
#echo off
setlocal
set "sourcedir=C:\FILETEST"
pushd %sourcedir%
for /f "tokens=1,2*delims=-" %%a in (
'dir /b /a-d *-*--*.*'
) do if "%%c" neq "" (
md "%%a-%%b"
move "%%a-%%b--%%c" ".\%%a-%%b\"
)
popd
goto :eof
The above code caters for file names have a "-" and "--", for example filename "UK-London--filename.doc" would create a folder "UK-London" and put the file there.
However, there are also other filenames that are more detailed, for example filename "EU-UK--London-Camden--filename.doc". The below script works for this example and will create a folder for "EU-UK", then a subfolder "London-Camden" and put the file there:
#echo off
setlocal
set "sourcedir=c:\FILETEST"
pushd %sourcedir%
for /f "tokens=1,2,3,4*delims=-" %%a in (
'dir /b /a-d *-*--*-*--*.*'
) do if "%%e" neq "" (
md "%%a-%%b"
md "%%a-%%b/%%c-%%d"
move "%%a-%%b--%%c-%%d--%%e" ".\%%a-%%b\%%c-%%d\"
)
popd
goto :eof
The first issue that I am having is that I need to use both scripts to go through all files and it sometimes is a hit and miss. Is there a way to combine the two scripts with an "if exist" statement?
The second issue and more of a question is currently the delims is set to "-", can I add another delims e.g. "_"?
Thank you and please let me know if any questions.
Panos

#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*.doc" '
) DO (
SET "filename=%%~na"
FOR /f "tokens=1-3delims=:" %%m IN ("!filename:--=:!") DO (
IF "%%o"=="" (
MD "%destdir%\%%m" 2>NUL
ECHO move "%sourcedir%\%%a" "%destdir%\%%m\"
) ELSE (
MD "%destdir%\%%m\%%n" 2>NUL
ECHO move "%sourcedir%\%%a" "%destdir%\%%m\%%n\"
)
)
)
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances.
I'm assuming all of your .doc files fit the masks provided.
The dir command produces a list of all of the .doc files (obviously, provide a different mask if required). Each name is then assigned to %%a and the name part only to filename.
Using delayedexpansion, convert each -- to : (a character that can't exist in a filename) and use the delims=: to tokenise to %%m..%%o. Then check whether the third token (and hence second level) exists and perform the appropriate create and move.
Note that md one\two will create one if it doesn't already exist. The 2>nul suppresses complaints that 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)
As for the delims issue, any character between the = and " is defined as a delimiter; all ranking equally. The line will be interpreted as delims token1 delims token2 ... where the delims string is a sequence of (any of (any of the delimiters))

I'm thinking you could use a more general approach: whatever the filename happens to be, change all occurrences of "--" to "\" so that you create a pathname (full file spec such as C:\path\to\name.ext). Then extract the parent directory from the pathname, and move the file accordingly.
Something like this (not tested):
setlocal enabledelayedexpansion
for %%a in (*) do (
set "pathname=%%a"
set "pathname=!pathname:--=\!"
for %%b in ("!pathname!") do set "parent=%%~dpb"
md !parent!
move "%%a" "!pathname!"
)

Related

Renaming file based on the content of two values from the first line

I have files that its first line would look like this:
VND|OVERSTOCKL|OSH|004010
It is a pipe delimiter and only the first three characters remain the same among all files.
I need a batch file that reads this first line and if, and only if it finds "OVERSTOCKL", it will rename the file from ####.### to OVERSTOCKL856.DAT
The 856 is based on OSH at the third place from the fist line.
If OIN, then it should be OVERSTOCKL810.DAT
If OIB, then it should be OVERSTOCKL846.DAT
If OPR, then it should be OVERSTOCKL855.DAT
If OVERSTOCKL is not found, it should do nothing to the file name.
Can this be done?
Thanks.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir\t w o"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*" '
) DO (
SET "processed="
SET "name="
FOR /f "usebackqtokens=1-4delims=|" %%t IN ("%sourcedir%\%%a") DO (
IF NOT DEFINED processed IF "%%u"=="OVERSTOCKL" (
IF "%%v"=="OIN" SET "name=810"
IF "%%v"=="OIB" SET "name=846"
IF "%%v"=="OPR" SET "name=855"
IF "%%v"=="OSH" SET "name=856"
)
SET "processed=Y"
)
IF DEFINED name CALL :CHANGE "%%a"
)
GOTO :EOF
:CHANGE
IF "%~1"=="OVERSTOCKL%name%.dat" GOTO :eof
IF EXIST "%sourcedir%\OVERSTOCKL%name%.dat" (
ECHO OVERSTOCKL%name%.dat already exists naming "%~1"
GOTO :eof
)
ECHO(REN "%sourcedir%\%~1" "OVERSTOCKL%name%.dat"
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances.
The required REN commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(REN to REN to actually rename the files.
Perform a directory scan of the source directory, in /b basic mode /a-d without directories and assign each filename found to %%a.
For each filename in %%a, set name and processed to nothing and read a line from the file. If the second token is as specified then set name to the potential new name from the third token.
Setting processed to any value ensures that only the very first line is read.
If name is set, renaming is required and executed by the subroutine :change.
If the filename being processed has already been examined, don't bother trying to rename. If the destination name exists already, produce an error line and if not, do the rename.
This solution assumes that the "VND|OVERSTOCKL|" string can appear at first line only; if this is not true, a small modification is required.
#echo off
setlocal EnableDelayedExpansion
rem Define the equivalence numbers
set "number[OSH]=856"
set "number[OIN]=810"
set "number[OIB]=846"
set "number[OPR]=855"
rem Process files that have "VND|OVERSTOCKL|" string at beginning of line only
cd "D:\Folder"
for /F "tokens=1,3,4 delims=:|" %%a in ('findstr /B "VND|OVERSTOCKL|" *.*') do (
rem FINDSTR output have this format: "FILENAME.EXT:VND|OVERSTOCKL|OSH|004010"
rem Do the required rename
ECHO ren "%%a" "%%b!number[%%c]!.DAT"
)
The ren command is just echoed to the screen. If the output is correct, remove the ECHO part in order to execute the rename.

How do I create a list of folder names which contain at least one file of a given type

I have a large directory \books.
In that directory are many subdirs. Some of these subdirs contain MP3s.
I want to be able to print a list of each folder name where the folder contains at least one MP3.
The other caveat is that some of these folders themselves contain subdirs where users created a subdir for each -chapter-. Eg:
\mybook
\chapter1
\chapter2
..etc...
So the script needs to check for the existence of an MP3 either in the folder OR go down one more subdir level and then if it finds an MP3, print the parent folder name and exit back.
I hope all that made sense. I'd know how to do this with a 'real' programming language but I'm stumped in DOS batch land.
I got this far and gave up.
#echo off
for /F %%i in ('dir /s/b "g:\books\*.mp3"') do (
echo %%i
)
...which gives me the first word of the file folder but also recurses so I get the name of each chapter (which I do not want).
How do I get the -full- folder name?
How do I get it to ONLY search down one level and return the parent folder name (which is the book title).
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
FOR /f "delims=" %%a IN (
'dir /b /ad "%sourcedir%\*" '
) DO (
IF EXIST "%sourcedir%\%%a\*.mp3" (ECHO %%a
) ELSE (
SET "found="
FOR /f %%m IN (
'dir /b /ad "%sourcedir%\%%a\*"'
) DO IF NOT DEFINED found IF EXIST "%sourcedir%\%%a\%%m\*.mp3" (
SET found=y
ECHO %%a
)
)
)
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
for/f by default assigns the first token to the metavariable. The default delimiter includes space, so turning delimiters off with the "delims=" option will deliver the entire line.
So - outer loop: get a directory-list in basic form (/b) of all of the directorynames (/ad) in the source directory; assign each in turn to %%a.
If the directory contains a filename *.mp3, report it, otherwise
Inner loop: set "not found" status; same deal - find subdirectory-names of %%a in %%m, if the required file exists, report it and flag "found" status to prevent further reports if more than one subdirectory of %%a contains the required filename.
#echo off
setlocal
rem Accumulate .mp3 files in folders one level below "books" one
cd /D g:\books
for /D %%d in (*) do (
for /F %%i in ('dir /s/b "%%d\*.mp3"') do (
set /A folder[%%~Pd]+=1
)
)
rem Show results
for /F "tokens=2,3 delims=[]=" %%a in ('set folder[') do echo %%a = %%b .mp3 files
This method will fail if the full folder names contain spaces or hypens. This point may be fixed, if needed.

copy & rename files to other folder

I have some of log files formatted like this "name.log"
I would like to copy those from one folder to another folder like
xcopy /y "C:\Folder1" "D:\Folder2"
And i need to rename file with created date of original file (no copy file) so that the text file in Folder2 would be like "name yyyymmddhhmm.log" if some file has the same name (date of creation) it will be overwritten.
The code:
set Source=C:\Users\user1\Desktop\Folder1
set Dest=D:\Folder2
if not exist %Dest% md %Dest%
for /F %%a in ('dir /b "%Source%\*.txt"') do call :Sub %%a
goto :eof
:Sub
set "filename=%1"
for /F %%s in ("%Source%\%1") do if %%~zs==0 goto :eof
set "datepart="
FOR /F "tokens=1-5 delims=/-: " %%a IN ('dir /tc "%filename%" ^| findstr "%filename%"') DO (
IF "%%c" neq "" SET "datepart=%%c%%a%%b%%d%%e"
)
FOR /F %%a IN ("%filename%") DO (
set "NewName=%%~na %datepart%%%~xa"
)
xcopy /y "%Source%\%filename%" "%Dest%\%NewName%*"
GOTO :EOF
The problem is that If I don't put the .bat in the same folder that origin files (Folder1),the files aren't change name. For example, if it is out, the files change name with old name and one white space.
The command windows tell me that it doesn't find the file when it get the creation date.
If I put the script into folder1 it works well.
On the other hand, if I execute the script with "Task Scheduler" I have the same problem. The files are copied but without date of creation.
What do I need to solve this problem?
#ECHO OFF
SETLOCAL
set Source=C:\Users\user1\Desktop\Folder1
set Dest=D:\Folder2
set "Source=u:\sourcedir\t w o"
set "Dest=u:\destdir"
if not exist "%Dest%" md "%Dest%"
for /F "delims=" %%k in ('dir /b "%Source%\*.log"') do call :Sub "%%k"
goto :eof
:Sub
SET "newname=%~1"
for /F "delims=" %%s in ("%Source%\%~1") do (if %%~zs==0 goto :eof
FOR /F "tokens=1-5 delims=/-: " %%a IN ('dir /tc "%Source%\%~1" ^| findstr "%~1"') DO (
IF "%%c" neq "" SET "newname=%%~ns %%c%%a%%b%%d%%e%%~xs"
)
)
ECHO(xcopy /y "%Source%\%~1" "%Dest%\%NewName%"
GOTO :EOF
The required XCOPY commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(XCOPY to XCOPY to actually copy the files. Append >nul to suppress report messages (eg. 1 file copied)
This may seem quite a radical change, but actually it really isn't.
First issue is that I overrode your directory settings with directories to suit my system. The syntax SET "var=value" (where value may be empty) is used to ensure that any stray trailing spaces are NOT included in the value assigned. set /a can safely be used "quoteless".
Using quotes in the md command makes the procedure immune to "paths containing separators" - I test using spaces in paths and filenames because that appears to be a popular thing to do.
I changed the directory-scan metavariable from %%a to %%k to avoid confusion with the %%a in the subroutine. Your text says that you are starting with &.log files, but your filemask was *.txt so I changed it to *.log. Quoting the parameter delivered to :Sub means the procedure will receive the entire name of the file if it contains spaces.
Within the subroutine, it would appear that yowant no name-change if the %%c part from the dir/tc scan is empty. %~1 is the supplied filename minus the quotes.
The outer loop in %%s : I added delims= to cater for spaces in filenames and used %~1 in preference to %filename%
Within the %%s block, %%s refers to the file, so you can use %%s and its modified forms like %%~zs to refer to that file's characteristics - which unfortunately do not include create-date (%%~ts contains the last-update date - you may be able to use that in te following line rather than dir and findstr)
Then as #aschipfi suggested, include the source directory in the dir otherwise the dir takes place on the current directory.
FOR /F "tokens=1-5 delims=/-: " %%a IN ("%%~ts") DO (
should work for you if you can use last-update-date in place of create-date.
So - if %%c is not empty, set the new name to (the name part of the file in %%s)+space+the date string+(the extension in %%s)
And then do the xcopy - using the old name unless it was modified.

Unflattening a directory structure based on filenames

This is the inverse of the "flatten" operation described in this question: Flattening a directory
I would like a batch script that will go through each file in a "flattened" directory and put them back into their original directories, creating the directories as necessary
So if the following files were in my folder:
images-nature-dcim001.jpg
images-nature-dcim002.jpg
images-dcim003.jpg
images-indoors-dcim004.jpg
It would produce the resulting directory structure, creating the directories and moving (or copying) the files into the correct folder.
images
dcim003.jpg
nature
dcim001.jpg
dcim002.jpg
indoors
dcim004.jpg
Note: the example uses hyphens to separate the directories, but they can be any character.
This works here. It creates the four files at the top and then moves them.
#echo off
type nul >images-nature-dcim001.jpg
type nul >images-nature-dcim002.jpg
type nul >images-dcim003.jpg
type nul >images-indoors-dcim004.jpg
for %%a in (*.jpg) do call :routine "%%a"
pause
goto :eof
:routine
set "a=%~1"
set "b=%a:-=\%"
for %%b in ("%b%") do (
md "%%~pb" 2>nul
move "%a%" "%%~pb\%%~nxb"
)
#ECHO OFF &SETLOCAL
FOR /f "delims=" %%a IN ("%cd%") DO SET "precur=%%~dpa"
FOR /f "delims=" %%a IN ('dir /b /s /a-d *.txt') DO (
SET "fname=%%~fa"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "nname=!fname:%precur%=!"
SET "nname=!nname:\=-!"
ECHO REN "!fname!" "!nname!"
ENDLOCAL
)
Here's the version I am using based on foxidrive's approach to getting the directory name. I didn't think of simply replacing delimiters with back-slashes.
#echo off
Setlocal EnableDelayedExpansion
rem // Directory Unflatten
rem // recursively unflattens directories
rem // and prepends the directory name to
rem // the filename
rem // Configuration options
rem // * Files to search for
set pattern=*jpg;*.png
rem // * Directory name delimiter
set delim=-
rem // Perform moving
for %%X in (%pattern%) do (
set A=%%X
rem // Replace delimiter with back-slash
set b=!A:%delim%=\!
rem // Not sure how to clean this up
for %%B in ("!b!") do (
if not exist %%~pB (
md "%%~dpB"
)
move "!A!" "%%~dpB%%~nxB"
)
)

Batch: Rename multiple files using input string

I am trying to rename files after the user inputs a string they want to remove from the file name. This works fine except when I want to rename files that are in a different location than the script:
Here is what i have so far which works if I dont specific the file path (e.g. remove C:\DATABASE\*.* /s)
SET /P X=Type in the String that you want to remove and then press ENTER:
set deletestring=%X%
for /f "delims==" %%F in ('dir C:\DATABASE\*.* /s /b ^| find "%deletestring%"') do (
set oldfilename=%%F
set newfilename=!oldfilename:%deletestring%=!
Ren "!oldfilename!" "!newfilename!"
)
Thanks!
Use this instead. e.g.:
remove *.*
or
remove "relative path\*.*"
or
remove C:\DATABASE\*.*
or
remove "C:\My Database\2010-*.bak"
Meaning that a directory and file mask must be specified. Here's the remove.bat file:
#echo off
setlocal enabledelayedexpansion
set mask=%~1
set mask=!mask:%~dp1=!
if not exist "%~1" (
echo No files found
goto :eof
)
pushd "%~dp1"
SET /P X=Type in the String that you want to remove and then press ENTER:
set deletestring=%X%
for /f "delims==" %%F in ('dir "%mask%" /s /b ^| find "%deletestring%"') do (
set oldfilename=%%F
set newfilename=!oldfilename:%deletestring%=!
Ren "!oldfilename!" "!newfilename!"
)
Your primary problem you are running into is that the 1st argument to REN can accept full path info, but the 2nd can only contain the new name without path info. You can use the ~nx modifier to extract the name and extension from the full path reported by the FOR /F command.
Your FOR /F options are not reliable - it will break if the file name contains =. You want to set delims to nothing instead.
This problem is actually more complicated than it first looks. Your code will attempt to rename both files and directories. If you want to rename the directories then you must rename in reverse alpha order because the entire list is built before any thing is renamed. If you process in normal alpha order and rename a directory, then subsequent entries within that directory will not be found.
The FIND filter in the IN() clause is not necessary. Ideally your filter should only match the file or directory name, not the path. That is doable, but a bit tricky. I would simply skip the filtering in the IN() clause and do it in the DO clause.
A file or directory name can contain ! character. But the FOR variable expansion will be corrupted if it contains ! and delayed expansion is enabled. The problem can be avoided by toggling delayed expansion on and off within the loop.
It is possible for the entire name to be removed by the search and replace, but you cannot rename a file to nothing. So I added a test to ensure there is a name left.
setlocal disableDelayedExpansion
SET /P "X=Type in the String that you want to remove and then press ENTER:"
for /f "delims=" %%F in ('dir C:\DATABASE\* /s /b ^| sort /r') do (
set "old=%%F"
set "file=%%~nxF"
setlocal enableDelayedExpansion
set "new=!file:%X%=!"
if defined new if !new! neq !file! ren "!old!" "!new!"
endlocal
)
If you don't really want to rename directories then you need to add the /A-D option. I first thought you could use a FOR /R statement, but that could potentially cause the same file to be renamed twice. FOR /F buffers the entire result set before processing any files, but FOR /R does not.
setlocal disableDelayedExpansion
SET /P "X=Type in the String that you want to remove and then press ENTER:"
for /f "delims=" %%F in ('dir C:\DATABASE\* /s /b /a-d') do (
set "old=%%F"
set "file=%%~nxF"
setlocal enableDelayedExpansion
set "new=!file:%X%=!"
if defined new if !new! neq !file! ren "!old!" "!new!"
endlocal
)

Resources