1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 | #!/bin/bash
#
# cleanup.sh โ read-only by default macOS disk-space cleaner.
#
# Modes:
# ./cleanup.sh same as --dry-run: print what WOULD be
# deleted and its size. Deletes nothing.
# ./cleanup.sh --dry-run explicit dry run.
# ./cleanup.sh --diagnose run Step 1 diagnostics (disk / memory
# pressure / swap). Deletes nothing.
# ./cleanup.sh --execute actually delete the safe-path items.
# ./cleanup.sh --help show this text.
#
# Safety model:
# - Nothing is deleted unless --execute is passed explicitly.
# - Every candidate path is checked against SAFE_PREFIXES before any rm.
# If a resolved path does not start with one of those prefixes, it is
# skipped and reported as skipped, never deleted.
# - Only cache / derived-data / trash style paths are in scope. Downloads,
# node_modules, Docker volumes, and app Containers are intentionally
# NOT touched by this script โ see README.md Step 2/3 for those, they
# need a human to look at what's actually in them first.
#
set -u
MODE="dry-run"
case "${1:-}" in
--execute) MODE="execute" ;;
--dry-run) MODE="dry-run" ;;
--diagnose) MODE="diagnose" ;;
--help|-h)
sed -n '2,25p' "$0"
exit 0
;;
"") MODE="dry-run" ;;
*)
echo "Unknown option: $1 (use --dry-run, --diagnose, --execute, or --help)"
exit 1
;;
esac
HOME_DIR="${HOME}"
# Hard allowlist of prefixes this script is permitted to delete under.
# A resolved candidate path must start with one of these or it is skipped.
SAFE_PREFIXES=(
"${HOME_DIR}/Library/Caches"
"${HOME_DIR}/Library/Developer/Xcode/DerivedData"
"${HOME_DIR}/.Trash"
)
# Candidate paths considered for deletion. Kept short and conservative on
# purpose โ everything riskier (Downloads, node_modules, Docker, app
# Containers) is left to human judgment in the README, not automated here.
CANDIDATES=(
"${HOME_DIR}/Library/Caches"
"${HOME_DIR}/Library/Developer/Xcode/DerivedData"
"${HOME_DIR}/.Trash"
)
is_safe_path() {
local path="$1"
local prefix
for prefix in "${SAFE_PREFIXES[@]}"; do
case "$path" in
"$prefix"|"$prefix"/*) return 0 ;;
esac
done
return 1
}
human_size() {
# $1 = path
du -sh "$1" 2>/dev/null | awk '{print $1}'
}
run_diagnose() {
echo "== Disk space (/ volume) =="
df -h / 2>/dev/null
echo
echo "== Memory pressure =="
memory_pressure 2>/dev/null || echo "memory_pressure not available"
echo
echo "== Swap usage =="
sysctl vm.swapusage 2>/dev/null
echo
echo "== Pageouts / Swapins / Swapouts (vm_stat) =="
vm_stat 2>/dev/null | grep -E "Pageouts|Swapins|Swapouts"
echo
echo "Rule of thumb: below ~15% free on / AND elevated memory pressure or"
echo "swap usage means a disk cleanup (this script) will likely help."
echo "If disk space looks fine, this script will not fix your slowdown โ"
echo "see README.md for other things to check (CPU, thermal, failing disk)."
}
run_sweep() {
local total_would_reclaim=0
echo "Mode: ${MODE}"
echo
for path in "${CANDIDATES[@]}"; do
if [ ! -e "$path" ]; then
echo "SKIP (not found): $path"
continue
fi
if ! is_safe_path "$path"; then
echo "SKIP (not allowlisted, refusing to touch): $path"
continue
fi
size=$(human_size "$path")
size="${size:-0}"
if [ "$MODE" = "execute" ]; then
# Remove contents, not the directory itself, so macOS/apps that
# expect the folder to exist keep working after a relaunch.
echo "DELETE contents of ($size): $path"
find "$path" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null
else
echo "WOULD DELETE contents of ($size): $path"
fi
done
echo
if [ "$MODE" = "execute" ]; then
echo "Done. Reboot to purge swap: sudo reboot"
else
echo "Dry run only โ nothing was deleted."
echo "Review the list above, then re-run with --execute to actually delete."
echo "Run --diagnose first if you haven't confirmed this is a disk problem."
fi
}
case "$MODE" in
diagnose) run_diagnose ;;
dry-run) run_sweep ;;
execute) run_sweep ;;
esac
|