BATCH Comparing two folders recursively - windows

i'm updating a big save script and i need to compare 2 folders recursively. I think i'm pretty close but can't understand what's missing...
The code is the following :
set Rep1=f:\album1
set Rep2=f:\album2
set logfile=f:\logfile.txt
for /r "%Rep1%\" %%i in (*) do (
fc "%%i" "%Rep2%\%%~nxi"
iF ERRORLEVEL == 1 echo "%%i" et "%Rep2%\%%~nxi" Sont differents >> %logfile%
iF ERRORLEVEL == 2 echo "%%i" et "%Rep2%\%%~nxi" Un des fichier est manquant >> %logfile%
)
pause
The problem is that it compares all files like f:\album1\folder1\file1.txt with f:\album2\file1.txt so the comparing is always wrong... You can see the result in the picture:
If any of you find a solution, thank you a lot !
Vincs

#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "targetdir=U:\destdir"
FOR /f "delims=" %%a IN (
'dir /s /b /a-d "%sourcedir%\*" '
) DO (
SET "foundfile="
FOR /f "delims=" %%c IN (
'dir /s /b /a-d "%targetdir%\%%~nxa" 2^>nul'
) DO (
SET foundfile=Y
FC /b "%%a" "%%c" >NUL
IF ERRORLEVEL 1 ECHO different "%%a" "%%c"
)
IF NOT DEFINED foundfile ECHO missing "%%a" from "%targetdir%"
)
FOR /f "delims=" %%a IN (
'dir /s /b /a-d "%targetdir%\*" '
) DO (
SET "foundfile="
FOR /f "delims=" %%c IN (
'dir /s /b /a-d "%sourcedir%\%%~nxa" 2^>nul'
) DO (
SET foundfile=Y
)
IF NOT DEFINED foundfile ECHO missing "%%a" from "%sourcedir%"
)
GOTO :EOF
You would need to change the settings of sourcedir and targetdir to suit your circumstances.
One problem with your approach is that the file %%i is compared to "%Rep2%\%%~nxi" which means only in %rep2%, not in the subdirectory.
Another problem is
IF ERRORLEVEL == 1 echo "%%i" et "%Rep2%\%%~nxi" Sont differents >> %logfile%
which compares the literal string ERRORLEVEL to the literal string 1 so will not find a match. To use your method, you would have to turn delayedexpansion on with a setlocal enabledelayedexpansion statement and then use IF !ERRORLEVEL!==1 ...
The solution posted above compares files that have the same name and extension wherever they are found on the tree, so if file1.txt is anywhere in the first tree, then it will be compared to all the file1.txt files found anywhere in the second tree.
Note also that there is a standard anomaly when dealing with a standard system that gives files both a long and a short name. Each file potentially has two names - and dir matches on either name.

Magoo's answer already points out the major problems of your code.
However, the solution he provided uses only the file names for finding the files to compare, so it will fail if files with equal names occur at different locations in the source and target directories, or if multiple files with equal names occur within any of the directory trees.
Here is a similar solution, based on the dir /S command, which also requires each file name in the trees to be unique and to occur at the same location within source and target (it does not check for files present in only one of the source or target trees):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Provide source and target directory paths here:
set "SOURCE=.\source"
set "TARGET=.\target"
rem Convert relative paths to absolute ones:
for %%D in ("%SOURCE%") do set "SOURCE=%%~fD"
for %%D in ("%TARGET%") do set "TARGET=%%~fD"
for /F "eol=| delims=" %%I in ('
2^> nul dir /B /S /A:-D "%SOURCE%\*.*"
') do (
set "SRC=%%~fI"
for /F "eol=| delims=" %%J in ('
2^> nul dir /B /S /A:-D "%TARGET%\%%~nxI"
') do (
set "TRG=%%~fJ"
setlocal EnableDelayedExpansion
> nul fc /B "!SRC!" "!TRG!" || echo "!SRC!" and "!TRG!" differ.
endlocal
)
)
endlocal
exit /B
This code snippet compares only files which occur in both source and target directory trees. If respective files are different, a related message is returned.
To overcome the issue with equal file names at different locations, let us use the xcopy /L command, because this is capable of returning a list of paths relative to the source directory (the /L option prevents xcopy from copying any files; the appended find command filters out the ? File(s) summary lines):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Provide source and target directory paths here:
set "SOURCE=.\source"
set "TARGET=.\target"
rem Convert relative paths to absolute ones:
for %%D in ("%SOURCE%") do set "SOURCE=%%~fD"
for %%D in ("%TARGET%") do set "TARGET=%%~fD"
pushd "%SOURCE%"
for /F "eol=| delims=" %%I in ('
2^> nul xcopy /L /S /Y ".\*.*" "%TEMP%\" ^| find ".\"
') do (
set "SRC=%%~fI"
pushd "%TARGET%"
for /F "eol=| delims=" %%J in ('
2^> nul xcopy /L /Y "%%~I" "%TEMP%\" ^| find ".\"
') do (
set "TRG=%%~fJ"
setlocal EnableDelayedExpansion
> nul fc /B "!SRC!" "!TRG!" || echo "!SRC!" and "!TRG!" differ.
endlocal
)
popd
)
popd
endlocal
exit /B
This code snippet also compares only files which occur in both source and target directory trees. If you also want to check whether each fils is present in both source and target trees, check out Magoo's answer which provides a good idea of how to accomplish that (two-loop approach).

