I have some batch script code to recursively copy the contents of a folder and its sub-folders into a singular folder :
for /r %f in (*) do #copy "%f" .
I would like to change the behavior slightly, so that if two or more files have the same name, I would like to copy only the one which has the longest file path (in terms of how many levels deep it is, not the character length).
eg if we had :
C:/Desktop/Folder/File1
and
C:/Desktop/Folder/NewFolder/File1
in this case, we would take the second file and copy it into the new folder.
I don't believe this is possible with a batch script, though I may be incorrect in this.
Thanks.
you may be already there, because FOR /R will recurse in the same branch from top to bottom, you will end with the deepest file just by force overwriting previously copied files
for /r %f in (*) do #copy /Y "%f" \destination
The copy operations in this code are only echoed to console. If the output is correct, remove the echo command before copy
#echo off
setlocal enableextensions disabledelayedexpansion
:: CONFIGURATION
rem Source and target folders for copy operation
set "sourceFolder=%cd%"
set "targetFolder=x:\somewhere"
rem Temporary files needed
set "tempList=%temp%\%~nx0.%random%%random%.tmp"
set "endList=%tempList%.end"
rem Variable used for level padding
set "level=10000"
:: SEARCH FILES
rem For each folder under the indicated source
(for /r "%sourceFolder%" /d %%a in (.) do (
rem Retrieve the deep level splitting the folder name
rem and counting the segments
set "folder=%%a"
setlocal enabledelayedexpansion & for %%b in ("!folder:\=" "!") do set /a "level+=1"
rem For each file in the folder output filename, level and full file path
rem A pipe character is used as delimiter between the tokens
for %%b in (!level!) do (
endlocal
for %%c in ("%%~fa\*") do (echo(^|%%~nxc^|%%b^|%%~fc)
)
rem And send everything to a temporary file
)) > "%tempList%"
rem Sort the temporary file on filename and level in descending order.
sort /r "%tempList%" /o "%endList%"
rem Now for each group of files with same name, the first one is the
rem file located in the deepest folder
:: COPY FILES
rem No file has been copied
set "lastFile=|"
rem For each record in the sorted file list, split it in three tokens
rem using the included pipe charactes as delimiters
for /f "usebackq tokens=1-3 delims=|" %%a in ("%endList%") do (
setlocal enabledelayedexpansion
for %%d in ("!lastFile!") do (
endlocal
rem If the name of the file is different to
rem the previous one, we have started a new group and as the first file
rem is the deepest one, copy it and store the new file name
if not "%%a"=="%%~d" (
echo copy /y "%%c" "%targetFolder%"
set "lastFile=%%a"
)
)
)
:: CLEANUP
rem Remove the temporary files
del /q "%tempList%"
del /q "%endList%"
Related
I am trying to create a batch script to move files base on search criteria into another folder of the same subfolder structure.
I tried the following but the result is not quite right.
for /r "c:\Test_Copy\Source1\" %%x in (Test*.txt) do move "%%x" "c:\Test_Copy\Target1\"
As it is showing
move "c:\Test_Copy\Source1\Sub1\Test1.txt" "c:\Test_Copy\Target1\"
move "c:\Test_Copy\Source1\Sub2\Test1.txt" "c:\Test_Copy\Target1\"
I would like the outcome to be the following.
move "c:\Test_Copy\Source1\Sub1\Test1.txt" "c:\Test_Copy\Target1\Sub1\Test1.txt"
move "c:\Test_Copy\Source1\Sub2\Test1.txt" "c:\Test_Copy\Target1\Sub2\Test1.txt"
How will I be able to achieve this?
Thanks
The for /R loop returns full absolute paths, even the ~-modifiers do not allow to return relative paths. However, you could use xcopy /L, which just lists files that it would copy without the /L option, with paths relative to the source root directory; that list can easily be captured and processed by a for /F loop:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_SOURCE=C:\Test_Copy\Source1"
set "_DESTIN=C:\Test_Copy\Target1"
rem // Change into source root directory:
pushd "%_SOURCE%" && (
rem /* Run `xcopy /L` to list files but not actually copy any, because this returns
rem paths relative to the source root directory; then let `for /F` capture that
rem list, split off the preceding drive letter and skip the summary line: */
for /F "tokens=2 delims=:" %%F in ('
xcopy /L /S /Y "Test*.txt" "%TEMP%"
') do (
rem // Create potentially needed sub-directory, suppress errors when it exists:
2> nul md "%_DESTIN%\%%F\.."
rem // Actually move the currently iterated file into the destination directory:
move "%%F" "%_DESTIN%\%%F"
)
rem // Return from source root directory:
popd
)
endlocal
exit /B
The great advantage of this method is that no string manipulation is involved to derive relative paths, which is dangerous as it could fail in certain situations (for instance, when a root path like D:\ is given, or when a path like D:\some\.\source\dummy\..\folder is specified).
Of course you can also use robocopy /L as suggested in a comment by user Mofi:
robocopy "C:\Test_Copy\Source1" "C:\Test_Copy\Target1" "Test*.txt" /S /MOV /NDL /NJH /NJS
#echo off
for /D /R "c:\Test_Copy\Source1\" %%I in (*)do pushd "%%~I" & (
2>nul >nul mkdir "c:\Test_Copy\Target1\%%~nxI"
move "%%~I\Test*.txt" "c:\Test_Copy\Target1\%%~nxI" & popd
)
Outputs command loop results:
move "c:\Test_Copy\Source1\Sub1\test_001.txt" "c:\Test_Copy\Target1\Sub1\test_001.txt"
move "c:\Test_Copy\Source1\Sub1\test_002.txt" "c:\Test_Copy\Target1\Sub1\test_002.txt"
move "c:\Test_Copy\Source1\Sub1\test_003.txt" "c:\Test_Copy\Target1\Sub1\test_003.txt"
move "c:\Test_Copy\Source1\Sub2\test_001.txt" "c:\Test_Copy\Target1\Sub2\test_001.txt"
move "c:\Test_Copy\Source1\Sub2\test_002.txt" "c:\Test_Copy\Target1\Sub2\test_002.txt"
move "c:\Test_Copy\Source1\Sub2\test_003.txt" "c:\Test_Copy\Target1\Sub2\test_003.txt"
The move command results:
c:\Test_Copy\Source1\Sub1\test_001.txt
c:\Test_Copy\Source1\Sub1\test_002.txt
c:\Test_Copy\Source1\Sub1\test_003.txt
3 file(s) moved.
c:\Test_Copy\Source1\Sub2\test_001.txt
c:\Test_Copy\Source1\Sub2\test_002.txt
c:\Test_Copy\Source1\Sub2\test_003.txt
3 file(s) moved.
Obs.: If all your subfolders c:\Test_Copy\Target1\Sub[n] already exist, remove line 2>nul >nul mkdir "c:\Test_Copy\Target1\%%~nxI"
#echo off
For /D /R "c:\Test_Copy\Source1\" %%I in (*)do pushd "%%~I" & (
move "%%~I\Test*.txt" "c:\Test_Copy\Target1\%%~nxI" & popd
)
Try using for /D /R:
FOR /R - Loop through files (recursively)
FOR /D - Loop through several folders/directories
The option /D /R is undocumented, but can be a useful combination, while it will recurse through all subfolders the wildcard will only match against Folder/Directory names (not filenames)
Source linked to ss64.com
Rem :: Set your folder /Directory /Recursively tree starting at "c:\Test_Copy\Source1"
For /D /R "c:\Test_Copy\Source1"
If you want to do it with batch you need to modify the value of %%x to point to the target directory INSIDE the loop. When you do this you can NOT use % to surround the variables you create inside the for loop - you have to use delayed expansion with ! to surround those variables. Then you can use variable replacement on %%x to change it's value.
Like the comments say, this doesn't work if your directory/file names contain more than one exclamation point.
This does what you want I think:
#echo off
setlocal enabledelayedexpansion
set sourcedir=c:\Test_Copy\Source1\
set targetdir=c:\Test_Copy\Target1\
for /r "%sourcedir%" %%x in (Test*.txt) do (
set sourcefile=%%x
set destfile=!sourcefile:%sourcedir%=%targetdir%!
echo move !sourcefile! !destfile!
)
Just change echo move to move when you are ready to actually do the move.
I would like to append recursively folder (and parent folder) names to each *.txt files that folder contains. After that I want to move all files to base folder and delete all folders. I need to achieve this in Windows BATCH script. For examle:
\BaseFolder\A01\B01\EX1.TXT
\BaseFolder\C01\EX2.TXT
\BaseFolder\EX3.TXT
To:
\BaseFolder\A01-B01-EX1.TXT
\BaseFolder\C01-EX2.TXT
\BaseFolder\EX3.TXT
To do this i've found this solution thanks to JosefZ:
Recursively append folder name to the files in Windows batch file
#echo OFF
SETLOCAL EnableExtensions
for /F "delims=" %%G in ('dir /B /S "C:\Source\*.txt"') do (
for %%g in ("%%~dpG.") do rename "%%~fG" "%%~nxg_%%~nxG"
)
pause
where the FOR loops are:
outer %%G loop creates a static list of .txt files (recursively), and
inner %%g loop gets the parent folder of every particular file.
But this solve only a part of my goal. Can anyone help?
Here's a 'fun' idea:
#Set "BaseFolder=C:\Users\Mustafa\BaseFolder"
#ForFiles /P "%BaseFolder%" /S /M *.txt /C "Cmd /C If #IsDir==FALSE For /F 0x22Tokens=*Delims=.\0x22 %%# In (#RelPath)Do #If Not 0x22%%#0x22==#File Set 0x22_=%%#0x22&Call Move /Y 0x22%BaseFolder%\%%#0x22 0x22%BaseFolder%\%%_:\=-%%0x22>NUL"
Please note that this untested solution is very likely to have command line length limitations. I would therefore avoid it if your initial base folder is deep within the volume and/or its tree is deep or carries long file and directory names. Given that cautionary information, please remember to adjust the full path value on line one as necessary.
The following script should accomplish what you want, namely moving and renaming the files as predefined and deleting the remaining empty sub-directories (see all the explanatory rem remarks):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=.\BaseFolder" & rem // (target base directory)
set "_MASK=*.txt" & rem // (file search pattern)
set "_CHAR=-" & rem // (separator character)
rem // Switch to target directory:
pushd "%_ROOT%" && (
rem // Loop through list of relative paths of matching files:
for /F "tokens=1* delims=:" %%E in ('
xcopy /L /S /I "%_MASK%" "%TEMP%" ^| find ":"
') do (
rem // Store current relative file path, initialise variables:
set "FILE=%%F" & set "NAME=" & set /A "NUM=0"
rem // Toggle delayed expansion to avoid trouble with `!` and `^`:
setlocal EnableDelayedExpansion
rem // Loop through all individual elements of file relative path:
for %%I in ("!FILE:\=" "!") do (
endlocal
rem // Store current path element and count them:
set "ITEM=%%~I" & set /A "NUM+=1"
setlocal EnableDelayedExpansion
rem // Build new file name and pass it over `endlocal` barrier:
for /F "delims=" %%N in ("!NAME!%_CHAR%!ITEM!") do (
endlocal
set "NAME=%%N"
setlocal EnableDelayedExpansion
)
)
rem // Finalise new file name:
if defined _CHAR set "NAME=!NAME:*%_CHAR%=!"
rem // Actually move and rename the current file:
> nul move "!FILE!" "!NAME!"
rem // Switch to parent directory of current file:
pushd "!FILE!\.." && (
rem // Loop through parent directory elements:
for /L %%N in (2,1,!NUM!) do (
rem // Try to remove parent directory when empty, go one up:
set "DD=!CD!" & cd ".." & 2> nul rd "!DD!"
)
rem // Return to previous working directory:
popd
)
endlocal
)
rem // return to original working directory:
popd
)
endlocal
exit /B
I want to create a .bat script to copy only one random file from each folder (also subfolders, so recursively) whilst also keeping the folder structure. I've tried the following code which comes close to what I want but doesn't copy the folder structure and one file per folder.
#ECHO OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
SET Destination=H:\Temp
SET FileFilter=.ape
SET SubDirectories=/S
SET Source=%~dp1
SET FileList1Name=FileList1.%RANDOM%.txt
SET FileList1="%TEMP%\%FileList1Name%"
SET FileList2="%TEMP%\FileList2.%RANDOM%.txt"
ECHO Source: %Source%
IF /I {%SubDirectories%}=={/S} ECHO + Sub-Directories
IF NOT {"%FileFilter%"}=={""} ECHO File Filter: %FileFilter%
ECHO.
ECHO Destination: %Destination%
ECHO.
ECHO.
ECHO Building file list...
CD /D "%Source%"
DIR %FileFilter% /A:-D-H-S /B %SubDirectories% > %FileList1%
FOR /F "tokens=1,2,3 delims=:" %%A IN ('FIND /C ":" %FileList1%') DO SET TotalFiles=%%C
SET TotalFiles=%TotalFiles:~1%
ECHO The source has %TotalFiles% total files.
ECHO Enter the number of random files to copy to the destination.
SET /P FilesToCopy=
ECHO.
IF /I %TotalFiles% LSS %FilesToCopy% SET %FilesToCopy%=%TotalFiles%
SET Destination="%Destination%"
IF NOT EXIST %Destination% MKDIR %Destination%
SET ProgressTitle=Copying Random Files...
FOR /L %%A IN (1,1,%FilesToCopy%) DO (
TITLE %ProgressTitle% %%A / %FilesToCopy%
REM Pick a random file.
SET /A RandomLine=!RANDOM! %% !TotalFiles!
REM Go to the random file's line.
SET Line=0
FOR /F "usebackq tokens=*" %%F IN (%FileList1%) DO (
IF !Line!==!RandomLine! (
REM Found the line. Copy the file to the destination.
XCOPY /V /Y "%%F" %Destination%
) ELSE (
REM Not the random file, build the new list without this file included.
ECHO %%F>> %FileList2%
)
SET /A Line=!Line! + 1
)
SET /A TotalFiles=!TotalFiles! - 1
REM Update the master file list with the new list without the last file.
DEL /F /Q %FileList1%
RENAME %FileList2% %FileList1Name%
)
IF EXIST %FileList1% DEL /F /Q %FileList1%
IF EXIST %FileList2% DEL /F /Q %FileList2%
ENDLOCAL
The destination should be set in the .bat code like the code above. Can anybody please help me with this? Thanks in advance!
Copying a directory tree structure (folders only) is trivial with XCOPY.
Selecting a random file from a given folder is not too difficult. First you need the count of files, using DIR /B to list them and FIND /C to count them. Then use the modulo operator to select a random number in the range. Finally use DIR /B to list them again, FINDSTR /N to number them, and another FINDSTR to select the Nth file.
Perhaps the trickiest bit is dealing with relative paths. FOR /R can walk a directory tree, but it provides a full absolute path, which is great for the source, but doesn't do any good when trying to specify the destination.
There are a few things you could do. You can get the string length of the root source path, and then use substring operations to derive the relative path. See How do you get the string length in a batch file? for methods to compute string length.
Another option is to use FORFILES to walk the source tree and get relative paths directly, but it is extremely slow.
But perhaps the simplest solution is to map unused drive letters to the root of your source and destination folders. This enables you to use the absolute paths directly (after removing the drive letter). This is the option I chose. The only negative aspect of this solution is you must know two unused drive letters for your system, so the script cannot be simply copied from one system to another. I suppose you could programatically
discover unused drive letters, but I didn't bother.
Note: It is critical that the source tree does not contain the destination
#echo off
setlocal
:: Define source and destination
set "source=c:\mySource"
set "destination=c:\test2\myDestination"
:: Replicate empty directory structure
xcopy /s /t /e /i "%source%" "%destination%"
:: Map unused drive letters to source and destination. Change letters as needed
subst y: "%source%"
subst z: "%destination%"
:: Walk the source tree, calling :processFolder for each directory.
for /r y:\ %%D in (.) do call :processFolder "%%~fD"
:: Cleanup and exit
subst y: /d
subst z: /d
exit /b
:processFolder
:: Count the files
for /f %%N in ('dir /a-d /b %1 2^>nul^|find /c /v ""') do set "cnt=%%N"
:: Nothing to do if folder is empty
if %cnt% equ 0 exit /b
:: Select a random number within the range
set /a N=%random% %% cnt + 1
:: copy the Nth file
for /f "delims=: tokens=2" %%F in (
'dir /a-d /b %1^|findstr /n .^|findstr "^%N%:"'
) do copy "%%D\%%F" "z:%%~pnxD" >nul
exit /b
EDIT
I fixed an obscure bug in the above code. The original COPY line read as follows:
copy "%%~1\%%F" "z:%%~pnx1" >nul
That version fails if any of the folders within the source tree contain %D or %F in their name. This type of problem always exists within a FOR loop if you expand a variable with %var% or expand a :subroutine parameter with %1.
The problem is easily fixed by using %%D instead of %1. It is counter-intuitive, but FOR variables are global in scope as long as any FOR loop is currently active. The %%D is inaccessible throughout most of the :processFolder routine, but it is available within the FOR loops.
The "natural" way to process a directory tree is via a recursive subroutine; this method minimize the problems inherent to this process. As I said at this post: "You may write a recursive algorithm in Batch that gives you exact control of what you do in every nested subdirectory". I taken the code at this answer, that duplicate a tree, and slightly modified it in order to solve this problem.
#echo off
setlocal
set "Destination=H:\Temp"
set "FileFilter=*.ape"
rem Enter to source folder and process it
cd /D "%~dp1"
call :processFolder
goto :EOF
:processFolder
setlocal EnableDelayedExpansion
rem For each folder in this level
for /D %%a in (*) do (
rem Enter into it, process it and go back to original
cd "%%a"
set "Destination=%Destination%\%%a"
if not exist "!Destination!" md "!Destination!"
rem Get the files in this folder and copy a random one
set "n=0"
for %%b in (%FileFilter%) do (
set /A n+=1
set "file[!n!]=%%b"
)
if !n! gtr 0 (
set /A "rnd=!random! %% n + 1"
for %%i in (!rnd!) do copy "!file[%%i]!" "!Destination!"
)
call :processFolder
cd ..
)
exit /B
Here is anther approach using xcopy /L to walk through all files in the source directory, which does not actually copy anything due to /L but returns paths relative to the source directory. For explanation of the code see all the remarks:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define source and destination directories here:
set "SOURCE=%dp~1"
set "DESTIN=H:\Temp"
rem Change to source directory:
cd /D "%SOURCE%"
rem Reset index number:
set /A "INDEX=0"
rem Walk through output of `xcopy /L`, which returns
rem all files in source directory as relative paths;
rem `find` filters out the summary line; `echo` appends one more line
rem with invalid path, just to process the last item as well:
for /F "delims=" %%F in ('
2^> nul xcopy /L /S /I /Y "." "%TEMP%" ^
^| find ".\" ^
^& echo^(C:\^^^|\^^^|
') do (
rem Store path to parent directory of current item:
set "CURRPATH=%%~dpF"
setlocal EnableDelayedExpansion
if !INDEX! EQU 0 (
rem First item, so build empty directory tree:
xcopy /T /E /Y "." "%DESTIN%"
endlocal
rem Set index and first array element, holding
rem all files present in the current directory:
set /A "INDEX=1"
set "ITEMS_1=%%F"
) else if "!CURRPATH!"=="!PREVPATH!" (
rem Previous parent directory equals current one,
rem so increment index and store current file:
set /A "INDEX+=1"
for %%I in (!INDEX!) do (
endlocal
set /A "INDEX=%%I"
set "ITEMS_%%I=%%F"
)
) else (
rem Current parent directory is not the previous one,
rem so generate random number from 1 to recent index
rem to select a file in the previous parent directory,
rem perform copying task, then reset index and store
rem the parent directory of the current (next) item:
set /A "INDEX=!RANDOM!%%!INDEX!+1"
for %%I in (!INDEX!) do (
xcopy /Y "!ITEMS_%%I!" "%DESTIN%\!ITEMS_%%I!"
endlocal
set /A "INDEX=1"
set "ITEMS_1=%%F"
)
)
rem Store path to parent directory of previous item:
set "PREVPATH=%%~dpF"
)
endlocal
exit /B
For this approach the destination directory can also be located within the source directory tree.
I have a folder structure with a bunch of *.jpg files scattered across the folders.
Now I want to find some files listed in a CSV file (one column only) or a text file line by line like
one.jpg
ten.jpg
five.jpg
and copy all those *.jpg files to another folder keeping the folder structure like:
outputfolder\somefolder\somefolder\one.jpg
outputfolder\somefolder\ten.jpg
outputfolder\somefolder\somefolder\somefolder\five.jpg
How can I achieve this?
Here is what I've tried
#echo off
CLS
REM CHECK FOR ADMIN RIGHTS
COPY /b/y NUL %WINDIR%\06CF2EB6-94E6-4a60-91D8-AB945AE8CF38 >NUL 2>&1
IF ERRORLEVEL 1 GOTO:NONADMIN
DEL %WINDIR%\06CF2EB6-94E6-4a60-91D8-AB945AE8CF38 >NUL 2>&1
:ADMIN
REM GOT ADMIN RIGHTS
COLOR
1F ECHO Please wait...
FOR /R "%~dp0" %%I IN (.) DO
for /f "usebackq delims=" %%a in ("%~dp0list.txt") do
echo d |xcopy "%%I\%%a" "C:\B2B_output_files" /e /i
COLOR 2F
PAUSE
GOTO:EOF
:NONADMIN
REM NO ADMIN RIGHTS
COLOR 4F
pause
GOTO:EOF
Stack Overflow is not a free code writing service, see help topic What topics can I ask about here?
However, I have nevertheless written the entire batch code for this task. Learn from this commented code and next time try to write the batch code by yourself and ask only if you stick on a problem you can't solve by yourself after several trials and not finding a solution on Stack Overflow or any other website.
The paths of source and target base folder must be defined at top of the batch script below.
The text file containing the name of the files to copy line by line must be named FileNames.txt and must be stored in source base folder with using batch code below.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem Define source and target base folders.
rem Note:
rem The paths should not contain an equal sign as then the
rem string substitutions below would not work as coded. The
rem target base folder can be even a subfolder of the source
rem base folder.
set "SourceBaseFolder=C:\Temp"
set "TargetBaseFolder=C:\Temp\OutputFolder"
rem Set source base folder as current directory. The previous
rem current directory is restored by command endlocal at end.
if not exist "%SourceBaseFolder%\*" (
echo %~nx0: There is no folder %SourceBaseFolder%
set "ErrorCount=1"
goto HaltOnError
)
cd /D "%SourceBaseFolder%"
if not exist "FileNames.txt" (
echo %~nx0: There is no file %SourceBaseFolder%\FileNames.txt
set "ErrorCount=1"
goto HaltOnError
)
rem For each file name in text file FileNames.txt in
rem source base folder the loops below do following:
rem 1. Search recursively for a file with current file name
rem in entire directory tree of source base folder.
rem 2. If a file could be found, check its path. Skip the
rem file if the path of found file contains the target
rem folder path to avoid copying files to itself. This
rem IF condition makes it possible that target base
rem folder is a subfolder of source base folder.
rem 3. Create the folders of found file relative to source
rem base path in target base folder. Then check if this
rem was successful by verifying if the target folder
rem really exists and copy the file on existing folder or
rem output an error message on failure creating the folder.
set "ErrorCount=0"
for /F "usebackq delims=" %%N in ("FileNames.txt") do (
for /R %%J in ("%%N*") do (
set "FilePath=%%~dpJ"
if "!FilePath:%TargetBaseFolder%=!" == "!FilePath!" (
set "TargetPath=%TargetBaseFolder%\!FilePath:%SourceBaseFolder%\=!"
md "!TargetPath!" 2>nul
if exist "!TargetPath!\*" (
echo Copying file %%~fJ
copy /Y "%%~fJ" "!TargetPath!" >nul
) else (
set /A ErrorCount+=1
echo Failed to create directory !TargetPath!
)
)
)
)
:HaltOnError
if %ErrorCount% NEQ 0 (
echo.
pause
)
endlocal
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 /? ... for an explanation of %~nx0
copy /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
pause /?
rem /?
set /?
setlocal /?
And read also the Microsoft article about Using command redirection operators to understand 2>nul for suppressing error messages written to STDERR.
The following code should do what you asked for:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LISTFILE=copylist.txt"
set "SOURCE=.\source"
set "DESTIN=.\destin"
for /F "usebackq eol=| delims=" %%L in ("%LISTFILE%") do (
for /F "eol=| delims=" %%F in ('
cd /D "%SOURCE%" ^
^& xcopy /L /S /I /Y ".\%%~L*" "%TEMP%" ^
^| findstr /I /R "^\..*\\%%~L$"
') do (
2> nul md "%DESTIN%\%%F\.."
copy /B /-Y "%SOURCE%\%%F" "%DESTIN%\%%F"
)
)
endlocal
exit /B
It relies on the fact that xcopy outputs relative paths to the console if a relative source path is given, and that it features a switch /L that tells it not to copy anything but list what would be copied without the switch. There is also the switch /S which defines to search for the source item recursively also within subdirectories.
There is a small problem though which requires to be worked around: xcopy /S only walks through subdirectories if source contains a wildcard * or ?, but not if a dedicated file name is given. That is why * is appended to the file name. Since this could also match some unintended items of course, findstr is used to filter them out.
So basically there is a for /F loop that iterates through the items listed in the text file copylist.txt. Within this loop another for /F is nested that enumerates the output of the aforementioned findstr-filtered xcopy /L /S output, which receives the items of the outer loop one after another. The embedded cd command ensures to be in the source directory. The destination of xcopy is just an existing directory to avoid error messages (remember nothing is actually copied due to /L).
The inner loop bopy contains an md command that creates the destination directory (tree), if not existing (2> nul avoids error messages if has already been created earlier), and a copy command which actually performs the copying activity; the switch /-Y defines to prompt the user in case an item already exists at the destination location, you can change it to /Y to overwrite without asking.
First off, I'm new to batch files.
I want to find all files with extension .doc or .pdf
On the first run, I want to save the directories containing these files into a text file
Without creating duplicates entries
I then want to read from this file and copy all of the listed directories.
Basically I'm creating a backup program that finds certain directories based on what files they contain. It saves this list so it doesn't have to search again on the next run.
I don't have access to a windows rig until later in the week, so this code is untested/psudocode.
for /R "c:\my\dir" %%f in (*.pdf | *.doc) do (
set "folderPath=%%f"
echo %folderPath%>> directoryList.txt
)
load %readPath% from directoryList.txt ::Not sure how to read lines one by one?
if %readPath% != eof (
xcopy %folderPath% %writePath% /e/v/c/y/h/r/d >> %logFile%
)
Next script might become a starting point for you. Explanation in code via rem
comments.
#ECHO OFF >NUL
rem set up output directory
set "writePath=c:\my\writedir"
rem create empty file
type nul>directoryList.txt
rem get a list of unique folder names
rem might contain recursion in names, e.g.
rem c:\my\dir\foo
rem c:\my\dir\foo\subfoo
for /F "tokens=*" %%f in ('dir /B /A:D /S "c:\my\dir\*.*"') do (
if exist "%%~ff\*.doc" (
echo "%%~ff">> directoryList.txt
) else (
if exist "%%~ff\*.pdf" echo "%%~ff">> directoryList.txt
)
)
rem treat the list of unique names
rem check the list, compare to output
for /F "tokens=*" %%f in ('type directoryList.txt') do (
rem check next line otput carefully before removing echo
echo xcopy "%%~f" "%writePath%" /e/v/c/y/h/r/d
rem ? or with trailing backslashes "%%~f\" "%writePath%\"
rem ? or with trailing \ escaped "%%~f^\" "%writePath%^\"
rem not tested: XCOPY logic, paths, switches
rem not tested: recursion in input file
)
Resouces:
~ parameter modifier
for /F loop against the results of another command