列出给定结构中所有文件夹和子文件夹以及它们的文件大小

5

我正在尝试列出磁盘的文件夹结构和每个文件夹的大小。

我已经得到了文件夹结构,现在只需要输出每个文件夹的大小。

根据https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/dir,没有用于显示文件大小的标志 - 只有隐藏它的标志。我猜我走错了路线,但是任何帮助都将不胜感激。

这是我目前的代码:

dir /s /b /o:n /a:d > "C:\folderlist.txt"

期望输出:

C:\WINDOWS\system32\downlevel 400mb
C:\WINDOWS\system32\drivers 100mb
C:\WINDOWS\system32\DriverState 4kb
C:\WINDOWS\system32\DriverStore 1kb
C:\WINDOWS\system32\DRVSTORE 1gb

无论文件大小的缩写是什么(例如mb,kb,gb,tb),都不重要。只要以某种可量化的方式显示文件夹大小即可。

欢迎使用Powershell替代方案。

3个回答

12

一个建立在 montonero有用的答案 基础上的 PowerShell 解决方案,改进了以下方面:

  • 递归深度的控制
  • 性能的提升
  • 更好的与其他 cmdlet 集成以实现可组合功能

下面是基于定义如下的函数 Get-DirectorySize 的示例调用:

# Get the size of the current directory (only).
Get-DirectorySize

# As requested by the OP:
# Recursively report the sizes of all subdirectories in the current directory.
Get-DirectorySize -Recurse -ExcludeSelf

# Get the size of all child directories and sort them by size, from largest
# to smallest, showing only the 5 largest ones:
Get-DirectorySize -Depth 1 -ExcludeSelf |
  Sort-Object Size -Descending |
    Select-Object -First 5

last命令的示例输出:

FullName                           FriendlySize       Size
--------                           ------------       ----
C:\Users\jdoe\AppData                3.27gb     3514782772
C:\Users\jdoe\Desktop              801.40mb      840326199
C:\Users\jdoe\.nuget               778.97mb      816814396
C:\Users\jdoe\.vscode              449.12mb      470931418
C:\Users\jdoe\Projects             104.07mb      109127742

请注意,属性.FriendlySize包含友好的自动缩放大小的字符串表示,而.Size是一个数字([long]),其中包含实际的字节计数,这是有助于进一步的程序处理。
注:在此仅为实现方便,将属性添加到输出对象中仅用于促进友好的显示。正确的Powershell方式是根据输出对象类型定义格式化指令 - 请参见文档警告(也适用于链接的答案):
  • 只报告逻辑大小,即文件数据需要的实际字节数,这与磁盘上的大小不同,后者通常由于文件占用固定大小块而更大;相反,压缩和稀疏文件占用更少的磁盘空间。

  • 递归的实现(使用-Recurse和/或-Depth)是低效的,因为遇到的每个目录的子树都会被完全扫描;这在一定程度上得到了文件系统缓存的帮助。


Get-DirectorySize源代码

注:需要Windows PowerShell v3+;也与PowerShellCore兼容。
function Get-DirectorySize
{

  param(
    [Parameter(ValueFromPipeline)] [Alias('PSPath')]
    [string] $LiteralPath = '.',
    [switch] $Recurse,
    [switch] $ExcludeSelf,
    [int] $Depth = -1,
    [int] $__ThisDepth = 0 # internal use only
  )

  process {

    # Resolve to a full filesystem path, if necessary
    $fullName = if ($__ThisDepth) { $LiteralPath } else { Convert-Path -ErrorAction Stop -LiteralPath $LiteralPath }

    if ($ExcludeSelf) { # Exclude the input dir. itself; implies -Recurse

      $Recurse = $True
      $ExcludeSelf = $False

    } else { # Process this dir.

      # Calculate this dir's total logical size.
      # Note: [System.IO.DirectoryInfo].EnumerateFiles() would be faster, 
      # but cannot handle inaccessible directories.
      $size = [Linq.Enumerable]::Sum(
        [long[]] (Get-ChildItem -Force -Recurse -File -LiteralPath $fullName).ForEach('Length')
      )

      # Create a friendly representation of the size.
      $decimalPlaces = 2
      $padWidth = 8
      $scaledSize = switch ([double] $size) {
        {$_ -ge 1tb } { $_ / 1tb; $suffix='tb'; break }
        {$_ -ge 1gb } { $_ / 1gb; $suffix='gb'; break }
        {$_ -ge 1mb } { $_ / 1mb; $suffix='mb'; break }
        {$_ -ge 1kb } { $_ / 1kb; $suffix='kb'; break }
        default       { $_; $suffix='b'; $decimalPlaces = 0; break }
      }
  
      # Construct and output an object representing the dir. at hand.
      [pscustomobject] @{
        FullName = $fullName
        FriendlySize = ("{0:N${decimalPlaces}}${suffix}" -f $scaledSize).PadLeft($padWidth, ' ')
        Size = $size
      }

    }

    # Recurse, if requested.
    if ($Recurse -or $Depth -ge 1) {
      if ($Depth -lt 0 -or (++$__ThisDepth) -le $Depth) {
        # Note: This top-down recursion is inefficient, because any given directory's
        #       subtree is processed in full.
        Get-ChildItem -Force -Directory -LiteralPath $fullName |
          ForEach-Object { Get-DirectorySize -LiteralPath $_.FullName -Recurse -Depth $Depth -__ThisDepth $__ThisDepth }
      }
    }

  }

}

