简体   繁体   中英

count number of subdirectories within subdirectories

I'm looking to see the maximum number of subdirectories within any subdirectory within a folder. Ie,

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.

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

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);

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM