Windows Batch Script- for /D delims Executing Multiple times - windows

I am trying to set up a batch backup script on Win7. It is mostly working, but I have a line of code executing multiple times. I've done some searching around and I think it has to do with how batch files handle code in "( )" but I'm not sure the best way to fix it.
Any help would be awesome!
Script:
for /D %%d in (C:\toBackupDir\*.*) do (
for %%f in (C:\zipDir\*) do (
for /f "tokens=1 delims=_" %%i in ("%%~nf") do (
if "%%i"=="%%~nd" (
if "%%~tf"=="%%~td" (
echo "%%~nf" is "%%~nd" No Backup necessary.
) else (
7z.exe a -tzip -stl -mx=1 "C:\zipDir\%%~nd_%DATE:~-4%.%DATE:~4,2%.%DATE:~7,2%.zip" "%%d"
)
)
)
)
)
The line "7z.exe" is executed many times if "%%i"=="%%~nd" is true. My thought is that last for loop to split the file name is being executed many times, but once it gets %i and does the comparisons, I'm done with it. Can I break out of the loop once the "7z.exe" line is executed? I've read that breaking out in a batch file is tricky.
Let me know if you need a breakdown of the code.
Thank you in advance!

You also test every directory against every zip file.
These are more than 60000 compares with 250 directories and the matching zips. But this is not necessary at all. Then you can not against the time of the folder Compare - it does not update itself. The output from the DIR is therefore not at all suitable.
In addition - what is already with the existing zip fuses, these are also compared each time with. So with two backups of a folder, your batch would always make a new backup.
Your simple loop, which searches for the zipfiles, is practically not finished, because always new files are entered in the MFT and the loop this after the creation issues.
I've taken robocopy to list the files, because this time stamp is very well suited for comparison.
First, only the folder is checked and with it the appropriate zip file. The folder is checked recursively for the files as well as the appropriate zip file. The time stamps are placed in a list and sorted. The loop sets the last value so, if the last (ie the newest) file comes from the folder of the zips does not need a backup.
A suitable time stamp for the file name is also generated.
You may need to adjust the paths.
#echo off
setlocal
set "Folder=d:\toBackup"
set "Backup=d:\zipdir"
call :TAB
set "TS=."
for /d %%F in ("D:\files\*")do (
set "TsFn="
set "tozip="
for /f "tokens=2,3delims=%TAB%" %%A in ('
( robocopy /L "%backup%" ".. only listing ..\\" "%%~nF_*.zip" /njh /fp /ts /ns /nc /ndl /njs ^
^& robocopy /L /e "%%F" ".. List only ..\\" /njh /fp /ts /ns /nc /ndl /njs ^)^|sort
')do (
set "TsFn=%%A*%%~dpB"
if /i "%%~dpB"=="%Backup%\" ( set "tozip="
)else set "tozip=1"
)
if defined tozip call :timestamp
if defined TsFn (
setlocal enabledelayedexpansion
for /f "tokens=1-3delims=*" %%S in ("!TS!*!TsFn!")do (
endlocal
if NOT defined tozip ( echo %%T %%~nxF - No Backup necessary.
)else echo 7z.exe a -tzip -stl -mx=1 "%Backup%\%%~nF_%%S.zip" "%%F"
)
)
)
pause
exit /b
:TAB
for /f "delims= " %%T in ('robocopy /L . . /njh /njs') do set "TAB=%%T"
rem END TAB
exit /b
:Timestamp
rem robocopy /L "\.. Timestamp ..\\" .
for /f "eol=D tokens=1-6 delims=/: " %%T in (' robocopy /L /njh "\|" .^|find "123" ') do (
set "TS=%%T%%U%%V-%%W%%X%%Y"
set "TSDATE=%%T%%U%%V"
set /a YY=%%T , MM=100%%U %%100 , TT=100%%V %%100
)
rem END Timestamp
exit /b
If there is not yet a zip file from the matching folder is available, of course, a zip is created.
A complete backup program
robocopy backup cmd

Related

Batch How to avoid a same 2nd FOR loop?