这里是与注释相关的函数帮助;如果你将该函数添加到你的$PROFILE中,直接将帮助放置在函数上方或者函数体内部,以便支持-?和自动集成Get-Help

<#
.SYNOPSIS
Gets the logical size of directories in bytes.

.DESCRIPTION
Given a literal directory path, output that directory's logical size, i.e.,
the sum of all files contained in the directory, including hidden ones.

NOTE: 
* The logical size is distinct from the size on disk, given that files
  are stored in fixed-size blocks. Furthermore, files can be compressed
  or sparse.
  Thus, the size of regular files on disk is typically greater than
  their logical size; conversely, compressed and sparse files require less
  disk space.
  Finally, the list of child items maintained by the filesystem for each 
  directory requires disk space too.

* Wildcard expressions aren't directly supported, but you may pipe in
  Output from Get-ChildItem / Get-Item; if files rather than directotries 
  happen to be among the input objects, their size is reported as-is.

CAVEATS:
 * Can take a long time to run with large directory trees, especially with
   -Recurse.
* Recursion is implemented inefficently.

.PARAMETER LiteralPath
The literal path of a directory. May be provided via the pipeline.

.PARAMETER Recurse
Calculates the logical size not only of the input directory itself, but of
all subdirectories in its subtree too.
To limit the recursion depth, use -Depth.

.PARAMETER Depth
Limits the recursion depth to the specified number of levels. Implies -Recurse.
Note that 0 means no recursion. Use just -Recurse in order not to limit the
recursion.

.PARAMETER ExcludeSelf
Excludes the target directory itself from the size calculation.
Implies -Recurse. Since -Depth implies -Recurse, you could use -ExcludeSelf
-Depth 1 to report only the sizes of the immediate subdirectories.

.OUTPUTS
[pscustomobject] instances with properties FullName, Size, and FriendlySize.

.EXAMPLE
Get-DirectorySize

Gets the logical size of the current directory.

.EXAMPLE
Get-DirectorySize -Recurse

Gets the logical size of the current directory and all its subdirectories.

.EXAMPLE
Get-DirectorySize /path/to -ExcludeSelf -Depth 1 | Sort-Object Size

Gets the logical size of all child directories in /path/to without including
/path/to itself, and sorts the result by size (largest last).
#>

5

使用dir命令无法获取文件夹大小。您需要递归地为每个文件夹单独计算大小。有许多有效的Powershell示例可供使用。以下是其中一个不错的示例:https://md3v.com/getting-a-folder-tree-size-with-powershell

function tree($startFolder)
{
   $colItems = Get-ChildItem $startFolder | Where-Object {$_.PSIsContainer -eq $true} | Sort-Object
   foreach ($i in $colItems)
   {
       $subFolderItems = Get-ChildItem $i.FullName -recurse -force | Where-Object {$_.PSIsContainer -eq $false} | Measure-Object -property Length -sum | Select-Object Sum
       $i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
       tree $i.FullName
   }
}

