QUESTION :
In the following code, I need to take the path and size of folder and subfolders into a file. But when the loop runs for the second time, path and size are not getting printed to the file. size.txt only contains the path and size of the 1st folder. Please somebody help me.
@echo off
SETLOCAL EnableDelayedExpansion
SET xsummary=
SET xsize=
for /f "tokens=1,2 delims=C" %%i IN ('"dir /s /-c /a | find "Directory""') do (echo C%%j >> abcd.txt)
for /f "tokens=*" %%q IN (abcd.txt) do (
cd "%%q"
For /F "tokens=*" %%h IN ('"dir /s /-c /a | find "bytes" | find /v "free""') do Set xsummary=%%h
For /f "tokens=1,2 delims=)" %%a in ("!xsummary!") do set xsize=%%b
Set xsize=!xsize:bytes=!
Set xsize=!xsize: =!
echo.%%q >> size.txt
echo.!xsize! >> size.txt
)
ANSWER :
The problem is the cd
— you are creating a two-line size.txt
file in each directory. Try
for ... %%h in ('"dir /s /-c /a %%q | find "bytes" | find /v "free""') ...
and remove the cd
.
By the way, do you realize that you are getting cumulative totals? For example, if your directory structure looks like
a 100K ab 200K ac 400K
then your result will say
a 700000 ab 200000 ac 400000
That’s because the second dir
has the /s
option. That’s fine if that’s what you want; I just thought it might have been an oversight.
If you can manage it, avoid this mess by grabbing the Sysinternals du utility and running du -v <directoryname>
. Output will be kilobytes. The -v
option means output the size of all subdirectories.
With your current script, the problem seems to be appending text while in a loop. An easy workaround is to output to the command prompt window, like so:
echo %%q
echo !xsize!
and when you run the batch file, sent its output to a text file: test.bat > size.txt
A quick tip: the first bit can be simplified with:
for /f "tokens=*" %%q in ('dir /a:d /s /b') do (
cd "%%q"
...
echo !xsize!
)
Basically, it’s replacing the use of find
with the /a:d
option (list directories only) and /b
(use bare format, no heading or summary). This also gets rid of the temporary file.
Modified version to work on folders with exclamation marks in the name. Using Scott’s answer. See comments below.
Note that REM
means it’s a comment.
@echo off
REM clear the file
type NUL > size.txt
for /f "tokens=*" %%q IN ('dir /a:d /s /b') do (
echo %%q >> size.txt
REM Run :getsize, passing "%%q" including quotes as the first parameter
call :getsize "%%q" >> size.txt
)
REM go to the end of the file, i.e. exit
goto :eof
:getsize
REM %1 is the first parameter. It should already be quoted.
for /f "tokens=*" %%h IN ('"dir /s /-c /a %1 | find "bytes" | find /v "free""') do set xsummary=%%h
for /f "tokens=1,2 delims=)" %%a in ("%xsummary%") do set xsize=%%b
set xsize=%xsize:bytes=%
set xsize=%xsize: =%
echo %xsize%
goto :eof
An alternative is to use PowerShell:
Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | %{$_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0; $_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum; $_} | Sort-Object DirSize -Descending | Select-Object -First 10 FullName,DirSize | Format-Table -AutoSize | Out-String -Width 4096 | Out-File 'sizes.txt';
This is overly complex for the purpose of being as complete as possible – also, most of those commands have shorter aliases; this is just more readable. And if you separate certain sections out into functions/their own line, it becomes much more readable and possibly shorter.
Expanding it:
-
Get-ChildItem -Recurse
Recursively list all subdirectories and files
-
?{ $_.PSIsContainer }
Filter so only directories are kept (
?
is an alias forWhere-Object
).$_
refers to the object(s) piped to this – sinceGet-ChildItem
passes a list/array of objects, this processes them one at a time. -
%{$_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0; $_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum; $_}
Calculate size of each directory (
%
is an alias forForeach-Object
, so run what is between{ }
on each individual object. Each object is a directory, as per the previous parts.)Expanding it:
-
$_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0;
A semicolon (
;
) denotes the end of a line/statement, and does not pass the output on, as opposed to a pipe (|
). This particular bit has no output, and it’s just adding theDirSize
property to the directory object and initialising it to 0. This is done for easier filtering and neater output later. -
$_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum;
Set
DirSize
to theSum
property of the output of that command.$()
means execute what is inside the parentheses and substitute that in.Expanding it:
-
Get-ChildItem -Recurse $_.Fullname
Get all the contents of the current directory object (
$_
,Fullname
just means the full path).-Recurse
means count the contents of subdirectories as well. -
?{ -not $_.PSIsContainer }
Filter so only files are kept. Directories have a
Length
property of 0, so we need to use sum the files within – essentially what your batch script does, except we’re dealing with actual numbers, objects and properties instead of grabbing a ‘number’ as text usingfind
. -
Measure-Object -Sum -Property Length
Calculate the sum of all the
Length
properties passed to it. This returns an object containing the propertySum
, which is used above (.Sum
)
-
-
$_
Essentially
echo
ing the object to pass it on to the next section. It now has aDirSize
property. -
-
Sort-Object DirSize -Descending
Sort descending by
DirSize
-
Select-Object -First 10 FullName,DirSize
Only output the first 10, and only the name and size
-
Format-Table -AutoSize | Out-String -Width 4096 | Out-File 'sizes.txt';
Output to
sizes.txt
. TheFormat-Table
andOut-String
bits are to prevent truncating to console window width (normally 80 characters). 4096 should be more than enough characters for any Windows path and directory size, considering a Windows path is typically limited to 255 characters (IIRC). TheAutoSize
just means make it as short as possible without truncating, instead of padding with a whole lot of whitespace to fit 4096 characters.
And the command as a one liner to run in cmd.exe
(you can run the above version within PowerShell):
powershell -c "Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | %{$_ | Add-Member -MemberType NoteProperty -Name DirSize -Value 0; $_.DirSize = $(Get-ChildItem -Recurse $_.Fullname | ?{ -not $_.PSIsContainer } | Measure-Object -Sum -Property Length).Sum; $_} | Sort-Object DirSize -Descending | Select-Object -First 10 FullName,DirSize | Format-Table -AutoSize | Out-String -Width 4096 | Out-File 'sizes.txt';"