Related

Delete all files in a folder but skip files that cointain certain string

I'm trying to clean the temp files, but I want skip some files that cointain a certain name. No matter the extension it is.
Tried this way:
#echo off
setlocal EnableDelayedExpansion
for /f "eol=: delims=" %F in ('dir "%windir%/temp" /b /a-d * ') do find "TESTE" %F > nul || del "%F"
pause
Wanted all files that cointains TESTE in name got skipped from deletation.
But my script not even run.
Can someone explain me what is wrong?
It is not absolutely clear whether you want to exclude files from deletion whose names or whose contents include a certain string; anyway, this is for the former:
pushd "%WinDir%\TEMP" && (
for /F "delims= eol=|" %%F in ('
dir /B /A:-D "*.*" ^| findstr /V /I /C:"TESTE"
') do (
ECHO del /F "%%F"
)
popd
)
Once you are satisfied with the output, remove the upper-case ECHO command.
The above script would also not delete files whose extensions contain the given string. But if you want to regard only the base name, you might want to use this code instead:
pushd "%WinDir%\TEMP" && (
for /F "delims= eol=|" %%F in ('
dir /B /A:-D "*.*"
') do (
set "NAME=%%~nF"
setlocal EnableDelayedExpansion
if /I "!NAME:TESTE=!"=="!NAME!" (
endlocal
ECHO del /F "%%F"
) else endlocal
)
popd
)
You should be able to use findstr.exe with its /V and /M options to list your unwanted files.
#SetLocal EnableExtensions
#For /F "Delims=" %%G In (
'%__AppDir__%findstr.exe /VPMLI "TESTE" "%SystemRoot%\TEMP\*" 2^>NUL'
)Do #Del /A/F "%%G"
#Pause
Please note that the \Windows\Temp directory is usually protected, so you may need to run this script 'as administrator'.

How to rename only sub-folders without changing parent folder names

