Disk Usage tool icon

Disk Usage

C drive full? Find which folder is using the most disk space on Windows, Mac or Linux.

Platforms
Windows, Linux, macOS
Needs
Nothing to install. PowerShell is built into Windows; the bash version uses the standard du command found on Linux and macOS.
Files
Get-DiskUsage.ps1 (PowerShell) · disk-usage.sh (bash)
Licence
MIT — read, modify, redistribute
Verified
Ran on our test machine on 2026-10-17
Last reviewed
2026-10-17
Download Get-DiskUsage.ps1Download disk-usage.shRead the sourceSee the full workflow

The problem it solves

The disk is full, but which folder is responsible? Disk Usage walks a path, sums every file inside its top-level children, and prints them biggest-first with a share bar.

Example output

Here is what the script printed when we ran it on a test computer:

Real terminal output
Scanning: C:\Users\you\Documents\Pctools
(read-only: this takes a moment on large folders)

node_modules      135.1 MB   55.7%  ###################/
.opencode          55.8 MB   23.0%  ########/
vendor             48.4 MB   20.0%  #######/
.claude             1.9 MB    0.8%  #/
dist                410 KB    0.2%  #/
.git                296 KB    0.1%  #/
src                 129 KB    0.1%  #/
.astro              117 KB    0.0%  #/
...                 324 KB  in 28 more entries

Total measured: 242.5 MB in 36 entries

Usage

Download the file, then run it from a terminal. On Windows (PowerShell):

powershell -NoProfile -ExecutionPolicy Bypass -File .\Get-DiskUsage.ps1 -Path $HOME -Top 10

On Linux or macOS (bash):

bash disk-usage.sh ~ 10

Options

-Path <folder>
folder to scan (default: your home folder)
-Top <n>
how many entries to print (default 15)

What it does

Limitations

Know these before you rely on it:

Security notes — is this safe to run?

Compare the checksum of the file you downloaded against this value to be sure it was not tampered with en route:

FileSHA-256
Get-DiskUsage.ps1181e122f504497ba640fb56596408d6f2fc50856646da3b4894a008ad92788d3
disk-usage.shda43d015b24cfe5a818df84380c768a97c0e34a138e01b02cc1be4ef7a6c33d3

Verify with Get-FileHash .\Get-DiskUsage.ps1 -Algorithm SHA256 on Windows, sha256sum Get-DiskUsage.ps1 on Linux or macOS.

Alternatives

In context

This tool is one of the steps in the c drive full? free up disk space on windows 10/11 workflow — the guide explains what order to run things in and how to interpret the results.

Source

The complete PowerShell source, so you can audit it before running:

Get-DiskUsage.ps1
<#
.SYNOPSIS
  Get-DiskUsage - ranks the folders and files eating a disk, biggest first.

.DESCRIPTION
  READ-ONLY. Walks a path, totals the size of each top-level entry and prints
  them largest-first with a share bar. Never modifies, moves or deletes
  anything. Unreadable folders are skipped, not fatal.

.EXAMPLE
  powershell -ExecutionPolicy Bypass -File .\Get-DiskUsage.ps1 -Path $HOME -Top 10
#>
[CmdletBinding()]
param(
  [string]$Path = $HOME,
  [int]$Top = 15
)

if (-not (Test-Path -LiteralPath $Path)) {
  Write-Error "Path not found: $Path"
  exit 1
}

Write-Host "Scanning: $Path"
Write-Host '(read-only: this takes a moment on large folders)'
Write-Host ''

$entries = Get-ChildItem -LiteralPath $Path -Force -ErrorAction SilentlyContinue
$rows = @()
foreach ($e in $entries) {
  if ($e.LinkType) { continue }   # skip symlinks / junctions
  try {
    if ($e.PSIsContainer) {
      $sum = (Get-ChildItem -LiteralPath $e.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
              Measure-Object -Property Length -Sum).Sum
    } else {
      $sum = $e.Length
    }
    if ($null -eq $sum) { $sum = 0 }
    $rows += [PSCustomObject]@{ Name = $e.Name; Size = [double]$sum; IsDir = $e.PSIsContainer }
  } catch {
    # unreadable entry: skip it, keep going
  }
}

$rows = $rows | Sort-Object Size -Descending
$grand = ($rows | Measure-Object -Property Size -Sum).Sum
if ($grand -le 0) {
  Write-Host 'Nothing found to measure.'
  exit 0
}

$shown = $rows | Select-Object -First $Top
$rest  = ($rows | Select-Object -Skip $Top | Measure-Object -Property Size -Sum).Sum
$width = ($shown | ForEach-Object { $_.Name.Length } | Measure-Object -Maximum).Maximum
if ($width -lt 8) { $width = 8 }