1
干得好!为了一致性,我建议在 Get-ChildItem $startFolder 中也添加 -Force(以包括隐藏目录)。PSv3引入了 -Directory-File 开关到 Get-ChildItem,这使得 Where-Object {$_.PSIsContainer -eq $true}Where-Object {$_.PSIsContainer -eq $false} 不再必要。虽然它没有被文档化,但是 Get-ChildItem 在输出之前总是按名称排序,因此 Sort-Object 调用是不必要的。 - mklement0
2
感谢您的补充。我刚刚对此示例进行了轻微修改,来源于https://md3v.com/getting-a-folder-tree-size-with-powershell,当然它远非完美,只是一个快速而简单的解决方案。尽管它比您的代码要短得多 :) - montonero

0

这里有一段批处理脚本,它可以列出给定根目录下的所有目录及其大小。将根目录作为第一个命令行参数提供;如果省略,则使用当前目录。代码如下:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_ROOT=%~1" & if not defined _ROOT set "_ROOT=."

rem // Change into the given root directory:
pushd "%_ROOT%" && (
    rem // Walk through all immediate sub-directories:
    for /F "delims= eol=|" %%D in ('dir /B /A:D-H "*"') do (
        rem // Initialise variable holding size of sub-directory:
        set "SIZE=0"
        rem // Process sub-directory in a sub-soutine:
        set "ITEM=%%~D" & call :PROCESS SIZE "%%ITEM%%"
        rem // Display size of sub-directory:
        call set "SIZE=              %%SIZE%%"
        set "ITEM=%%~fD" & call echo %%SIZE:~-14%%  "%%ITEM%%"
    )
    popd
)

endlocal
exit /B


:PROCESS
    rem /* Change into the given directory; use short names to avoid trouble with
    rem    extremely long and/or deep paths (remember the limit is 260 characters): */
    pushd "%~s2" && (
        rem /* Walk through all files in the directory; instead of a normal `for` loop,
        rem    `dir` together with a `for /F` loop is used, because `for` would ignore
        rem    hidden files; with `dir` you can choose the attributes and therefore
        rem    ensure that all files are returned: */
        for /F "delims= eol=|" %%F in ('2^> nul dir /B /A:-D "*"') do (
            rem // Sum up the file sizes in a sub-routine:
            call :SUM SIZE "%%SIZE%%" "%%~zF"
        )
        rem /* Walk through all sub-directories; instead of a normal `for` loop, `dir`
        rem    together with a `for /F` loop is used, because `for` would ignore hidden
        rem    sub-directories; with `dir` you can choose the attributes and therefore
        rem    ensure that all sub-directories are returned: */
        for /F "delims= eol=|" %%D in ('2^> nul dir /B /A:D "*"') do (
            rem // Call this sub-routine recursively to process sub-directories:
            set "ITEM=%%~D" & call :PROCESS SIZE "%%ITEM%%"
        )
        popd
    )
    rem // Return resulting directory size:
    set "%~1=%SIZE%"
    exit /B

:SUM
    rem // Split provided numbers into ones and billions:
    set "ENA1=%~2" & set "ENA2=%~3"
    set "GIG1=%ENA1:~,-9%" & set "ENA1=%ENA1:~-9%"
    set "GIG2=%ENA2:~,-9%" & set "ENA2=%ENA2:~-9%"
    rem /* Sum up the ones, disregard leading zeros, which would let numbers be
    rem    interpreted as octal ones: */
    for /F "tokens=* delims=0" %%M in ("0%ENA1%") do (
        for /F "tokens=* delims=0" %%N in ("0%ENA2%") do (
            set /A "ENA=%%M+%%N+0"
        )
    )
    rem // Sum up the billions, regard the carry from the ones:
    set "GIG=%ENA:~,-9%" & set /A "GIG1+=0, GIG2+=0, GIG+=GIG1+GIG2"
    rem // Join resulting billions and ones to the finally resulting number:
    set "ENA=000000000%ENA%"
    for /F "tokens=* delims=0" %%K in ("%GIG%%ENA:~-9%") do set "%~1=%%K"
    exit /B

示例调用(假设脚本名为list_folders.bat):

list_folders.bat "D:\Root"

示例输出:

        442368  "D:\Root\Data"
     101685022  "D:\Root\More"
       5441536  "D:\Root\Test"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接