I have about 250 files that I need to move to a specific folder. The problem is that folder only have the partial name of the files.
For example, I need to move file "12345.txt" to folder "12345 - hello" as each folder starts by the actual file name.
Can I do this in a batch file in DOS?
Thank you.
Assuming Windows, it's actually not hard:
#echo off
rem loop over all files
for %%f in (*) do call :process "%%f"
rem this is necessary to avoid running the subroutine below
rem after the loop above ended
goto :eof
rem subroutine that gets called for every file
rem this finds the first matching folder and moves the file there
:process
rem the /d loops over all directories - the mask ensures that
rem the directory name starts with the given file name (without
rem extension)
for /d %%d in ("%~n1*") do (
echo Moving "%~1" to "%%d" ...
move "%~1" "%%d"
rem Return since the file was moved already
goto :EOF
)
Can also be found in my SVN repository.
Related
I'm trying to create a batch file script that will compress all subfolders named folder1 within a directory, BUT it should only compress this folder if it does NOT contain another subfolder with the same name.
I am very new to cmd and batch files, and this is also my first post of stack overflow, please let me know if I've failed to give some information that I should!
The bit of pseudo-ish code below hopefully illustrates what I'm trying to accomplish:
#echo off
SETLOCAL EnableDelayedExpansion
FOR /D /R %%G IN (*folder1*) DO (
CD %%G
SET /A compress=true
FOR /D /R %%H IN (*folder1*) DO (
ECHO folder contains another folder of same name, should not be compressed
SET /A compress=false
)
IF !compress!==true (
ECHO Run compression operation on folder
"C:\Program Files (x86)\7-zip\7z.exe" a -tzip "%%G.zip" "%%G\"
)
)
Please ask away if anything seems unclear! I'm really hoping to turn the above into functional code, thank you in advance for any input or thoughts.
#ECHO OFF
SETLOCAL
:: Starting directory
SET "sourcedir=U:\sourcedir"
:: name of directory to compress
SET "targetname=targetdir"
FOR /d /r "%sourcedir%" %%a IN (*) DO (
IF /i "%%~nxa" == "%targetname%" IF NOT EXIST "%%a\%targetname%\." (
ECHO compress %%a
rem temporarily switch to target directory
PUSHD "%%a"
ECHO 7z a -tzip "%%a.zip"
rem back to original directory
POPD
)
)
GOTO :EOF
This should do as you want - it will echo not execute the 7z command.
The if sees whether the "name and extension" portion of the directory-name in %%a matches the target (the /i makes the match case-insensitive). If it matches AND there is a subdirectory with the required name, then the compression portion is executed.
There are two points to consider.
First, the name of the destination ZIP file. As you have written it, the ZIP generated would be folder1.zip in the parent directory. IDK what you want here. This code would do the same, but %%a.zip could be replaced by ..\%targetname%.zip since the pushd/popd changes the current directory to the folder1 directory and .. means the parent directory.
The second matter is whether or not you want to compress ...\folder1\folder1 (which would have a destination ZIP file of ...\folder1\folder1.zip)
Revision given comment:
#ECHO OFF
SETLOCAL
:: Starting directory
SET "sourcedir=U:\sourcedir"
:: name of directory to compress
SET "targetname=targetdir"
REM (
FOR /d /r "%sourcedir%" %%a IN (*) DO IF /i "%%~nxa" NEQ "%targetname%" (
rem calculate parent name in %%~nxp and grandparent in %%~nxg
FOR %%p IN ("%%~dpa.") DO FOR %%g IN ("%%~dpp.") DO (
IF /i "%%~nxp" == "%targetname%" IF /i "%%~nxg" NEQ "%targetname%" (
ECHO child %%~nxa
ECHO parent %%~nxp
ECHO Gparent %%~nxg Gppath=%%~dpg
ECHO compress %%a
rem temporarily switch to target directory
PUSHD "%%a"
ECHO 7z a -tzip "%%a.zip"
ECHO -------------------
rem back to original directory
POPD
)
)
)
GOTO :EOF
It's not that clear what you want to do with a directory named ...\folder1\folder1\something or what the target ZIP file-name should be.
The if in the for /d /r line will ensure that only leaf-names that do not match the target name are processed. The path-name in %%a is then processed into the parent and grandparent portions - note that %%~dp? is the drive+path portion of %%? which terminates \ so appending . to this resolves to effectively removing the terminal \ yielding a "filename".
You appear to want to compress directories that have a parent but not a grandparent named with the target string, hence the innermost if statement.
I've just echoed the various strings available at this point so they may be strung together as required to form your destination ZIP file-name. Note that the pushd/popd bracket ensures that the current directory at the time of the compress is the leaf to be compressed.
I have hundreds of sub folders coming off a c:\pdf\ folder.
I want to take the first pdf (sorted by name) in each sub folder and copy them to a single folder, say c:\QA\ so I can check the PDF for errors manually.
Can anyone help? Please!
I have this so far:
#echo off
cls
cd c:
cd c:\pdf\
for /r %%a in (*.pdf) do (
copy %%a c:\qa\%%~nxa
GOTO :EXIT
)
:Exit
Note File names are unique. Target dir is never a subdir of source. The baove code works for the first pdf in a sub folder but does not do the rest of the sub folders.
The New Technology File System (NTFS) returns a list of file names matching a wildcard pattern always sorted in alphabetical order. So for NTFS formatted partitions the following batch code is perhaps enough for this task.
#echo off
for /D %%D in ("C:\pdf\*") do if exist "%%D\*.pdf" call :CopyFirstFile "%%D"
goto :EOF
:CopyFirstFile
for %%F in ("%~1\*.pdf") do (
copy "%%F" C:\qa\
goto :EOF
)
goto :EOF
Please note that first FOR ignores subfolders with hidden attribute set. And the second FOR in subroutine CopyFirstFile ignores PDF files with hidden attribute set.
Another solution working also for File Allocation Table (FAT) partitions (FAT16, FAT32, exFAT) uses the command DIR to get the list of *.pdf files sorted by name.
#echo off
for /D %%D in ("C:\pdf\*") do if exist "%%D\*.pdf" call :CopyFirstFile "%%D"
goto :EOF
:CopyFirstFile
for /F "delims=" %%F in ('dir /A-D /B /ON "%~1\*.pdf"') do (
copy "%~1\%%F" C:\qa\
goto :EOF
)
goto :EOF
The command DIR ignores directories matching the pattern *.pdf and finds also hidden *.pdf files because of option /A-D. But COPY does not copy a hidden *.pdf file!
It would be perhaps better to use MOVE instead of COPY as on next run the batch file would copy the same files again if the folders in C:\pdf are not modified in the meantime.
The command lines below label CopyFirstFile are interpreted as subroutine called by the first FOR loop.
The first goto :EOF exits batch file processing once the first FOR loop iterating over all non hidden subdirectories of C:\pdf finished.
The second goto :EOF exits the second FOR loop and the subroutine after copying the first file found in the directory passed as first (and only) argument to the subroutine.
The third goto :EOF would not be really necessary as this command line should be never executed and is additionally also not needed when there are no more command lines in the batch file. But it is good practice to make sure that each block called as subroutine has as last line goto :EOF or alternatively exit /B which is exactly the same as goto :EOF.
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 /?
dir /?
echo /?
for /?
goto /?
if /?
Here's some example code for you. It looks inside each directory and any potential subdirectory below C:\PDF, it does not look at files inside C:\PDF. If you're looking for something different then update your question accordingly.
#Echo Off
For /D /R "C:\PDF" %%A In (*) Do (
For /F "Delims=" %%B In ('Dir/B/A-D/O-N "%%A\*.pdf" 2^>Nul') Do (
Set "first=%%A\%%B")
SetLocal EnableDelayedExpansion
If Exist "!first!" Copy "!first!" "C:\QA"
EndLocal)
Pause
Please don't just use it, take your time to learn it by checking various references as well.
All you need to do is to combine the features of Dir & For . Now, You can simply analyze the code and modify it according to your need.
Hint for the Code:
CD /D "C:\PDF"
DIR /s /b *.pdf >a.txt
For /f "Tokens=*" %%A In (a.txt) Do (Copy /Y "%%A" "C:\QA")
Exit
And, it is almost all you need. :)
My photo import tool (Picasa) does a great job at importing photos and videos from my phone and camera. What I like is that it creates a subfolder under the Pictures directory based on the Photo Taken Date of each photo/video. So you end up with this structure:
C:\Pictures\2017-02-01\DSC_0001.jpg
C:\Pictures\2017-02-01\DSC_0002.jpg
C:\Pictures\2017-02-01\DSC_0003.mp4 <--- problem
The only problem is that it puts videos in this same structure under Pictures.
As such, I'd like to right a batch script to find and move all video files (.mp4, .avi, .mov) from the C:\Pictures directory to the C:\Videos directory, but also with the date subfolder....
i.e.
Move C:\Pictures\2017-02-01\DSC_0003.mp4 to C:\Videos\2017-02-01\DSC_0003.mp4
Note that the date subfolder may or may not exist under C:\Videos.
Also since these are large video files, and there are a lot of them, I'd prefer a process that actually does a move and not a copy then delete, for the sake of speed and disk space utilization as I am almost out of space (after re-organizing these files, I will be archiving off to a NAS).
Also prefer using RoboCopy, xcopy, or xxcopy as I have them and use them today on my machine. If massively easier using PowerShell scripting, I can learn that if it is easy to do.
Final Solution
I used Mofi's answer, but enhanced it just a bit to add a function to calculate the directory string length
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define folder with the pictures which is never deleted.
set "PicturesFolder=D:\Users\Chad\PicturesTest"
rem get string length of source directory to later use in a substring type function
call :strlen PicturesFolderDirectoryLength PicturesFolder
echo PicturesFolderDirectoryLength = %PicturesFolderDirectoryLength%
rem Change the current directory to directory with the pictures.
cd /D "%PicturesFolder%"
rem Search recursive in this directory for video files with
rem file extension AVI, MOV, MP4 or MPG and move those files.
for /F "delims=" %%I in ('dir /A-D /B /S *.avi *.mov *.mp4 *.mpg 2^>nul') do call :MoveVideo "%%I"
rem Discard all environment variables defined in this batch code
rem and restore initial current directory before exiting batch file.
endlocal
goto :EOF
rem MoveVideo is a subroutine called with name of current
rem video file name with full path by the FOR loop above.
rem It first defines target path for video file depending on source path
rem by removing the backslash at end and concatenating C:\Videos with the
rem source path omitting the first 11 characters which is C:\Pictures.
rem Then the target directory structure is created with redirecting the
rem error message output by command MD to handle STDERR in case of the
rem target directory already exists to device NUL to suppress it.
rem Next the video file is moved from source to target folder with silently
rem overwriting an already existing file with same name in target folder
rem because of using option /Y. Remove this option if a video file should
rem be kept in pictures folder and an error message should be displayed in
rem case of a video file with same name already existing in target folder.
rem Last the source folder is removed if it is completely empty which means
rem it does not contain any file or subfolder. All parent folders up to the
rem pictures folder are also removed if each parent folder is also empty
rem after deletion of an empty folder.
rem The subroutine is exited with goto :EOF and execution of batch file
rem continues in main FOR loop above with next found video file.
:MoveVideo
set "SourcePath=%~dp1"
set "SourcePath=%SourcePath:~0,-1%"
ECHO SourcePath=%SourcePath%
CALL SET "SourceSubFolder=%%SourcePath:~%PicturesFolderDirectoryLength%%%"
ECHO SourceSubFolder=%SourceSubFolder%
set "TargetPath=D:\Users\Chad\VideosTest%SourceSubFolder%"
echo TargetPath=%TargetPath%
md "%TargetPath%" 2>nul
move /Y "%~1" "%TargetPath%\%~nx1" >nul
:DeleteSourceFolder
rd "%SourcePath%" 2>nul
if errorlevel 1 goto :EOF
for /F "delims=" %%D in ("%SourcePath%") do set "SourcePath=%%~dpD"
set "SourcePath=%SourcePath:~0,-1%"
if /I not "%SourcePath%" == "%PicturesFolder%" goto DeleteSourceFolder
goto :EOF
:strlen <resultVar> <stringVar>
(
setlocal EnableDelayedExpansion
set "s=!%~2!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
)
(
endlocal
set "%~1=%len%"
exit /b
)
Here is a commented batch code for this file moving task with keeping directory structure.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define folder with the pictures which is never deleted.
rem Note: ~11 in third line of subroutine MoveVideo must be
rem replaced by ~length of the folder path defined here.
set "PicturesFolder=C:\Pictures"
rem Change the current directory to directory with the pictures.
cd /D "%PicturesFolder%"
rem Search recursive in this directory for video files with
rem file extension AVI, MOV, MP4 or MPG and move those files.
for /F "delims=" %%I in ('dir /A-D /B /S *.avi *.mov *.mp4 *.mpg 2^>nul') do call :MoveVideo "%%I"
rem Discard all environment variables defined in this batch code
rem and restore initial current directory before exiting batch file.
endlocal
goto :EOF
rem MoveVideo is a subroutine called with name of current
rem video file name with full path by the FOR loop above.
rem It first defines target path for video file depending on source path
rem by removing the backslash at end and concatenating C:\Videos with the
rem source path omitting the first 11 characters which is C:\Pictures.
rem Then the target directory structure is created with redirecting the
rem error message output by command MD to handle STDERR in case of the
rem target directory already exists to device NUL to suppress it.
rem Next the video file is moved from source to target folder with silently
rem overwriting an already existing file with same name in target folder
rem because of using option /Y. Remove this option if a video file should
rem be kept in pictures folder and an error message should be displayed in
rem case of a video file with same name already existing in target folder.
rem Last the source folder is removed if it is completely empty which means
rem it does not contain any file or subfolder. All parent folders up to the
rem pictures folder are also removed if each parent folder is also empty
rem after deletion of an empty folder.
rem The subroutine is exited with goto :EOF and execution of batch file
rem continues in main FOR loop above with next found video file.
:MoveVideo
set "SourcePath=%~dp1"
set "SourcePath=%SourcePath:~0,-1%"
set "TargetPath=C:\Videos%SourcePath:~11%"
md "%TargetPath%" 2>nul
move /Y "%~1" "%TargetPath%\%~nx1" >nul
:DeleteSourceFolder
rd "%SourcePath%" 2>nul
if errorlevel 1 goto :EOF
for /F "delims=" %%D in ("%SourcePath%") do set "SourcePath=%%~dpD"
set "SourcePath=%SourcePath:~0,-1%"
if /I not "%SourcePath%" == "%PicturesFolder%" goto DeleteSourceFolder
goto :EOF
This batch file also removes all folders in C:\Pictures which become empty after moving the video files. But it does not remove folders which were already empty on starting the batch file.
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.
cd /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
move /?
rd /?
rem /?
set /?
setlocal /?
Read also the Microsoft article about Using Command Redirection Operators for an explanation of >nul and 2>nul. In the main FOR loop the redirection operator > is escaped with caret character ^ to be interpreted as literal character on parsing FOR command line and later as redirection operator on execution of DIR command line by FOR.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
XCOPY /T "%sourcedir%" "%destdir%"
FOR %%x IN (mp4 mov) DO (
FOR /f "tokens=1*delims=>" %%a IN (
'XCOPY /Y /s /d /F /L "%sourcedir%\*.%%x" "%destdir%"'
) DO IF "%%b" neq "" (
SET "topart=%%b"
SET "frompart=%%a"
ECHO(MOVE /y "!frompart:~0,-2!" "!topart:~1!"
)
)
GOTO :EOF
You would need to change the settings of sourcedir and destdir to suit your circumstances.
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)
The first xcopy creates the required subtrees, the second uses the /L option to list rather than copy the files
The loop on %%x assigns %%x to the required extensions. The output from the inner xcopy will be of the form fullsourcefilename -> fulldestinationfilename so it needs to be parsed using > as a delimiter, from-filename to %%a, to-filename to %%b. If %%b is not set, then this is the final line of xcopy's report (n files copied) which needs to be ignored. The to and from filenames need to be trimmed of unwanted, but fortunately constant character strings.
What is interesting is that there appears to be no way using xcopy to suppress prompting in the case where the destination filename already exists.
I try to write a script that can monitor a folder H:\Start and copy a new subfolder with its files in H:\Start to new location H:\Target. The script should store the copied folder and files in a text file.
Each time the script starts new and monitors H:\Start, it should check the text file and copy only those subfolders which are not yet included in the text file because copied already before.
I was searching the world wide web for examples, but could not really find a starting point. Any help would be nice.
I have so far not working good :)
#echo off
setlocal EnableDelayedExpansion
pushd %1
for /D %%d in (“H:\Start\*.*”) do set n=!n!;%%d
if defined n echo %n:~1% > C:\Desktop\list.txt
popd
endlocal
for /f %%i in (C:\Desktop\list.txt) do not (
xcopy /d /s H:\Start H:\Target > C:\Desktop\list.txt >nul 2>&1
)
I suggest using:
%SystemRoot%\System32\xcopy.exe H:\Start H:\Target /C /H /I /K /M /Q /R /S /Y >nul
For information about all those parameters of xcopy open a command prompt window and run there xcopy /? to get help of this command displayed which explains all those parameters.
The important one is /M which selects for copying process just files with archive attribute set and which removes the archive attribute on each file in H:\Start after copying the file. This avoids that a once copied file is copied once more as long as not modified in H:\Start since last copy.
If you want to log all copied files into a text file, I suggest using:
%SystemRoot%\System32\xcopy.exe H:\Start H:\Target /C /F /H /I /K /M /R /S /Y >>C:\Desktop\list.txt
The names of the copied files are with this command line appended to text file C:\Desktop\list.txt
The following commented batch code works with directory lists as asked for.
#echo off
rem Define source and destination directory as well as
rem the names of the used list files each with full path.
setlocal EnableExtensions
set "Source=H:\Start"
set "Destination=H:\Target"
set "MainDirList=C:\Desktop\list.txt"
set "CurrentList=%Temp%\CurrentList.tmp"
set "ExcludeList=%Temp%\ExcludeList.tmp"
set "FinalDoList=%Temp%\FinalDoList.tmp"
rem Write the names of all subdirectories found in source
rem directory into a list file in directory for temporary files.
dir /AD /B "%Source%">"%CurrentList%"
rem Check if list file is not empty because of no subdirectories.
call :CheckEmpty "%CurrentList%"
if %FileIsEmpty% == 1 (
echo No directories in %Source%
goto EndBatch
)
rem Start copying the directories if there is no main list file
rem from a previous execution of this batch file or the main list
rem file was deleted intentionally to force copying all again.
if not exist "%MainDirList%" goto CopyDirectories
rem Start copying also if main list file is an empty file.
call :CheckEmpty "%MainDirList%"
if %FileIsEmpty% == 1 del "%MainDirList%" & goto CopyDirectories
rem Search in main list for lines matching completely lines in current
rem list with ignoring case and write the found lines into an exclude
rem list file as those directories were copied already before.
%SystemRoot%\System32\findstr.exe /I /L /X /G:"%CurrentList%" "%MainDirList%" >"%ExcludeList%"
rem Copy all directories if no line in current list is found in main list.
if errorlevel 1 goto CopyDirectories
rem Get all lines from current list not listed also in exclude list.
%SystemRoot%\System32\findstr.exe /B /I /L /V /G:"%ExcludeList%" "%CurrentList%" >"%FinalDoList%"
rem Replace the current list with the reduced final list.
move /Y "%FinalDoList%" "%CurrentList%"
rem Check if remaining current list is not empty because
rem all subdirectories copied already before.
call :CheckEmpty "%CurrentList%"
if %FileIsEmpty% == 1 (
echo Copied already before all directories in %Source%
goto EndBatch
)
:CopyDirectories
rem Copy each directory in remaining current list file.
for /F "usebackq delims=" %%D in ("%CurrentList%") do (
echo Coping %Source%\%%D
%SystemRoot%\System32\xcopy.exe "%Source%\%%D" "%Destination%\%%D" /C /H /I /K /Q /R /S /Y >nul
)
rem Append the list of copied directories to main list file.
type "%CurrentList%">>"%MainDirList%"
goto EndBatch
:CheckEmpty
rem This little subroutine just checks if size of a list file is 0.
if %~z1 == 0 ( set "FileIsEmpty=1" ) else ( set "FileIsEmpty=0" )
goto:EOF
:EndBatch
rem Delete all not further needed listed files and environment variables.
del "%ExcludeList%" 2>nul
del "%CurrentList%"
endlocal
This batch file should work for the FTP folder mounted as drive on Windows. It does not depend on attributes or timestamps. It uses explicitly only the names of the directories in H:\Start. It also does not check which directories exist already in H:\Target. Therefore it is possible to delete a directory in H:\Target if not interested in and the deleted directory will be nevertheless not copied once again from H:\Start as long as the deleted directory is not also removed from main list file.
For details on parameters used on findstr run in a command prompt window findstr /? and read the entire help output into the window.
Thanks for this question as this was really an interesting batch coding task.
You don't need to store anything, you can use
xcopy /d /s h:\start h:\target
/D:mm-dd-yyyy
Copy files changed on or after the specified date.
If no date is given, copy only files whose
source date/time is newer than the destination time.
but if you need a list of the files you can just use a redirection :
xcopy /d /s h:\start h:\target > logfile.txt
I would like to use a batch file to delete all blank lines in multiple files in the directory "Data". I don't want to rename the files.
I have seen this post, but it does not help: How to delete blank lines from multiple files in a directory for the following reasons:
* Files are renamed
* Files must be in same directory as the .bat file
If you could also explain the batch file commands, then that would be greatly appreciated.
Thanks.
I've decided to include all the explanations as comments. There are some ways of doing it without a rename/move operation, but are not as reliable as this. Anyway, at the end, files will have the same name but no empty lines.
#echo off
setlocal enableextensions disabledelayedexpansion
rem There are some problems with references to batch files
rem that are called with quotes. To avoid the problems, a
rem subroutine is used to retrieve the information of
rem current batch file
call :getBatchFileFullPath batch
rem From the full path of the batch file, retrieve the
rem folder where it is stored
for %%a in ("%batch%") do set "folder=%%~dpa"
rem We will use a temporary file to store the valid
rem lines while removing the empty ones.
set "tempFile=%folder%\~%random%%random%%random%"
rem For each file in the batch folder, if the file is
rem not the batch file itself
for %%a in ("%folder%\*") do if /i not "%%~fa"=="%batch%" (
rem Now %%a holds a reference to the file being processed
rem We will use %%~fa to get the full path of file.
rem Use findstr to read the file, and retrieve the
rem lines that
rem /v do not match
rem /r the regular expression
rem /c:"^$" start of line followed by end of line
rem and send the output to the temporary file
findstr /v /r /c:"^$" "%%~fa" > "%tempFile%"
rem Once we have the valid lines into the temporary
rem file, rename the temporary file as the input file
move /y "%tempFile%" "%%~fa" >nul
)
rem End - Leave the batch file before reaching the subroutine
exit /b
rem Subrotutine used to retrieve batch file information.
rem First argument (%1) will be set to the name of a variable
rem that will hold the full path to the current batch file.
:getBatchFileFullPath returnVar
set "%~1=%~f0"
goto :eof
The uncommented version
#echo off
setlocal enableextensions disabledelayedexpansion
call :getBatchFileFullPath batch
for %%a in ("%batch%") do set "folder=%%~dpa"
set "tempFile=%folder%\~%random%%random%%random%"
for %%a in ("%folder%\*") do if /i not "%%~fa"=="%batch%" (
findstr /v /r /c:"^$" "%%~fa" > "%tempFile%"
move /y "%tempFile%" "%%~fa" >nul
)
exit /b
:getBatchFileFullPath returnVar
set "%~1=%~f0"
goto :eof