I have a folder containing several hundred sub-folders in the format Name, ID. Each of these folders contain several sub folders, some of which contain spaces in their names. I would like to rename only the sub folders and not the parent folders by replacing the spaces with underscores, e.g. C:\Location\John, 1234\My Documents to C:\Location\John, 1234\My_Documents.
I have tried modifying a piece of script I have found on here but it changes the parent folder as well
Here is the unedited code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "StartFolder=C:\Tydelik"
cd /D %SystemRoot%
set "RenameError="
rem Rename all folders containing at least one space character in folder name.
for /F "delims=" %%I in ('dir "%StartFolder%\* *" /AD /B /S 2^>nul') do call :RenameFolder "%%I"
if defined RenameError echo/& pause
rem Restore initial environment and exit this batch file.
endlocal
goto :EOF
:RenameFolder
set "NewFolderName=%~nx1"
set "NewFolderName=%NewFolderName: =_%"
set "FolderPath=%~dp1"
if not exist "%FolderPath%" set "FolderPath=%FolderPath: =_%"
set "FullFolderName=%FolderPath%%~nx1"
if not exist "%FullFolderName%\" set "RenameError=1" & goto :EOF
for %%J in ("%FullFolderName%") do set "FolderAttributes=%%~aJ"
if "%FolderAttributes:~3,1%" == "h" %SystemRoot%\System32\attrib.exe -h "%FullFolderName%"
ren "%FullFolderName%" "%NewFolderName%" 2>nul
if errorlevel 1 goto ErrorFolderRename
if "%FolderAttributes:~3,1%" == "h" %SystemRoot%\System32\attrib.exe +h "%FolderPath%%NewFolderName%"
goto :EOF
:ErrorFolderRename
echo Error renaming folder "%FullFolderName%"
set "RenameError=1"
if "%FolderAttributes:~3,1%" == "h" %SystemRoot%\System32\attrib.exe +h "%FullFolderName%"
goto :EOF
As I said the expected output for each sub folder should be C:\Location\John, 1234\My_Documents instead of C:\Location\John, 1234\My Documents. Currently with the code I have, I get C:\Tydelik\John,_1234\My_Documents.
While Compo's solution renames folders "depth=2", this renames just the "leafes" (very last folders of a tree, "depth=last"). I kept your call approach to avoid delayed expansion and resulting possible problems (folder names with ! - unlikely in your situation, but one never knows...)
#echo off
setlocal
set "sourcedir=..\..\"
for /f "delims=" %%I in ('dir "%sourcedir%" /ad /b /s 2^>nul') do call :RenameFolder "%%I"
goto :eof
:RenameFolder
dir /ad /b /s "%~1" 2>nul | find /v "" >nul && goto :eof ::skip renaming, if a subfolder exists
set "leaf=%~nx1"
ECHO ren "%~1" "%leaf: =_%"
goto :eof
Note: for security reasons I disabled the ren command by just echoing it. If it works as intended, remove the ECHO.
Here's an example of what I think you're looking for, based upon the fact that you're interested only in renaming subdirectories of "C:\Tydelik\Name, ID", not any contained within those subdirectories:
#Echo Off
SetLocal DisableDelayedExpansion
Set "SourceDir=C:\Tydelik"
For /F "EOL=?Delims=" %%A In ('Dir /B/AD "%SourceDir%" 2^>NUL'
)Do Set "TargetDir="&For /F "EOL=?Delims=" %%B In (
'Dir /B/AD "%SourceDir%\%%A" 2^>NUL') Do (Set "TargetDir=%%B"
SetLocal EnableDelayedExpansion
If Not "!TargetDir: =!"=="!TargetDir!" (
Ren "%SourceDir%\%%A\%%B" "!TargetDir: =_!")
EndLocal)

Batch for scanning dirs needs a possibility to skip defined dirs

I have a batch which scans a directory structure and gives out the size of directorys I defined he should look for.
E.g. the directory '10XXX' contains the subdirectorys '10001', '10002' and '10003'. Now lets say that the batch should look for the dir 'XYZ' in those subdirs. Result is a .csv with informations like '10001\XYZ' 100 Bytes; '10002\XYZ' 2000 Bytes; '10003\XYZ' 0 Bytes.
The problem is the batch scans every directory every time it runs, which takes a lot of time and resources.
My idea is to implement a section into the code that everytime the batch starts scanning a dir it takes a look into a .txt (for example) and when the dir name is found in that file the batch skips the dir.
This is maybe not the most effective solution, but it will take less time and resources than scanning every dir every time.
So different ideas are of course welcome.
Unfortunately I have not enough knowledge to do it myself so I hope that someone could help me.
Code:
#echo off &setlocal
set /p rootfolder=<Enter_Directory_Path_Here.txt
set /p savefolder=<Enter_Save_Directory_Here.txt
set "batpath=%~dp0"
pushd "%rootfolder%"
for /d %%i in (*) do (
set "foldername=%%~nxi"
set "folder=%%i"
>"%batpath%%%~nxi.csv" type nul
for /f "delims=" %%j in ('dir /ad /b "%%i\*"') do (
set "subfolder=%%j"
call :procfolder
)
>"%batpath%%%~nxi.~csv" type nul
for /f "usebackq tokens=1* delims==" %%j in ("%savefolder%\patterns.txt") do (
>>"%batpath%%%~nxi.~csv" findstr /b %%k "%batpath%%%~nxi.csv"
if errorlevel 1 >>"%batpath%%%~nxi.~csv" echo "%%j";"not found"
)
>nul move /y "%batpath%%%~nxi.~csv" "%batpath%%%~nxi.csv"
)
popd
call :cleaner
exit /b
:procfolder
setlocal
for /f "tokens=3" %%i in ('dir /a /-c "%folder%\%subfolder%\"^|findstr /c:"Datei(en)"') do >>"%batpath%%foldername%.csv" echo "%subfolder%";%%i;"Bytes"
for /f "delims=" %%i in ('dir /ad /b "%folder%\%subfolder%\"') do (
set "subfolder=%subfolder%\%%i"
call :procfolder
)
endlocal
exit /b
A sample of reading a text file to match
for /F "tokens=*" %%X in (N:\SomeDirectory\SomeFile.txt) do if /I "%foldername%" EQU "%%X" goto SKIPIT
REM Do your normal stuff here
:SKIPIT
If the exclusion filename is encapsulated in quotes, add the usebackq option:
for /F "usebackq tokens=*" %%X in ("N:\Some Directory\Some File.txt") do if /I "%foldername%" EQU "%%X" goto SKIPIT
REM Do your normal stuff here
:SKIPIT

