Why the loop is not running as expected? - batch-file

I have folders like E:\Backups\code\Hazard\test1 ... testn
And inside these test folders something like E:\Backups\code\Hazard\test1\it0 ... itn
The root folder is E:\Backups\code from where the code runs.
The below code runs on each subfolders and copies summary.yml from it0 folder to latest it(n) folder.
Why the code runs just for test1 folder and then hangs?
setlocal ENABLEDELAYEDEXPANSION
set root=%cd%
for /D %%X in (%root%\*) do (
echo %%X
cd %%X
for /D /r %%b in (*) do (
cd %%b
echo %%b
for /f "tokens=1,2,*" %%a in ('robocopy . . file.txt /l /nocopy /is /s /nc /ns /ts /ndl /njh /njs ^| sort /r') do set "lastFolder=%%~dpc" & goto :done
:done
echo Last folder : %lastFolder%
for /d %%j in (*) do (
if /i "%%~nj"=="it0" COPY %%j\summary.yml %lastFolder%
)
cd ..
)
)

There are two main problems in the code:
If goto is used inside a for loop, the loop is cancelled
If you set a variable inside a block of code (code inside parenthesis), to retrieve the value of the variable inside the same block of code you need delayed expansion, enabling it with setlocal enabledelayedexpansion and changing the syntax used to retrieve the value in the variable from %var% into !var!.
But
the goto can be removed as indicated in the previous answer,
the delayed expansion is not needed. Instead of storing the value from the for replaceable parameter inside a variable, just use the replaceable parameter
Not tested, but more or less
#echo off
setlocal enableextensions disabledelayedexpansion
rem E:\Backups\ code \ Hazard \ test1 \ it0 ... itn
rem ^root ^ %%X ^ %%Y ^ %%~dpc
for /D %%X in ("*") do for /D %%Y in ("%%~fX\*") do for /f "tokens=1,2,*" %%a in ('
robocopy "%%~fY." "%%~fY." file.txt /l /nocopy /is /s /nc /ns /ts /ndl /njh /njs
^| sort /r 2^>nul
^| cmd /q /v /c "(set /p .=&echo(!.!)"
') do copy "%%~fY\it0\summary.yml" "%%~dpc."
Being E:\Backups\code the current active directory:
%%X will enumerate the folders under E:\Backups\code (Hazard)
%%Y will enumerate the folders under E:\Backups\code\Hazard (testn)
%%a executes a robocopy command to locate the folder containing the latest file.txt file
sort /r sorts the list of files in descending order so the latest file is the first in the list
cmd retrieves and outputs only the first line
With all the information available in the several for replaceable parameters, execute the indicated copy command.

I'm not sure about what the line with robocopy should do. It looks like this command is for getting name of last subdirectory in current directory.
Perhaps this code works better. But I could not test it.
setlocal EnableDelayedExpansion
set "root=%cd%"
for /D %%X in ("%root%\*") do (
echo %%X
cd "%%~X"
for /D /r %%b in (*) do (
cd "%%~b"
echo %%b
call :GetLastFolder
echo Last folder : !lastFolder!
for /d %%j in (*) do (
if /i "%%~nj"=="it0" copy "%%j\summary.yml" "!lastFolder!"
)
cd ..
)
)
goto :EOF
:GetLastFolder
for /f "tokens=1,2,*" %%a in ('robocopy . . file.txt /l /nocopy /is /s /nc /ns /ts /ndl /njh /njs ^| sort /r') do set "lastFolder=%%~dpc" & goto :EOF
goto :EOF
It is at least necessary to reference environment variable lastFolder with exclamation marks instead of percent signs to really use delayed expansion as needed here.
goto :EOF exits the subroutine resulting in continuing on line below call :GetLastFolder.
There is one more goto :EOF or alternatively exit /B after main code necessary to avoid that the code of the subroutine is executed once more after most outer for loop finished. This goto :EOF results in exiting the processing of this batch file.
For understanding the used commands and how they work, open a command prompt window, execute there at least the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
for /?
goto /?
set /?

Related

How can I get special directory list by batch file?

Following is the directory that I use.
C:\test>dir /s /b /a:d
C:\test\A
C:\test\A\a
C:\test\A\b
C:\test\A\a\a
C:\test\A\a\b
C:\test\A\a\a\20160101
C:\test\A\a\a\20160816
C:\test\A\a\b\20160101
C:\test\A\a\b\20160816
C:\test\A\b\a
C:\test\A\b\b
C:\test\A\b\a\20160101
C:\test\A\b\a\20160816
C:\test\A\b\b\20160101
C:\test\A\b\b\20160816
Using dir /s /b /a:d gets all of folder directory.
How can I get the file list to go under 3 layer of test folder by batch file?
I would like to get the following list:
C:\test\A
C:\test\A\a
C:\test\A\b
C:\test\A\a\a
C:\test\A\a\b
C:\test\A\b\a
C:\test\A\b\b
#echo off
cls
for /f "delims=" %%I in ('dir /s /b /ad') do (
call :countAppr "%%~I"
)
exit /b
:countAppr
set "string=%~1"
set count=0
:again
set "oldstring=%string%"
set "string=%string:*\=%"
set /a count+=1
if not "%string%" == "%oldstring%" goto :again
if %count% leq 4 echo( %~1
exit /b
Explanation:
Loops thorough files
Count for the amount \ appears in the folder name
If less than or equal 4, return the folder name
To show folder only in the folder level, change leq to equ. The level deep can also be changed.
Note: Some of the script is copied and edited from Stephan's answer here.
One simple solution is to use the robocopy command. While intended for file copy operations, it includes a /L switch to request not to copy but to list. Adjusting the switches to remove non needed information you can use
robocopy . . /e /nfl /njh /njs /ns /lev:4 /l
This will recursively (/e) list (/l) all selected elements under the current folder, not showing file information (/nfl), without job header (/njh), without summary (/njs), without file/size counters (/ns) for a deep search of four levels (current folder plus the three required levels below)
The output of the robocopy command includes some tabs/spaces at the start of the line. If you need to remove them, you can use something like
for /f "tokens=*" %a in ('robocopy . . /e /nfl /njh /njs /ns /lev:4 /l') do echo %a
Or, from a batch file
for /f "tokens=*" %%a in ('robocopy . . /e /nfl /njh /njs /ns /lev:4 /l') do echo %%a
edited If robocopy usage is a problem (not available/allowed in your system), or you need (from comments) to limit the output to only the last level, you can use something like
#echo off
setlocal enableextensions disabledelayedexpansion
rem Retrieve folder from command line, default current folder
for /f "delims=" %%a in ("%~f1\.") do set "target=%%~fa"
echo ----------------------------------------------------------------------
rem Call subroutine searching for ALL folders up to 3 levels
call :treeDump target 3
echo ----------------------------------------------------------------------
rem Call subroutine searching for folders ONLY 3 levels deep
call :treeDump target 3 true
goto :eof
rem Recursive folder search
:treeDump targetVar maxLevel forceLevel
rem targetVar = name of variable containing the folder to iterate
rem maxLevel = how many levels to search under target
rem forceLevel = only show the last requested level
setlocal disabledelayedexpansion
rem Check we are not searching too deep
2>nul set /a "nextLevel=%~2-1", "1/(%~2+1)" || goto :eof
rem Retrieve folder to iterate
setlocal enabledelayedexpansion & for %%a in ("!%~1!") do endlocal & (
rem Determine if current level must be shown
if "%~3"=="" (
echo %%~fa
) else (
if %nextLevel% lss 0 echo %%~fa
)
rem If not at the last level, keep searching
if %nextLevel% geq 0 for /d %%b in ("%%~fa\*") do (
set "target=%%~fb"
call :treeDump target %nextLevel% "%~3"
)
)
goto :eof
It uses a recursive function that will iterate over the directories tree. For each folder found, if we are not at the required level, subfolders are enumerated and the function is called again for each of them.

Folder1 and Folder2 logs not showing correctly in Batch files

This should be simple but is not working correctly.
FOR /D %%d IN ("D:\Folder*") DO (
FOR /R "%%d\logs" %%i IN (*) do echo %%i
)
pause
I have also tried:
FOR /D %%d IN ("D:\Folder*") DO (
SET fld=%%d
FOR /R "%fld%\logs" %%i IN (*) do echo %%i
)
pause
I feel that am missing something pretty basic.
Thanks!
First code
FOR /D %%d IN ("D:\Folder*") DO (
FOR /R "%%d\logs" %%i IN (*) do echo %%i
)
fails because you can not use a for replaceable parameter in the folder of a for /r. In the way the for parser works, the value to use as the starting folder used in the /R must be available before the for /d starts to execute.
Second code
FOR /D %%d IN ("D:\Folder*") DO (
SET fld=%%d
FOR /R "%fld%\logs" %%i IN (*) do echo %%i
)
fails because %fld% will not be parsed as you think. The full for /d %%d is parsed as only one command and all the %var% references inside this command are removed, being replaced with the value stored in the variables before starting to execute. You can not change a variable's value and retrieve the changed value inside the same command because while executing there is not any variable expansion included in the command, only the values the variables had when the command was parsed.
This is usually handled enabling delayed expansion so you can change where needed %var% into !var! to indicate to the parser that the variable expansion (value retrieval) should be delayed until the command is executed. So you could write something like
setlocal enabledelayedexpansion
FOR /D %%d IN ("D:\Folder*") DO (
SET "fld=%%d"
FOR /R "!fld!\logs" %%i IN (*) do echo %%i
)
BUT this will also not work, for the same reason indicated in the first code. The value to use in /R is required at parse time.
So, how to solve the problem?
You can avoid having to indicate the folder in the /R by first changing the current active directory to the folder being iterated by for /D %%i
for /d %%d in ("d:\Folder*") do (
pushd "%%~fd\logs" && (
for /r %%i in (*) do echo %%i
popd
)
)
or you can place the inner loop in a separate subroutine
for /d %%d in ("d:\Folder*") do call :process "%%d"
goto :eof
:process folderPath
for /r "%~1\logs" %%i in (*) do echo %%i
goto :eof
or you can replace the inner recursive for with a for /f processing a recursive dir
for /d %%d in ("d:\Folder*") do (
for /f "delims=" %%i in ('dir "%%~fd\logs" /a-d /b /s`) do echo %%i
)
You could also just simplify it.
For /D %%A In ("D:\Folder*") Do Where/R "%%~A" *

Batch script - Quick recursive find of first folder starting from a root location

I want to make a script which finds as quickly as possible first folder named Target starting from root location D:\ and return its absolute path.
Folder structure of root location (D:\) can be like this:
-DontSearchHereFolder
-Folder1\Subfolder1\SSubfolder1\SSSubfolder1\
-Folder2\Subfolder2\SSubfolder2\TargetFolder
-DontSearchHereFolder2
-Folder3\Subfolder3\
Output of the script should be: D:\Folder2\Subfolder2\SSubfolder2\TargetFolder
For now I tried 2 methods but it's not quick enough:
(1)
set TG=\TargetFolder
set root=D:\
cd %root%
for /f "delims=" %%a in ('dir /b /s /a:d "%root%" ^|findstr /e /i "%TG%"') do set "folderpath=%%~a"
(2)
for /d /r "%root%" %%a in (*) do if /i "%%~nxa"=="%TG%" set "folderpath=%%a"
(1) is quicker than (2)
Question1: Is it possible to specify in command to search only for a maximum of 2 folders "down" from root (e.g. D:\Folder1\Subfolder1) ?
Question2: Is it possible to specify folders that should be automatically skipped (e.g. DontSearchHereFolder1&2)
This batch code is exactly for what you have asked for optimized for speed. It ignores the two specified directories on first level and it searches for the folders maximal two folder levels deep.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "Root=D:"
set "TG=TargetFolder"
set "Ignore1=DontSearchHereFolder"
set "Ignore2=DontSearchHereFolder2"
for /D %%A in ("%Root%\*") do (
if "%%~nxA" == "%TG%" set "FolderPath=%%A" & goto Found
if not "%%~nxA" == "%Ignore1%" (
if not "%%~nxA" == "%Ignore2%" (
for /D %%B in ("%%A\*") do (
if "%%~nxB" == "%TG%" set "FolderPath=%%B" & goto Found
for /D %%C in ("%%B\*") do if "%%~nxC" == "%TG%" set "FolderPath=%%C" & goto Found
)
)
)
)
echo Could not find folder: "%TG%"
goto EndSearch
:Found
echo Found folder: "%FolderPath%"
:EndSearch
endlocal
The string comparisons are done case-sensitive for maximum speed.
No recursive subroutine calls are used as usually would be done for such tasks for maximum speed.
The comparisons for the directories to ignore in root folder are coded in batch script directly not using an array or a list of folder names for maximum speed.
Delayed expansion is not used for faster processing the command lines.
But much faster would be coding an executable in C/C++/C# for that task as processing the command lines of the batch file takes most likely the most time on searching for the folder.
Note: Command FOR ignores folders with hidden attribute set.
Well, I use for such tasks shareware tool Total Commander which supports searching only in selected folders for a specific folder not more than X levels deep extremely fast.
This should take into account all the limits indicated in the question, but unless a lot of folders are found inside the indicated exclusions, I don't think this should be faster, just give it a try
#echo off
setlocal enableextensions disabledelayedexpansion
set "source=d:\"
set "target=TargetFolder"
set "maxLevels=2"
set excludeFolders= "DontSearchHereFolder" "DontSearchHereFolder2"
for %%s in ("%source%") do for /f "tokens=*" %%f in ('
robocopy "%%~fs." "%%~fs." /l /nfl /njh /njs /nc /ns /s
/xd %excludeFolders% /lev:%maxLevels%
^| findstr /e /i /l /c:"\\%target%\\"
^| cmd /v /q /c"set /p .= &&(echo(!.!)"
') do echo "%%~f"
I think this is the fastest possible way to do this:
#echo off
setlocal EnableDelayedExpansion
if "%1" neq "" goto %1
set "root=D:\"
set "TG=TargetFolder"
set "exclude=/DontSearchHereFolder1/DontSearchHereFolder2/"
"%~F0" Input | "%~F0" Output > result.txt
set /P "folderpath=" < result.txt
del result.txt
echo First folder: %folderpath%
goto :EOF
:Input
cd "%root%"
for /D %%a in (*) do if "!exclude:/%%a/=!" equ "%exclude%" (
cd "%%a"
dir /B /S /A:D "%TG%" 2>NUL
cd ..
)
exit /B
:Output
set /P "folder="
echo "%folder%"
set "i=0"
for /F "tokens=2" %%a in ('tasklist /FI "IMAGENAME eq cmd.exe" /FO TABLE /NH') do (
set /A i+=1
if !i! equ 2 taskkill /PID %%a /F
)
exit /B
The folders to exclude are given in a slash-separated list; if this list is longer, the process run faster because more folders are skipped. The target folder is search in each one of the non-excluded folders via a dir /B /S /AD "%TG%" command, that is faster than any combination of other commands. The process ends as soon as the first folder name is received in the rigt side of the pipe; the remaining processing at left side of the pipe is cancelled via a taskkill command.

batch findstr to certain depth of subfolders

I had the below code for searching through sub directories for 2 exe files:
#echo off & setLocal EnableDELAYedeXpansion
for %%d in (c) do if exist %%d: (
for /f "delims=" %%a in ('dir/b/s/x %%d:\autolog.exe %%d:\autorun.exe 2^>nul ^| findstr /V /C:".*\.*\.*\.*\.*\.*\.*\.*\.*" /C:".*\.*\.*\.*\.*\.*\.*\.*" /C:".*\.*\.*\.*\.*\.*\.*" /C:".*\.*\.*\.*\.*\.*" /C:".*\.*\.*\.*\*"') do (
set var=%%a;!var!
))
echo %1,!var!, >>C:\test.txt
exit
While it works search for all subfolder (by using /s), I would like to have result returns only if it is within 4 subfolder level (e.g. c:\sf1\sf2\sf3\autorun.exe should be a valid result, while c:\sf1\sf2\sf3\sf4\autorun.exe and any finding further down the tree should be opt out and not returning as a result).
I use all wildcard combination (* | .| .*) along with "\V" in attempt to achieve it but failed. Why does it won't work or if there are other smarter way doing it?
Thanks in advance
Heres is a sample to limit to fourth folder level, using regular expressions in the findstr terms:
#echo off
for /f "delims=" %%a in ('dir /ad /b /s ^| findstr \\.*\\.*\\.*\\ ^| findstr /v \\.*\\.*\\.*\\.*\\') do echo %%a
pause
I was inspired by this answer. ROBOCOPY is available since Vista, and it is a robust utility that does more than copying files.
e.g. The /L switch prevents it from copying; while /LEV allows you to copy only the top N levels of root, which eliminates one FINDSTR.
Golfed
#echo off
SETLOCAL EnableDelayedExpansion
FOR /F "tokens=*" %%F in ('
ROBOCOPY "C:\." "C:\." autolog.exe autorun.exe /S /LEV:4 /IS /L /NS /NC /NDL /NJH /NJS^|FINDSTR \\.*\\.*\\.*\\
') do set "var=%%~fF;!var!"
echo %1,!var!,>>C:\test.txt
Formatted
#echo off
====SETLOCAL EnableDelayedExpansion EnableExtensions
set "list="
set ^"FORCMD=^
%__APPDIR__%ROBOCOPY.EXE C:\. C:\. autolog.exe autorun.exe^
/S %=non-empty Subdirectories=%^
/LEV:4 %=MAX 4 LEVels=%^
/IS %=Include Same files=%^
/L %=List only (don't copy)=%^
/NS %=No Size=%^
/NC %=No Class=%^
/NDL %=No Directory List=%^
/NJH %=No Job Header=%^
/NJS %=No Job Summary=%^
|%__APPDIR__%FINDSTR.EXE \\.*\\.*\\.*\\%=MIN 4 LEVels=%" It's convenient to use delayed expansion
FOR /F "tokens=*" %%F in ('!FORCMD!') do set "var=%%~fF;!var!"
::Due to weird expansion rules
::if VAR was undefined, it is set to '~,-1'
if DEFINED var set "var=!var:~,-1!" Remove trailing ;
>>"C:\test.txt" echo(%1,!var!,

batch script to recursively loop sub-directories and find missing files in 2 folders

I would like to compare recursively 2 folders and find missing files in them. I am using for loop to compare 2 folders but not able to search sub folders. Can somebody help?
Here is the code that I tried,
#echo off
if "%2" == "" GOTO Usage
cd /D %1
if errorlevel 1 goto usage
for %%x in (*.*) do if NOT exist %2\%%x echo missing %2\%%x
cd /D %2
for %%x in (*.*) do if NOT exist %1\%%x echo missing %1\%%x
goto end
:usage
echo Usage %0 dir1 dir2
echo where dir1 and dir2 are full paths
:end
try this, for explanation see comments in the code:
#ECHO OFF &SETLOCAL
SET "folder1=this"
SET "folder2=that"
REM delete variables
FOR /f "delims==" %%a IN ('set "$"') DO SET "%%a="
REM scanning folder1
FOR /r "%folder1%" %%a IN (*) DO SET "$%%~nxa=%%~a"
REM compare with folder2
FOR /r "%folder2%" %%a IN (*) DO (
IF NOT DEFINED $%%~nxa ECHO missing IN %folder1%: %%a
)
REM delete variables
FOR /f "delims==" %%a IN ('set "$"') DO SET "%%a="
REM scanning folder2
FOR /r "%folder2%" %%a IN (*) DO SET "$%%~nxa=%%~a"
REM compare with folder1
FOR /r "%folder1%" %%a IN (*) DO (
IF NOT DEFINED $%%~nxa ECHO missing IN %folder2%: %%a
)
ECHO Done.
#ECHO OFF
SETLOCAL
XCOPY /l /y /d "%~1\*" "%~2\*"|FIND "\"
XCOPY /l /y /d "%~2\*" "%~1\*"|FIND "\"
GOTO :EOF
Not strictly listing the files that are in one directory but not in the other; lists those and also those that exist in both but have a different timestamp.
Add /s to the XCOPY options to scan subdirectories also.

Resources