foreach ($r in $shown) {
  $pct  = $r.Size / $grand * 100
  $bar  = '#' * [Math]::Max(1, [int]($pct / 3))
  $size = if ($r.Size -ge 1GB) { '{0,9:N2} GB' -f ($r.Size/1GB) }
          elseif ($r.Size -ge 1MB) { '{0,9:N1} MB' -f ($r.Size/1MB) }
          else { '{0,9:N0} KB' -f ($r.Size/1KB) }
  $suffix = if ($r.IsDir) { '/' } else { '' }
  Write-Host (('{0,-' + $width + '}  {1}  {2,5:N1}%  {3}{4}') -f $r.Name, $size, $pct, $bar, $suffix)
}
if ($rows.Count -gt $Top) {
  $restSize = if ($rest -ge 1GB) { '{0,9:N2} GB' -f ($rest/1GB) }
              elseif ($rest -ge 1MB) { '{0,9:N1} MB' -f ($rest/1MB) }
              else { '{0,9:N0} KB' -f ($rest/1KB) }
  Write-Host (('{0,-' + $width + '}  {1}  in {2} more entries') -f '...', $restSize, ($rows.Count - $Top))
}
Write-Host ''
$grandStr = if ($grand -ge 1GB) { '{0:N2} GB' -f ($grand/1GB) }
            elseif ($grand -ge 1MB) { '{0:N1} MB' -f ($grand/1MB) }
            else { '{0:N0} KB' -f ($grand/1KB) }
Write-Host ('Total measured: {0} in {1} entries' -f $grandStr, $rows.Count)

The bash version:

disk-usage.sh
#!/usr/bin/env bash
# disk-usage.sh - ranks the folders eating a disk, biggest first.
# READ-ONLY: walks and prints, changes nothing.
# Run: bash disk-usage.sh [path] [top]

set -u

PATH_TO_SCAN="${1:-$HOME}"
TOP="${2:-15}"

if [ ! -d "$PATH_TO_SCAN" ]; then
  echo "Path not found: $PATH_TO_SCAN" >&2
  exit 1
fi

echo "Scanning: $PATH_TO_SCAN"
echo "(read-only: this takes a moment on large folders)"
echo ""

# du -sk: kilobytes, summary per top-level entry; skip unreadable.
entries=$(du -sk "$PATH_TO_SCAN"/* "$PATH_TO_SCAN"/.[!.]* 2>/dev/null | sort -rn)
if [ -z "$entries" ]; then
  echo "Nothing found to measure."
  exit 0
fi

total=$(echo "$entries" | awk '{s+=$1} END {print s+0}')
shown=$(echo "$entries" | head -n "$TOP")
count=$(echo "$entries" | wc -l | tr -d ' ')

echo "$shown" | while IFS= read -r line; do
  kb=$(echo "$line" | awk '{print $1}')
  name=$(echo "$line" | cut -f2- | sed "s|^$PATH_TO_SCAN/||; s|^$PATH_TO_SCAN\\\\||")
  pct=$(awk -v k="$kb" -v t="$total" 'BEGIN {if (t>0) printf "%.1f", k*100/t; else print "0.0"}')
  bars=$(awk -v p="$pct" 'BEGIN {n=int(p/3); if (n<1) n=1; printf "%*s", n, ""}' | tr ' ' '#')
  size=$(awk -v k="$kb" 'BEGIN {if (k>=1048576) printf "%8.2f GB", k/1048576; else if (k>=1024) printf "%8.1f MB", k/1024; else printf "%8d KB", k}')
  short=$(basename "$name")
  printf '%-28s %s %6s%%  %s/\n' "$short" "$size" "$pct" "$bars"
done

if [ "$count" -gt "$TOP" ]; then
  rest=$(echo "$entries" | tail -n +"$((TOP+1))" | awk '{s+=$1} END {print s+0}')
  restsize=$(awk -v k="$rest" 'BEGIN {if (k>=1048576) printf "%8.2f GB", k/1048576; else if (k>=1024) printf "%8.1f MB", k/1024; else printf "%8d KB", k}')
  printf '%-28s %s  in %d more entries\n' "..." "$restsize" "$((count - TOP))"
fi

echo ""
totalsize=$(awk -v k="$total" 'BEGIN {if (k>=1048576) printf "%.2f GB", k/1048576; else printf "%.1f MB", k/1024}')
echo "Total measured: $totalsize in $count entries"

← All tools