How can I get a count of all sub-folders with a specific name in Windows?

I am trying to figure out how many sub-folders contain a folder called "img", so I want to implement a recursive count of all subfolders called "img", if this is possible.
Also, how can I do a count that is one level deep? i.e it will look only one folder deep for all folders called "img"?
So, if my folder structure was :
>folder1
>img
>folder2
>subfolder
>img
>folder3
>subfolder
>subsubfolder
>img
>folder4
>img
The first part would return 4, and the second script would return 2.
Is this possible to do either from CMD or with some Windows tool?
I've had a look around and the closest I could find was a CLI script to print all folders and sub-folders to a file :
dir /directory:n /ad > f.txt
from here.
Which I could then copy paste into Notepad++ and do some manual messing around with...but I was hoping there was a cleaner/faster solution than this in case I ever come across this in the future. What would I need to write for this?
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir\t w o"
SET /a onelevelcount=0
FOR /f "delims=" %%a IN (
'dir /b /ad "%sourcedir%" 2^>nul'
) DO (
IF EXIST "%sourcedir%\%%a\img\." SET /a onelevelcount+=1
)
ECHO one level down=%onelevelcount%
SET /a grandtotal=0
FOR /f %%c IN ('dir /s /ad "%sourcedir%" 2^>nul^|findstr /i /e /r "\\img"') DO SET /a grandtotal+=1
ECHO grand total=%grandtotal%
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
As for how you'd figure it out - practice makes perfect. Lots of examples on SO.
type the folowing code on notepad and save it as .dll
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir\t w o"
SET /a onelevelcount=0
FOR /f "delims=" %%a IN (
'dir /b /ad "%sourcedir%" 2^>nul'
) DO (
IF EXIST "%sourcedir%\%%a\img\." SET /a onelevelcount+=1
)
ECHO one level down=%onelevelcount%
SET /a grandtotal=0
FOR /f %%c IN ('dir /s /ad "%sourcedir%" 2^>nul^|findstr /i /e /r "\\img"') DO SET /a grandtotal+=1
ECHO grand total=%grandtotal%
GOTO :EOF`enter code here`

look for file of a certain type one subfolder down, sort by date modified (newest first), and display subfolder name (NOT filename) in sorted order