I have created a batch script to backup some subfolders from a path A to a path B (Z:\Folder1\… > U:\Backup\…).
The script lists the subfolders inside path A and increments a number for each of them.
Then, I just have to enter the number(s) of the subfolder(s) I want to backup and xcopy does the rest.
The problem is that sometimes I have thousands of subfolders in path A and only a few to backup (10 or 15).
What I would like is that once I enter the number of these folders, it will go straight to the backup without having to loop all the subfolders inside path A AGAIN (which take time).
Here is my batch :
#echo off
setlocal EnableDelayedExpansion
rem Script for backuping some subfolders from path A to path B
set BackupLocation=U:\Backup
set subfolder_no=1
FOR /F "delims=" %%a IN ('dir /b "Z:\Folder1\*"') DO (
set subfolder=%%a
if defined subfolder (
echo !subfolder_no! !subfolder!
set /a subfolder_no+=1
)
)
set /a subfolder_no=%subfolder_no%-1
set /a index=0
set /a choice=-1
echo.
set /p choice=Enter the number(s) of the subfolder(s) you want to backup:
FOR /F "delims=" %%a IN ('dir /b "Z:\Folder1\*"') DO (
set subfolder=%%a
if defined subfolder (
set /a index+=1
)
FOR %%f IN (%choice%) DO if %%f==!index! (
echo.
echo Backuping subfolder !subfolder!
xcopy "Z:\Folder1\!subfolder!" "%BackupLocation%\!subfolder!\" /e /i /y
)
)
echo.
pause
exit
How can I do this ? Is it possible to get the subfolders' name from their matching number and store them in variables or something ?
Thanks a lot for your help !
Here's an example which only enumerates the directories once.
Please note, whilst it technically answers your question, and performs the task you require of it, this version is designed to work as intended on Windows 10. The specific part you asked about works in other versions too, but the :BackUp labelled section uses a new and undocumented Windows 10 feature of the sort command, to return only unique items from the selection list. Without that, your end user could tehnically provide the same number multiple times, and thus trigger multiple backups of the same directory. As this part of the code is technically outside of the scope of your question, I will leave it up to you to modify the code section yourself, should you be deploying this on older Operating Systems.
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Rem Script for backing up some subdirectories of a Source path to a Backup path.
Set "SourceLocation=Z:\Folder1"
Set "BackupLocation=U:\Backup"
If Not Exist "%Sourcelocation%\." Exit /B
If Not Exist "%Backuplocation%\." Exit /B
:ShowSet
For /F "Delims==" %%G In ('"(Set subdirectory[) 2>NUL"') Do Set "%%G="
Set "index=0"
For /F "EOL=? Delims=" %%G In ('Dir /B /A:D /O:N "%SourceLocation%" 2^>NUL'
) Do (Set /A index += 1
Set "subdirectory=%%G"
SetLocal EnableDelayedExpansion
Echo !index! !subdirectory!
For /F "Tokens=1,*" %%H In ("!index! !subdirectory!") Do (EndLocal
Set "subdirectory[%%H]=%%I"))
If Not Defined subdirectory[1] Exit /B
:Select
Echo(
Echo Please type the number(s) for the subdirectories you want to backup,
Echo(
Echo For multiple selections please separate each with spaces e.g. 1 3 6
Echo For none please type 0 or press [ENTER].
Set "selection=0"
Set /P "selection=>"
Set "selection=%selection:"=%"
Set "selection=%selection:)=%"
If Not Defined selection GoTo Select
If "%selection%" == "0" GoTo :EOF
(Set selection) | "%SystemRoot%\System32\findstr.exe"^
/X /R /C:"selection=[0123456789 ][0123456789 ]*" 1>NUL || GoTo Select
Set "selection=%selection% "
:BackUp
For /F %%G In (
'"(Echo(%selection: =^&Echo(%) | "%SystemRoot%\System32\sort.exe" /Unique"'
) Do If Not Defined subdirectory[%%G] (Echo Selection %%G was not valid) Else (
SetLocal EnableDelayedExpansion
Echo(&Echo Backing up subdirectory %%G !subdirectory[%%G]!
"%SystemRoot%\System32\Robocopy.exe" ^
"%SourceLocation%\!subdirectory[%%G]!" ^
"%BackupLocation%\!subdirectory[%%G]!" /E /NC /NDL /NJH /NJS /NS ^
| %SystemRoot%\System32\find.exe /V " (0x00000005) "
EndLocal)
Pause
In the :BackUp section, I have used Robocopy.exe instead of the deprecated xcopy.exe utility you had used.
If you wish to still use xcopy.exe, replace:
"%SystemRoot%\System32\Robocopy.exe" ^
"%SourceLocation%\!subdirectory[%%G]!" ^
"%BackupLocation%\!subdirectory[%%G]!" /E /NC /NDL /NJH /NJS /NS ^
| %SystemRoot%\System32\find.exe /V " (0x00000005) "
with:
"%SystemRoot%\System32\xcopy.exe" ^
"%SourceLocation%\!subdirectory[%%G]!" ^
"%BackupLocation%\!subdirectory[%%G]!\" /E /I /Y

Batch file to make folders and move files according to filenames

I'm brand new to batch scripting so I appreciate any help. I've seen similar problems here but can't get my move function to work.
I have files with the following format:
19013_01-PG-18-1000_NC_IL2RG_Ex2_F_D01.ab1
19013_01-PG-18-1000_NC_IL2RG_Ex2_R_H01.ab1
I want to make folders with the following format:
01-PG-18-1000_NC_IL2RG_Ex2
And then move all the files that have *01-PG-18-1000_NC* into that folder name.
Here's what I have so far. It's making the folders the way I want, but I can't get the files to move at all. Tried multiple iterations of the move function, but I'm not totally understanding the tokens and how it relates to the files for moving.
#ECHO OFF
SETLOCAL
SET "sourcedir="whatever my directory name is"
PUSHD %sourcedir%
FOR /f "tokens=1,2,3,4,5 delims=_" %%a IN (
'dir /b /a-d "*_*-*-*-*_*_*_*.*"'
) DO (
MD %%b_%%c_%%d_%%e 2>nul
MOVE "%%b_%%c_%%d_%%e" "%%b_%%c_%%d_%%e"
)
POPD
GOTO :EOF
Real quickie - and untested
#ECHO OFF
SETLOCAL
SET "sourcedir="whatever my directory name is"
PUSHD %sourcedir%
FOR /f "tokens=1,2,3,4,5,* delims=_" %%a IN (
'dir /b /a-d "*_*-*-*-*_*_*_*.*"'
) DO (
echo ++%%a++%%b++%%c++%%d++%%e++%%f++
MD %%b_%%c_%%d_%%e 2>nul
MOVE "%%a_%%b_%%c_%%d_%%e_%%f" "%%b_%%c_%%d_%%e"
)
POPD
GOTO :EOF
You were very close - the echo line should show you how the filename is parsed into %%a..%%f. The parts separated by ++ which is simply a very obvious separator and shows whether there are spaces in any element.
Adding * to the token-list means "everything after the highest-mentioned token number". Then reconstruct the filename from the parts - string together starting at %%a, re-inserting all the underscores.
This should work also:
#ECHO OFF
SETLOCAL
SET "sourcedir="whatever my directory name is"
PUSHD %sourcedir%
FOR /f %%q IN (
'dir /b /a-d "*_*-*-*-*_*_*_*.*"'
) do (
FOR /f "tokens=1,2,3,4,5,* delims=_" %%a IN ("%%q") do
echo ++%%a++%%b++%%c++%%d++%%e++%%f++FROM++%%q++
MD %%b_%%c_%%d_%%e 2>nul
MOVE "%%q" "%%b_%%c_%%d_%%e"
)
)
POPD
GOTO :EOF
In this version, %%q acquires each filename in turn, then "%%q" can be parsed by for /f and the original filename remains unmolested in %%q ready for use in the move statement.
[actually, quite minor] Revision:
REM <!-- language: lang-dos -->
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir\t w o"
PUSHD "%sourcedir%"
FOR /f "tokens=1,2,3,4,5,* delims=_" %%a IN (
'dir /b /a-d "*_*-*-*-*_*_*_*.*"'
) DO IF EXIST "%%a_%%b_%%c_%%d_%%e_%%f" (
MD %%b_%%c_%%d_%%e 2>nul
MOVE "*%%b_%%c*" "%%b_%%c_%%d_%%e"
)
POPD
SET "sourcedir=U:\sourcedir\t h r e e"
PUSHD "%sourcedir%"
FOR /f %%q IN (
'dir /b /a-d "*_*-*-*-*_*_*_*.*"'
) do IF EXIST "%%q" (
FOR /f "tokens=1,2,3,4,5 delims=_" %%a IN ("%%q") DO (
MD %%b_%%c_%%d_%%e 2>nul
MOVE "*%%b_%%c*" "%%b_%%c_%%d_%%e"
)
)
POPD
GOTO :EOF
Yes - misread that you wanted to move all files containing - been up gaming all night...
The above batch is in two sections, the first using %%a..%%f and the second incorporating %%q.
The difficulty faced is that move *[pattern]* will move all of the files, as desired BUT for /f...'dir... builds a list of ALL of the matching files that were originally in the directory.
Once the first filename is processed, those other files containing the 01-PG-18-1000_NC will have been moved, so you'll get a "no files found" error on the next 01-PG-18-1000_NC file in the the list for has built.
Sure, it's possible to cruft together some mechanism for ensuring that the pattern 01-PG-18-1000_NC is only processed once, but a simple if exists for the full filename returned by for...%%a... and rebuilt can be used to gate the MD/MOVE commands as the file will no longer exist when the next 01-PG-18-1000_NC is processed (as it's already been moved). So much easier using the %%q method though.
Of course, you could also simply dispose of the error messages instead of installing a gate - but that's probably regarded as being crude.
Also aschipfl's suggestion of adding delims= to the for...%%q... is quite valid if you have separators like Space in your filenames (and costs nothing, regardless)
Oh - and I'd missed the ( in the do part in my initial response - always happens when you don't actually test the code, especially when you're tired.

(Batch) How to recursively delete all files/folders in a directory except those with a leading .?

I have a directory, src. I want to recursively delete all of its contents except for files (.gitignore, ...) and folders (.git, .vscode, ...) whose names begin with .. Matching that pattern in subdirectories is neither necessary nor harmful.
What is the cleanest way to do this in a batch file?
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
:: step 1 : delete all files NOT starting "."
FOR /f "tokens=1*delims=" %%a IN (
'dir /s /b /a-d "%sourcedir%\*" '
) DO (
ECHO %%~nxa|FINDSTR /b /L "." >nul
IF ERRORLEVEL 1 ECHO(DEL "%%a"
)
:: step 2 : delete all directories NOT starting "."
FOR /f "tokens=1*delims=" %%a IN (
'dir /s /b /ad "%sourcedir%\*" ^|sort /r'
) DO (
ECHO %%~nxa|FINDSTR /b /L "." >nul
IF ERRORLEVEL 1 ECHO(RD "%%a"
)
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
The required DEL commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(DEL to DEL to actually delete the files.
The required RD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(RD to RD to actually delete the directories.
for each filename in the entire subtree, see whether it starts with ., setting errorlevel to non-0 if not and hence delete the file.
Once this has been done, repeat the operation with directorynames, but sort the names found in reverse so that a subdirectoryname of any directory will appear before the directoryname. Attempt to remove the directory with an rd - it will remain if it contains files or subdirectories (which implicitly will start .). Append 2>nul to the rd line to suppress error messages (where the directory cannot be removed as it still contains files/subdirectories)
Try this to exclude folders with a leading dot (on a per folder base):
for /f "tokens=*eol=." %%A in ('dir /B /AD') do Echo=%%A
This doesn't affect folder names containing dots at other positions.
A relatively slow variant recursing all folders and using findstr to filter all folders containing \. both chars need escaping with a backslash
for /r /D %%A in (*) do #echo %%A|findstr /V "\\\."

batch file to count all files in a folder and subfolder files and write output file in folder name using batch file

How to count files in a folder and subfolder files also. folder name and file count and sub folder name and file count.
Ex:
test1 : 2
test1\test11 :3
newfolder : 5
newfolder : 10
like this.
robocopy "x:\target\folder" "x:\target\folder" /l /nocopy /is /e /nfl /njh /njs
or
pushd "x:\target\folder" && (
robocopy . . /l /nocopy /is /e /nfl /njh /njs
popd
)
This will simply call robocopy, but instead of copying anything, we will request that nothing will be copied and only return the list of what should be processed (/nocopy /l). We request to copy from the current folder (a previous pushd command is used) to the current folder, including identical files in the process (/is), processing empty subfolder (/e, to include folders with 0 files), but only process two levels (/lev:2 the current folder and the one below), without generating a file list (/nfl), no job header (/njh) and no job summary (/njs)
The result will be the same list but with the folder name and the number of files in changed columns
To keep the original output format
#echo off
setlocal enableextensions disabledelayedexpansion
pushd "x:\target\folder" && (
for /f "tokens=1,*" %%a in ('
robocopy . . /l /nocopy /is /e /nfl /njh /njs
') do echo %%~fb : %%a
popd
)
This will use a for /f to process the previous robocopy command, splitting the line in two tokens, the first will contain the number of files and will be stored in %%a, and the second the rest of the line and will be stored in %%b. For each line in the output of the inner robocopy command, the code in the do clause is executed: just echo to console the two tokens in reverse order.
If robocopy can not be used (OS older than Vista), then
#echo off
setlocal enableextensions disabledelayedexpansion
for /d /r "x:\target\folder" %%a in (*) do for /f "tokens=1,5" %%b in ('
dir /a-d "%%~fa.\*" 2^> nul ^| findstr /b /c:" " ^|^| echo 0
') do if "%%c"=="" echo %%~fa : %%b
This will
For each folder under the start directory (for /r /d) grab a reference and store it in %%a replaceable parameter
Run a dir command with the full path of the folder %%~fa
Use a pipe (|) to filter the list to only retrieve the lines that start with two spaces (the footer lines)
If no lines are found (that is, the dir command failed) output a 0
The lines generated by the dir | findstr are handled with a for /f command. We will read the first token (the number of files in the adecuated line) and the fifth (only present in the footer line with the directories info)
If the fifth element is empty, this line has information about the files, not the folders, so, echo the folder path and the files inside it
At the end, restore the previous active directory
The problem with this approach is that the dir | findstr is executed for each of the subfolders. This will make this slower than the robocopy solution.
It can be faster, but more code is needed
#echo off
setlocal enableextensions disabledelayedexpansion
set "root=%cd%"
set "folder="
for %%r in ("%root%") do (
set "rootDrive=%%~dr\"
if not defined rootDrive set "rootDrive=\\"
for /f "delims=" %%a in ('
dir /s /a "%%~fr.\*" 2^>nul ^| findstr /r /c:"^ " /c:"^ .*\\."
') do for /f "tokens=1,* delims=\" %%b in ("%%~a") do if not "%%c"=="" (
set "folder=%%c"
) else if defined folder (
for /f %%d in ("%%~a") do (
setlocal enabledelayedexpansion
echo(!rootDrive!!folder! : %%d
endlocal
)
set "folder="
)
)
In this case, we will execute only one dir command to retrieve the full list and filter with findstr the list to only retrieve the lines with the name of the folder and the footer for each folder.
The code will iterate (for /f %%a) over this list. When a folder line is found (it contains a backslash and can be splitted by the for /f %%b), the folder name is saved in a variable. When the corresponding footer line is readed (it was not splitted by a backslash), the line is splitted to get the number of files (for /f %%d) and the folder name and the number of files are echoed to console (here delayed expansion is needed to retrieve the variable contents, as they were set inside the same loop)
Note: Using a for /f to process a large list of data generated by executing a command can be really slow (there is a bad behaviour in how the command handles memory). For this cases it is necessary to first execute the command that generates the data (dir | findstr in this case) sending its output to a file and then use the for /f to process the file.
#echo off
setlocal enableextensions disabledelayedexpansion
set "root=%~1"
if not defined root for %%a in (.) do set "root=%%~fa"
set "folder="
for %%t in ("%temp%\%~nx0.%random%%random%.tmp") do for %%r in ("%root%") do (
set "rootDrive=%%~dr\"
if not defined rootDrive set "rootDrive=\\"
( dir /s /a "%%~fr.\*" 2^> nul | findstr /r /c:"^ " /c:"^ .*\\." ) > "%%~ft"
for /f "usebackq delims=" %%a in ("%%~ft") do (
for /f "tokens=1,* delims=\" %%b in ("%%~a") do if not "%%c"=="" (
set "folder=%%c"
) else if defined folder (
for /f %%d in ("%%~a") do (
setlocal enabledelayedexpansion
echo(!rootDrive!!folder! : %%d
endlocal
)
set "folder="
)
)
del /q "%%~ft"
)
edited to adapt to comments: robocopy solution changed to include total number of files
#echo off
setlocal enableextensions disabledelayedexpansion
set "total=0"
pushd "x:\target\folder" && (
for /f "tokens=1,*" %%a in ('
robocopy . . /l /nocopy /is /e /nfl /njh /njs
') do (
echo %%~fb : %%a
set /a "total+=%%a"
)
popd
)
echo Total files: %total%

Batch remove parenthesis from file name

After successfully removing a bunch of Google Drive Folder duplicates, some files retain a "filename(2)"name.
Is there a way to batch rename every file so the parenthesis and the number inside the parenthesis is gone?
That includes folders and sub-folders.
Try like this :
Create a file test.bat with the code below in it and replace the path to test in the var $path
#echo off
set $path="C:\Users\CN Micros\Desktop\PROGRAMMATION\test"
for /f "tokens=1-3 delims=^(^)" %%a in ('dir /b/a-d %$path%') do (
if exist %$path%\"%%a(%%b)%%c" echo ren %$path%\"%%a(%%b)%%c" "%%a%%c"
)
pause
Then run it in the CMD or by double clicking.
If the output is ok for you remove the echo
The program create 3 tokens : %%a = what's before the (), %%b What's inside the () and %%c what's after the ().
Then we arrange this 3 tokens to rename the files without the ().
If you have some file who have the same final name ie : "file(1)name", "file(2)name" --> "filename"
It will work only with the first one. If you have this case you have to add a counter at the end of file to be sure that they will be renamed.
This will create renfiles.bat.txt for you to examine in Notepad and then rename to .bat and execute if you are happy with it.
#echo off
dir /b /a-d *(*).* |find /i /v "%~nx0" |find /i /v "repl.bat" |repl "(.*)\(.*\)(\..*)" "ren \q$&\q \q$1$2\q" xa >"renfiles.bat.txt"
This uses a helper batch file called repl.bat - download from: https://www.dropbox.com/s/qidqwztmetbvklt/repl.bat
Place repl.bat in the same folder as the batch file or in a folder that is on the path.
Edit: This version will recurse through subdirectories:
#echo off
dir /b /s /a-d *(*).* |find /i /v "%~nx0" |find /i /v "repl.bat" |repl ".*\\(.*)\(.*\)(\..*)" "ren \q$&\q \q$1$2\q" xa >"renfiles.bat.txt"
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
FOR /f "delims=" %%a IN (
'dir /b /s /a-d "%sourcedir%\*" '
) DO (
SET "name=%%~na"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "newname=!name:)=!"
SET "newname=!newname:(=!"
IF "!name!" neq "!newname!" (
IF EXIST "%%~dpa!newname!%%~xa" (ECHO cannot RENAME %%a
) ELSE (ECHO(REN "%%a" "!newname!%%~xa")
)
endlocal
)
GOTO :EOF
You'd need to set your required directory into sourcedir. I used u:\sourcedir which suits my testing.
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.

Resources