Reputation: 1218
I'm looking to see the maximum number of subdirectories within any subdirectory within a folder. I.e.,
Folder==>
SubA==>
b.xlsx
SubB==>
SubB.C==>
b.c.xlsx
Such that this would return (2) as SubB.C is two folders deep.
I've tried
set count=
for /d %%a in (U:\*) do set /a count+=1
echo %count%
But the recursion is stumping me. I can't get into the second/third/fourth level subdirectories.
Upvotes: 0
Views: 421
Reputation: 34909
You need to use some kind of recursion for that task. What about a sub-routine that loops through the sub-directories and calls itself for each one? What I mean is the following:
@echo off
rem // Define constants here:
set "_PATH=%~1" & rem // (path of the root directory to process)
rem // Define global variables here:
set /A "$DEPTH=0" & rem // (variable to determine the greatest depth)
rem // Initialise variables:
set /A "DEEP=0" & rem // (depth of the current directory branch)
rem // Call recursive sub-routine, avoid empty argument:
if defined _PATH (call :SUB "%_PATH%") else (call :SUB ".")
rem // Return found depth:
echo %$DEPTH%
exit /B
:SUB <root_path>
rem // Loop through all sub-directories of the given one:
for /D %%D in ("%~1\*") do (
rem // For each sub-directory increment depth counter:
set /A "DEEP+=1"
rem // For each sub-directory recursively call the sub-routine:
call :SUB "%%~fD"
)
rem // Check whether current branch has the deepest directory hierarchy:
if %$DEPTH% lss %DEEP% set /A "$DEPTH=DEEP"
rem // Decrement depth counter before returning from sub-routine:
set /A "DEEP-=1"
exit /B
Just as an alternative idea, but with a bit worse performance, you could also determine the number of backslashes (\
) in the resolved paths of all sub-directories, retrieve the greatest number and subtract that number of the root directory from the greatest one, like this:
@echo off
rem // Define constants here:
set "_PATH=%~1" & rem // (path of the root directory to process)
rem // Define global variables here:
set /A "$DEPTH=0" & rem // (variable to determine the greatest depth)
rem // Change to root directory:
pushd "%_PATH%" || exit /B 1
rem // Resolve root directory:
call :SUB "."
rem // Store total depth of root directory:
set /A "CROOT=$DEPTH, $DEPTH=0"
rem // Process all sub-directories recursicely:
for /D /R %%D in ("*") do (
rem // Determine greatest depth relative to root:
call :SUB "%%~fD" -%CROOT%
)
rem // Change back to original directory:
popd
rem // Return found depth:
echo %$DEPTH%
exit /B
:SUB <val_path> [<val_offset>]
rem // Resolve provided sub-directory:
set "ITEM=%~f1" & if not defined ITEM set "ITEM=."
rem // Initialise variables, apply count offset:
set "COUNT=%~2" & set /A "COUNT+=0"
rem // Count number of backslashes in provided path:
for %%C in ("%ITEM:\=" "%") do (
set /A "COUNT+=1"
)
rem // Check whether current branch has the deepest directory hierarchy:
if %$DEPTH% lss %COUNT% set /A "$DEPTH=COUNT"
exit /B
Upvotes: 2
Reputation: 130839
You can hack JREPL.BAT (regular expression text processor) to count the number of backslashes in each path. I list the root folder first so that I can subtract that value from the max to get the maximum subfolder depth within the root folder.
@echo off
pushd %1
(cd & dir /b /s /ad) | jrepl "\\" "$txt=false;n++" ^
/JMATCHQ ^
/JBEG "var n, root, max=0" ^
/JBEGLN "n=0" ^
/JENDLN "if (ln==1) root=n; if (n>max) max=n" ^
/JEND "output.writeLine(max-root)"
popd
I used a lot of ^
line continuation to make the code easy to read without horizontal scrolling.
If you want to capture the result in a variable, then the easiest solution is to write the CD and DIR output to a temp file and use the JREPL /RTN option to save the result.
@echo off
setlocal
set "file=%temp%\folders.txt"
pushd %1
(cd & dir /b /s /ad) >"%file%"
popd
call jrepl "\\" "$txt=false;n++" /F "%file%" ^
/JMATCHQ ^
/JBEG "var n, root, max=0" ^
/JBEGLN "n=0" ^
/JENDLN "if (ln==1) root=n; if (n>max) max=n" ^
/JEND "output.writeLine(max-root)" ^
/RTN count
del "%file%"
echo %count%
On my machine, my code runs about twice as fast as Compo's powershell solution, and much, much faster than the aschipfl recursive pure batch solution.
Here is a hybrid JScript/batch solution that doesn't require JREPL.BAT
@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::--- Batch section within JScript comment that calls the internal JScript ----
@echo off
pushd %1 || exit /b
(cd & dir /b /s /ad) | cscript //E:JScript //nologo "%~f0"
popd
exit /b
----- End of JScript comment, beginning of normal JScript ------------------*/
var root=0, max=0, level;
while( !WScript.StdIn.AtEndOfStream ) {
level=WScript.StdIn.ReadLine().split('\\').length-root;
if (!root) root=level;
if (level>max) max=level;
}
WScript.StdOut.WriteLine(max);
If you want to capture the result in a variable, then you can use a FOR /F loop:
@if (@X)==(@Y) @end /* Harmless hybrid line that begins a JScript comment
::--- Batch section within JScript comment that calls the internal JScript ----
@echo off
pushd %1 || exit /b
for /f %%A in ('(cd ^& dir /b /s /ad^) ^| cscript //E:JScript //nologo "%~f0"') do set depth=%%A
popd
set depth
exit /b
----- End of JScript comment, beginning of normal JScript ------------------*/
var root=0, max=0, level;
while( !WScript.StdIn.AtEndOfStream ) {
level=WScript.StdIn.ReadLine().split('\\').length-root;
if (!root) root=level;
if (level>max) max=level;
}
WScript.StdOut.WriteLine(max);
Upvotes: 3
Reputation: 38623
I've no idea about performance, (only an issue with large directory trees), but you could maybe try a PowerShell
based method from your batch file too:
@Echo Off
CD /D "U:\" 2>Nul || Exit /B
PowerShell -NoL -NoP "GCI .\ -R|?{$_.PSIsContainer}|Select @{Name='FullName';Expression={$_.FullName.Replace($PWD,'')}},@{Name='FolderDepth';Expression={($_.FullName.Split('\').Count)-($PWD.Path.Split('\').Count)}}|Sort -Des FolderDepth|Select -F 1 -Exp FolderDepth"
Pause
Edit (to satisfy the requirements in lit's comment):
@Echo Off
CD /D "U:\" 2>Nul || Exit /B
Set "MaxLevels="
For /F %%A In ('
PowerShell -NoL -NoP "GCI .\ -R|?{$_.PSIsContainer}|Select @{Name='FullName';Expression={$_.FullName.Replace($PWD,'')}},@{Name='FolderDepth';Expression={($_.FullName.Split('\').Count)-($PWD.Path.Split('\').Count)}}|Sort -Des FolderDepth|Select -F 1 -Exp FolderDepth"
') Do Set "MaxLevels=%%A"
Set MaxLevels 2>Nul
Pause
Upvotes: 3