This is a very specific question, so I've separated it into 3 parts.
Look in all subfolders for a certain file: world.sav, but only 1 level down, i.e.
C:\workingdir\foo\world.sav
C:\workingdir\bar\world.sav
C:\workingdir\baz\world.sav
C:\workingdir\qux\world.sav
is fine, but
C:\workingdir\foo\bar\world.sav
C:\workingdir\world.sav
isn't, etc.
Sort these world.sav files by date modified (newest first).
Display the name of the subfolder each world.sav file is in, in the previously sorted order. i.e. if the above list was date-sorted into
C:\workingdir\qux\world.sav (newest)
C:\workingdir\bar\world.sav
C:\workingdir\foo\world.sav
C:\workingdir\baz\world.sav (oldest)
then the output would be
qux
bar
foo
baz
I've tried numerous methods involving DIR, FORFILES and manipulation of variables, but the 2 main problems I've come across so far are
Both commands mentioned above will arrange by subfolder, then by date, and I can't find a way to avoid it.
Date formatting. It appears to be different for every localization of Windows, and I really want this to be locale-independent.
Also, it must support spaces in folder names.
EDITED version with a wait message:
#echo off
echo Please wait...
cd /d "c:\workingdir"
(for /d %%a in (*) do #robocopy "%%a" "%%a" world.sav /L /nocopy /is /njh /njs /ndl /nc /ns /ts)|(
#cls
for /f "tokens=2*" %%b in ('sort /r') do #for %%z in ("%%~dpc\.") do #echo %%~nz
)
cd /d "%~dp0"
echo.
pause
original version below
This works here:
robocopy is used on each folder in the directory to generate a list (UTCdate time drv:\path\world.sav format) and that is sorted to get most recent at the top of the list, which the second for parses to extract the drv:\path\folder\world.sav and the last for loop prints out just the folder name.
#echo off
cd /d "c:\workingdir"
(for /d %%a in (*) do #robocopy "%%a" "%%a" world.sav /L /nocopy /is /njh /njs /ndl /nc /ns /ts)|for /f "tokens=2*" %%b in ('sort /r') do #for %%z in ("%%~dpc\.") do #echo %%~nz
pause
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET /a count=0
:: remove variables starting $
FOR /F "delims==" %%a In ('set $ 2^>Nul') DO SET "%%a="
FOR /f "delims=" %%a IN (
'dir /b /ad "%sourcedir%\*" '
) DO IF exist "%sourcedir%\%%a\world.sav" CALL :set$ "%%a"
IF %count%==0 ECHO No files found&GOTO :EOF
IF %count%==1 ECHO %$1%&GOTO :EOF
:sortol
SET /a start=1
SET /a swap=0
:sortil
CALL :compdt
IF %start% neq %count% GOTO sortil
IF %swap% neq 0 GOTO sortol
FOR /l %%a IN (1,1,%count%) DO CALL ECHO %%$%%a%%
GOTO :EOF
:set$
SET /a count+=1
SET "$%count%=%~1"
GOTO :eof
:compdt
CALL SET f1=%%$%start%%%
SET /a START +=1
CALL SET f2=%%$%start%%%
FOR /f %%a IN (
'echo f^|xcopy /L /y /d "%sourcedir%\%f1%\world.sav" "%sourcedir%\%f2%\world.sav"'
) DO SET copies=%%a
IF %copies%==0 GOTO :EOF
SET /a swap +=1
SET /a START -=1
SET "$%start%=%f2%"
SET /a START +=1
SET "$%start%=%f1%"
GOTO :eof
This worked for me. You'd need to set sourcedir to suit your purpose.
Essentially, it builds a list of the next-level-down directory names which contain the target filename in variables $1...$whatever, then sorts the names using a simple bubble-sort which relies on the characteristics of xcopy. XCOPY's /L switch simply lists the files that would be copied; /y tells it to 'go ahead (and not copy)' and /d says 'only later files.ECHOingftellsxcopythat the desired "copy" is a file-copy, so it responds1 file(s) copiedor0...` as its last line, depending on whether the source file was later than the destination.
To reverse the order, simply change the if %copies%==0 to if %copies%==1
An idea :
#echo off
Setlocal EnableDelayedExpansion
set $WorkDir="C:\eh\guh\blugh\fneh\mork\workingdir"
for /f "tokens=*" %%a in ('dir /s/b/a-d %$WorkDir%') do (
for /f "tokens=9 delims=\" %%b in ('echo %%a') do (
if /i "%%b"=="world.sav" call:next "%%~pa" %%~ta
)
)
::Resultat
for /f "tokens=2 delims==" %%r in ('set #') do echo %%r
exit/b
:next
for /f "tokens=7 delims=\" %%c in ('echo %~1') do set $Path=%%c
for /f "tokens=1-3 delims=/" %%c in ('echo %~2') do set #%%e%%d%%c=!$path!

Resources