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
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:
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 10On Linux or macOS (bash):
bash disk-usage.sh ~ 10Options
-Path <folder>- folder to scan (default: your home folder)
-Top <n>- how many entries to print (default 15)
What it does
- Recursive size sum of every top-level folder or file under the given path
- Sorted largest-first with size, percentage and a share bar
- Skips symbolic links and junctions so it never loops or double-counts
- Skips unreadable directories instead of crashing
Limitations
Know these before you rely on it:
- It ranks only top-level children; to go deeper, re-run it inside the biggest folder
- It sums logical file sizes: NTFS-compressed or sparse files will look larger on disk than they report
- Directories it cannot read count as zero, so a locked folder can look deceptively small
- On very large or network drives a full walk can take minutes; there is no progress bar
Security notes — is this safe to run?
- Read-only: it walks, sums and prints - it never modifies the filesystem
- You choose the path; it reads nothing outside it
- Running it on a folder shared by other users reveals their file sizes to you - expected on a machine you administer, worth remembering on shared systems
Compare the checksum of the file you downloaded against this value to be sure it was not tampered with en route:
| File | SHA-256 |
|---|---|
Get-DiskUsage.ps1 | 181e122f504497ba640fb56596408d6f2fc50856646da3b4894a008ad92788d3 |
disk-usage.sh | da43d015b24cfe5a818df84380c768a97c0e34a138e01b02cc1be4ef7a6c33d3 |
Verify with Get-FileHash .\Get-DiskUsage.ps1 -Algorithm SHA256 on Windows, sha256sum Get-DiskUsage.ps1 on Linux or macOS.
Alternatives
- WizTree — Windows, reads the NTFS master file table - near-instant
- WinDirStat — Windows GUI classic with treemap view
- ncdu — terminal-first, interactive, the Linux standard
- Baobab (Disk Usage Analyzer) — ships with GNOME on Linux
Other tools on this site
- System Report — Check your PC specs: CPU, RAM, disk space and network — one free script, no install.
- Large Files — Find the largest files on your PC — forgotten downloads, old backups, duplicate videos.
- Open Ports — Check open ports on your PC — see what is listening and spot anything unexpected.
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:
<#
.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:
#!/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"