myspace-cli 1.2.0__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {myspace_cli-1.2.0.dist-info → myspace_cli-1.4.0.dist-info}/METADATA +107 -37
- myspace_cli-1.4.0.dist-info/RECORD +6 -0
- space_cli.py +428 -71
- myspace_cli-1.2.0.dist-info/RECORD +0 -6
- {myspace_cli-1.2.0.dist-info → myspace_cli-1.4.0.dist-info}/WHEEL +0 -0
- {myspace_cli-1.2.0.dist-info → myspace_cli-1.4.0.dist-info}/entry_points.txt +0 -0
- {myspace_cli-1.2.0.dist-info → myspace_cli-1.4.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: myspace-cli
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: A macOS disk space analysis CLI: health, index, app usage, big files.
|
5
5
|
Author-email: Your Name <you@example.com>
|
6
6
|
License: MIT
|
@@ -17,31 +17,54 @@ Description-Content-Type: text/markdown
|
|
17
17
|
Provides-Extra: mcp
|
18
18
|
Requires-Dist: mcp<2,>=1; extra == "mcp"
|
19
19
|
|
20
|
-
# space-cli -
|
20
|
+
# space-cli - macOS 磁盘空间优化工具
|
21
21
|
|
22
22
|
很多人的Mac电脑都会出现磁盘空间不够用,付费软件太贵或者难以使用。
|
23
23
|
|
24
|
-
space-cli是一个开源的
|
24
|
+
space-cli是一个开源的macOS命令行小工具,用于分析磁盘空间健康度并找出占用空间大的目录,可选择针对单个应用进行一键清理。
|
25
25
|
|
26
|
-
|
26
|
+
本软件采用**最严安全原则**,所有分析操作采用只读模式,未经允许不会尝试改写和破坏用户电脑的任何数据,也不会上传任何数据到外网,严格保护用户的隐私。
|
27
27
|
|
28
28
|
## 功能特性
|
29
29
|
|
30
30
|
- 🔍 **磁盘健康度检测** - 评估磁盘空间使用情况,提供健康状态建议
|
31
|
-
- 📊
|
32
|
-
- 💻
|
31
|
+
- 📊 **交互式目录分析** - 递归分析目录大小,支持选择序号进行深度下探分析
|
32
|
+
- 💻 **详细系统信息** - 显示CPU、内存、GPU、硬盘等完整硬件信息
|
33
33
|
- 📄 **报告导出** - 将分析结果导出为JSON格式报告
|
34
|
-
- ⚡
|
34
|
+
- ⚡ **高性能优化** - 优先使用 `du -sk` 命令,失败时回退到 `os.scandir` 高效遍历
|
35
35
|
- 🎯 **灵活配置** - 支持自定义分析路径和显示数量
|
36
|
-
- 🗂️
|
36
|
+
- 🗂️ **智能索引缓存** - 目录大小结果本地索引缓存(`~/.spacecli/index.json`),支持TTL与重建提示
|
37
37
|
- 🧩 **应用分析** - 汇总 `Applications`、`Library`、`Caches`、`Logs` 等路径估算应用占用,给出卸载建议
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
- 🗑️ **一键删除应用** - 在应用分析列表中输入序号即可一键删除所选应用及其缓存(含二次确认)
|
39
|
+
- 🏠 **用户目录深度分析** - 针对 `~/Library`、`~/Downloads`、`~/Documents` 分别下探并展示Top N目录
|
40
|
+
- 🗄️ **大文件分析** - 扫描并列出指定路径下最大的文件,支持数量和最小体积阈值
|
41
|
+
- 🔄 **返回上一级** - 在下探分析中支持选择0返回上一级目录
|
42
|
+
- ⏱️ **支持MCP调用** - 支持你自己的AI Agent无缝调用磁盘空间信息
|
41
43
|
|
42
44
|
## 安装
|
43
45
|
|
44
|
-
### 方法1
|
46
|
+
### 方法1:通过 pip 安装(推荐)
|
47
|
+
|
48
|
+
```bash
|
49
|
+
python3 -m pip install --upgrade myspace-cli
|
50
|
+
|
51
|
+
# 支持Pip安装
|
52
|
+
pip install myspace-cli
|
53
|
+
|
54
|
+
# 安装完成后直接使用
|
55
|
+
# 请注意命令行的启动文件名(space-cli)和pip包的名字(myspace-cli)不一样
|
56
|
+
# 建议直接运行,可以看到使用菜单
|
57
|
+
space-cli
|
58
|
+
|
59
|
+
# 或以模块方式
|
60
|
+
python3 -m space_cli
|
61
|
+
|
62
|
+
# 如果要使用高级的功能,请使用更复杂的命令行参数,可以运行help
|
63
|
+
space-cli --help
|
64
|
+
|
65
|
+
```
|
66
|
+
|
67
|
+
### 方法2:直接使用
|
45
68
|
|
46
69
|
```bash
|
47
70
|
# 克隆或下载项目
|
@@ -55,7 +78,7 @@ chmod +x space_cli.py
|
|
55
78
|
python3 space_cli.py
|
56
79
|
```
|
57
80
|
|
58
|
-
### 方法
|
81
|
+
### 方法3:创建全局命令
|
59
82
|
|
60
83
|
```bash
|
61
84
|
# 复制到系统路径
|
@@ -66,24 +89,14 @@ sudo chmod +x /usr/local/bin/space-cli
|
|
66
89
|
space-cli
|
67
90
|
```
|
68
91
|
|
69
|
-
|
70
|
-
|
71
|
-
```bash
|
72
|
-
python3 -m pip install --upgrade spacecli
|
73
|
-
|
74
|
-
# 直接使用命令
|
75
|
-
space-cli --help
|
76
|
-
|
77
|
-
# 或者作为模块调用
|
78
|
-
python3 -m space_cli --help
|
79
|
-
```
|
92
|
+
注:若你更倾向于使用 PyPI 包名 `spacecli`,也可执行 `python3 -m pip install --upgrade spacecli`,命令入口同为 `space-cli`。
|
80
93
|
|
81
94
|
## 使用方法
|
82
95
|
|
83
96
|
### 基本用法
|
84
97
|
|
85
98
|
```bash
|
86
|
-
#
|
99
|
+
# 分析根目录(默认)- 支持交互式下探分析
|
87
100
|
python3 space_cli.py
|
88
101
|
|
89
102
|
# 分析指定路径
|
@@ -94,6 +107,9 @@ python3 space_cli.py -n 10
|
|
94
107
|
|
95
108
|
# 快捷分析当前用户目录(含用户目录深度分析)
|
96
109
|
python3 space_cli.py --home
|
110
|
+
|
111
|
+
# 交互式目录空间分析(支持选择序号下探,选择0返回上一级)
|
112
|
+
python3 space_cli.py --directories-only
|
97
113
|
```
|
98
114
|
|
99
115
|
### 高级用法
|
@@ -126,6 +142,8 @@ python3 space_cli.py --no-prompt
|
|
126
142
|
# 分析应用目录占用并给出卸载建议(按应用归并)
|
127
143
|
python3 space_cli.py --apps -n 20
|
128
144
|
|
145
|
+
# 在应用分析输出后,按提示输入序号一键删除应用(会二次确认)
|
146
|
+
# 例如:输入 3 即删除列表中的第3个应用及其相关缓存
|
129
147
|
# 大文件分析(显示前20个,阈值2G)
|
130
148
|
python3 space_cli.py --big-files --big-files-top 20 --big-files-min 2G
|
131
149
|
|
@@ -174,21 +192,30 @@ python3 space_cli.py --big-files --export report.json
|
|
174
192
|
建议: 磁盘空间不足,建议清理一些文件
|
175
193
|
```
|
176
194
|
|
177
|
-
###
|
195
|
+
### 交互式目录分析
|
178
196
|
```
|
179
197
|
============================================================
|
180
198
|
📊 占用空间最大的目录
|
181
199
|
============================================================
|
182
200
|
显示前 20 个最大的目录:
|
183
201
|
|
184
|
-
1. /Applications
|
185
|
-
大小:
|
202
|
+
1. /Applications -- 大小: 15.2 GB (3.04%)
|
203
|
+
2. /Users/username/Library -- 大小: 8.5 GB (1.70%)
|
204
|
+
3. /System -- 大小: 6.8 GB (1.36%)
|
186
205
|
|
187
|
-
|
188
|
-
|
206
|
+
============================================================
|
207
|
+
🔍 下探分析选项
|
208
|
+
============================================================
|
209
|
+
选择序号进行深度分析,选择0返回上一级,直接回车退出:
|
210
|
+
请输入选择 [回车=退出]: 1
|
189
211
|
|
190
|
-
|
191
|
-
|
212
|
+
🔍 正在分析: /Applications (15.2 GB)
|
213
|
+
============================================================
|
214
|
+
📊 占用空间最大的目录
|
215
|
+
============================================================
|
216
|
+
1. /Applications/Xcode.app -- 大小: 8.2 GB (1.64%)
|
217
|
+
2. /Applications/Docker.app -- 大小: 3.1 GB (0.62%)
|
218
|
+
3. /Applications/Visual Studio Code.app -- 大小: 1.8 GB (0.36%)
|
192
219
|
```
|
193
220
|
|
194
221
|
### 大文件分析
|
@@ -200,6 +227,29 @@ python3 space_cli.py --big-files --export report.json
|
|
200
227
|
2. /Users/username/Movies/clip.mov -- 大小: 3.1 GB (0.62%)
|
201
228
|
```
|
202
229
|
|
230
|
+
### 应用分析与一键删除
|
231
|
+
```
|
232
|
+
============================================================
|
233
|
+
🧩 应用目录空间分析与卸载建议
|
234
|
+
============================================================
|
235
|
+
1. Docker Desktop -- 占用: 9.1 GB (1.80%) — 建议卸载或清理缓存
|
236
|
+
2. Xcode -- 占用: 6.2 GB (1.23%) — 建议卸载或清理缓存
|
237
|
+
3. WeChat -- 占用: 2.4 GB (0.47%) — 可保留,定期清理缓存
|
238
|
+
|
239
|
+
是否要一键删除某个应用?输入序号或回车跳过: 1
|
240
|
+
确认删除应用及相关缓存: Docker Desktop (约 9.1 GB)?[y/N]: y
|
241
|
+
将尝试删除以下路径:
|
242
|
+
- /Applications/Docker.app
|
243
|
+
- ~/Library/Application Support/Docker
|
244
|
+
- ~/Library/Caches/com.docker.docker
|
245
|
+
...(略)
|
246
|
+
✅ 删除完成,预计释放空间: 8.7 GB
|
247
|
+
```
|
248
|
+
|
249
|
+
说明:
|
250
|
+
- 删除动作包含二次确认,并会列出将删除的路径清单。
|
251
|
+
- 系统级目录可能因权限/SIP 受保护而无法完全删除,此时工具会尽量清理可删部分并给出失败项与原因。
|
252
|
+
|
203
253
|
|
204
254
|
## MCP Server(可选)
|
205
255
|
|
@@ -226,11 +276,13 @@ python3 mcp_server.py
|
|
226
276
|
|
227
277
|
## 性能优化
|
228
278
|
|
229
|
-
-
|
230
|
-
-
|
231
|
-
-
|
232
|
-
-
|
233
|
-
|
279
|
+
- **优先使用 `du -sk` 命令**:在 macOS 上使用原生 `du` 命令快速获取目录大小
|
280
|
+
- **智能回退机制**:当 `du` 命令失败时,自动回退到基于 `os.scandir` 的高效遍历
|
281
|
+
- **跳过系统目录**:自动忽略 `/System`、`/Volumes`、`/private` 等系统目录
|
282
|
+
- **跳过无法访问的文件**:自动处理权限错误和符号链接
|
283
|
+
- **支持中断操作**:使用 Ctrl+C 随时中断分析
|
284
|
+
- **内存优化遍历**:使用栈式迭代替代递归,避免深度目录的栈溢出
|
285
|
+
- **单行滚动进度**:避免输出刷屏,使用 ANSI 清行(\r\033[K)避免长行残留
|
234
286
|
|
235
287
|
## 故障排除
|
236
288
|
|
@@ -244,6 +296,11 @@ sudo python3 space_cli.py
|
|
244
296
|
python3 space_cli.py -p /Users/$(whoami)
|
245
297
|
```
|
246
298
|
|
299
|
+
此外,针对“Operation not permitted”等提示:
|
300
|
+
- 退出相关应用后再试(例如删除 Docker 前先退出 Docker Desktop)。
|
301
|
+
- 在“系统设置 → 隐私与安全性”中为终端授予“完全磁盘访问权限”。
|
302
|
+
- 遇到容器元数据或受 SIP 保护的系统级文件(如 `~/Library/Containers/com.docker.docker/... .plist`),可能无法删除,建议仅清理用户级缓存目录。
|
303
|
+
|
247
304
|
### 性能问题
|
248
305
|
对于大型文件系统,分析可能需要较长时间:
|
249
306
|
- 使用 `--directories-only` 跳过健康检查
|
@@ -282,3 +339,16 @@ MIT License
|
|
282
339
|
- 新增大文件分析 `--big-files`/`--big-files-top`/`--big-files-min`
|
283
340
|
- 导出报告在启用大文件分析时包含 `largest_files`
|
284
341
|
- 单行滚动进度显示
|
342
|
+
|
343
|
+
### v1.2.0
|
344
|
+
- 应用分析支持"按序号一键删除应用",并显示将删除的路径清单与预计释放空间
|
345
|
+
- 删除过程增加权限修复与降级清理策略(chflags nouchg / chmod 0777 / 逐项清理)
|
346
|
+
- 针对 "Operation not permitted" 增加友好提示(SIP、完全磁盘访问、退出相关应用)
|
347
|
+
- 单行覆盖输出加入 ANSI 清行,避免长行残留
|
348
|
+
|
349
|
+
### v1.3.0
|
350
|
+
- **性能大幅优化**:优先使用 `du -sk` 命令获取目录大小,失败时回退到 `os.scandir` 高效遍历
|
351
|
+
- **交互式下探分析**:支持选择序号进行深度目录分析,选择0返回上一级
|
352
|
+
- **增强系统信息**:显示 CPU、内存、GPU、硬盘等完整硬件信息
|
353
|
+
- **智能目录过滤**:自动忽略系统目录(`/System`、`/Volumes`、`/private`)
|
354
|
+
- **优化用户体验**:改进菜单选项,支持交互式目录空间分析
|
@@ -0,0 +1,6 @@
|
|
1
|
+
space_cli.py,sha256=qEEhU_3nuiG--fWGbUDdQytrL9mL1CZTKGTMFpCyBNk,48380
|
2
|
+
myspace_cli-1.4.0.dist-info/METADATA,sha256=DDp6iHPcaGr_nCmJg5GHqeVhfqY-2fpzuJQ-4izUlVg,12747
|
3
|
+
myspace_cli-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4
|
+
myspace_cli-1.4.0.dist-info/entry_points.txt,sha256=sIVEmPf6W8aNa7KiqOwAI6JrsgHlQciO5xH2G29tKPQ,45
|
5
|
+
myspace_cli-1.4.0.dist-info/top_level.txt,sha256=lnUfbQX9h-9ZjecMVCOWucS2kx69FwTndlrb48S20Sc,10
|
6
|
+
myspace_cli-1.4.0.dist-info/RECORD,,
|
space_cli.py
CHANGED
@@ -154,20 +154,48 @@ class SpaceAnalyzer:
|
|
154
154
|
return f"{bytes_value:.1f} PB"
|
155
155
|
|
156
156
|
def get_directory_size(self, path: str) -> int:
|
157
|
-
"""
|
158
|
-
|
157
|
+
"""高性能计算目录大小。
|
158
|
+
|
159
|
+
优先使用 macOS 的 du -sk(以 KiB 为单位,速度快,原生命令可处理边界情况),
|
160
|
+
若 du 调用失败则回退到基于 os.scandir 的非递归遍历实现(避免 os.walk 的函数调用开销)。
|
161
|
+
"""
|
162
|
+
# 优先尝试 du -sk(BSD du 在 macOS 可用)。
|
159
163
|
try:
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# 跳过无法访问的目录
|
164
|
+
# du 输出形如: "<kib>\t<path>\n"
|
165
|
+
result = subprocess.run([
|
166
|
+
'du', '-sk', path
|
167
|
+
], capture_output=True, text=True, check=True)
|
168
|
+
out = result.stdout.strip().split('\t', 1)[0].strip()
|
169
|
+
kib = int(out)
|
170
|
+
return kib * 1024
|
171
|
+
except Exception:
|
172
|
+
# du 不可用或失败时回退到 Python 实现
|
170
173
|
pass
|
174
|
+
|
175
|
+
total_size = 0
|
176
|
+
# 基于栈的迭代遍历,避免递归栈与 os.walk 的额外开销
|
177
|
+
stack = [path]
|
178
|
+
while stack:
|
179
|
+
current = stack.pop()
|
180
|
+
try:
|
181
|
+
with os.scandir(current) as it:
|
182
|
+
for entry in it:
|
183
|
+
# 跳过符号链接,避免循环与跨文件系统问题
|
184
|
+
try:
|
185
|
+
if entry.is_symlink():
|
186
|
+
continue
|
187
|
+
if entry.is_file(follow_symlinks=False):
|
188
|
+
try:
|
189
|
+
total_size += entry.stat(follow_symlinks=False).st_size
|
190
|
+
except (OSError, FileNotFoundError, PermissionError):
|
191
|
+
continue
|
192
|
+
elif entry.is_dir(follow_symlinks=False):
|
193
|
+
stack.append(entry.path)
|
194
|
+
except (OSError, FileNotFoundError, PermissionError):
|
195
|
+
continue
|
196
|
+
except (OSError, FileNotFoundError, PermissionError):
|
197
|
+
# 无法进入该目录则跳过
|
198
|
+
continue
|
171
199
|
return total_size
|
172
200
|
|
173
201
|
def analyze_largest_files(self, root_path: str = "/", top_n: int = 50,
|
@@ -182,7 +210,7 @@ class SpaceAnalyzer:
|
|
182
210
|
dirpath_display = dirpath[-80:] # 截取最后50个字符
|
183
211
|
if dirpath_display == "":
|
184
212
|
dirpath_display = dirpath
|
185
|
-
sys.stdout.write(f"\r-> 正在读取: \033[36m{dirpath_display}\033[0m")
|
213
|
+
sys.stdout.write(f"\r\033[K-> 正在读取: \033[36m{dirpath_display}\033[0m")
|
186
214
|
sys.stdout.flush()
|
187
215
|
for filename in filenames:
|
188
216
|
filepath = os.path.join(dirpath, filename)
|
@@ -203,7 +231,7 @@ class SpaceAnalyzer:
|
|
203
231
|
if dirpath_display == "":
|
204
232
|
dirpath_display = dirpath
|
205
233
|
# 间隔性进度输出(单行覆盖)
|
206
|
-
sys.stdout.write(f"\r-> 正在读取: \033[36m{dirpath_display}\033[0m 已扫描文件数: \033[32m{scanned}\033[0m")
|
234
|
+
sys.stdout.write(f"\r\033[K-> 正在读取: \033[36m{dirpath_display}\033[0m 已扫描文件数: \033[32m{scanned}\033[0m")
|
207
235
|
sys.stdout.flush()
|
208
236
|
except KeyboardInterrupt:
|
209
237
|
print("\n用户中断扫描,返回当前结果...")
|
@@ -241,6 +269,14 @@ class SpaceAnalyzer:
|
|
241
269
|
return [(e["path"], int(e["size"])) for e in cached["entries"]][:top_n]
|
242
270
|
|
243
271
|
print("正在分析目录大小,这可能需要一些时间...")
|
272
|
+
|
273
|
+
# 忽略的目录列表, 这些目录时系统目录,不需要分析
|
274
|
+
ignore_dir_list = [
|
275
|
+
"/System", # 系统目录
|
276
|
+
"/Volumes", # 外部挂载卷
|
277
|
+
"/private", # 私有目录
|
278
|
+
]
|
279
|
+
|
244
280
|
|
245
281
|
directory_sizes = []
|
246
282
|
|
@@ -252,11 +288,14 @@ class SpaceAnalyzer:
|
|
252
288
|
# 跳过隐藏文件和系统文件
|
253
289
|
if item.startswith('.') and item not in ['.Trash', '.localized']:
|
254
290
|
continue
|
291
|
+
|
292
|
+
if item_path in ignore_dir_list:
|
293
|
+
continue
|
255
294
|
|
256
295
|
if os.path.isdir(item_path):
|
257
296
|
try:
|
258
297
|
# 进度提示:当前正在读取的目录(单行覆盖)
|
259
|
-
sys.stdout.write(f"\r-> 正在读取: \033[36m{item_path}\033[0m")
|
298
|
+
sys.stdout.write(f"\r\033[K-> 正在读取: \033[36m{item_path}\033[0m")
|
260
299
|
sys.stdout.flush()
|
261
300
|
size = self.get_directory_size(item_path)
|
262
301
|
directory_sizes.append((item_path, size))
|
@@ -284,19 +323,58 @@ class SpaceAnalyzer:
|
|
284
323
|
return []
|
285
324
|
|
286
325
|
def get_system_info(self) -> Dict:
|
287
|
-
"""
|
326
|
+
"""获取系统信息(包括 CPU、内存、GPU、硬盘等硬件信息)"""
|
327
|
+
system_info = {}
|
328
|
+
|
288
329
|
try:
|
289
|
-
#
|
330
|
+
# 获取系统版本信息
|
290
331
|
result = subprocess.run(['sw_vers'], capture_output=True, text=True)
|
291
|
-
system_info = {}
|
292
332
|
for line in result.stdout.split('\n'):
|
293
333
|
if ':' in line:
|
294
334
|
key, value = line.split(':', 1)
|
295
335
|
system_info[key.strip()] = value.strip()
|
336
|
+
except Exception:
|
337
|
+
system_info["ProductName"] = "macOS"
|
338
|
+
system_info["ProductVersion"] = "未知"
|
339
|
+
|
340
|
+
try:
|
341
|
+
# 获取 CPU 信息
|
342
|
+
cpu_result = subprocess.run(['sysctl', '-n', 'machdep.cpu.brand_string'],
|
343
|
+
capture_output=True, text=True)
|
344
|
+
if cpu_result.returncode == 0:
|
345
|
+
system_info["CPU"] = cpu_result.stdout.strip()
|
296
346
|
|
297
|
-
|
347
|
+
# 获取 CPU 核心数
|
348
|
+
cores_result = subprocess.run(['sysctl', '-n', 'hw.ncpu'],
|
349
|
+
capture_output=True, text=True)
|
350
|
+
if cores_result.returncode == 0:
|
351
|
+
system_info["CPU核心数"] = cores_result.stdout.strip()
|
352
|
+
|
353
|
+
except Exception:
|
354
|
+
system_info["CPU"] = "未知"
|
355
|
+
system_info["CPU核心数"] = "未知"
|
356
|
+
|
357
|
+
try:
|
358
|
+
# 获取内存信息
|
359
|
+
mem_result = subprocess.run(['sysctl', '-n', 'hw.memsize'],
|
360
|
+
capture_output=True, text=True)
|
361
|
+
if mem_result.returncode == 0:
|
362
|
+
mem_bytes = int(mem_result.stdout.strip())
|
363
|
+
system_info["内存"] = self.format_bytes(mem_bytes)
|
364
|
+
except Exception:
|
365
|
+
system_info["内存"] = "未知"
|
366
|
+
|
367
|
+
|
368
|
+
try:
|
369
|
+
# 获取启动时间
|
370
|
+
boot_result = subprocess.run(['uptime'], capture_output=True, text=True)
|
371
|
+
if boot_result.returncode == 0:
|
372
|
+
uptime_line = boot_result.stdout.strip()
|
373
|
+
system_info["运行时间"] = uptime_line
|
298
374
|
except Exception:
|
299
|
-
|
375
|
+
system_info["运行时间"] = "未知"
|
376
|
+
|
377
|
+
return system_info
|
300
378
|
|
301
379
|
|
302
380
|
class SpaceCli:
|
@@ -311,6 +389,200 @@ class SpaceCli:
|
|
311
389
|
os.makedirs(app_cache_dir, exist_ok=True)
|
312
390
|
self.app_index = IndexStore(index_file=os.path.join(app_cache_dir, "apps.json"))
|
313
391
|
|
392
|
+
# —— 应用删除相关 ——
|
393
|
+
def _candidate_app_paths(self, app_name: str) -> List[str]:
|
394
|
+
"""根据应用名推导可能占用空间的相关目录/文件路径列表。"""
|
395
|
+
home = str(Path.home())
|
396
|
+
candidates: List[str] = []
|
397
|
+
possible_bases = [
|
398
|
+
("/Applications", f"{app_name}.app"),
|
399
|
+
(os.path.join(home, "Applications"), f"{app_name}.app"),
|
400
|
+
("/Library/Application Support", app_name),
|
401
|
+
(os.path.join(home, "Library", "Application Support"), app_name),
|
402
|
+
("/Library/Caches", app_name),
|
403
|
+
(os.path.join(home, "Library", "Caches"), app_name),
|
404
|
+
("/Library/Logs", app_name),
|
405
|
+
(os.path.join(home, "Library", "Logs"), app_name),
|
406
|
+
(os.path.join(home, "Library", "Containers"), app_name),
|
407
|
+
]
|
408
|
+
# 直接拼接命中
|
409
|
+
for base, tail in possible_bases:
|
410
|
+
path = os.path.join(base, tail)
|
411
|
+
if os.path.exists(path):
|
412
|
+
candidates.append(path)
|
413
|
+
# 模糊扫描:包含应用名的目录
|
414
|
+
scan_dirs = [
|
415
|
+
"/Applications",
|
416
|
+
os.path.join(home, "Applications"),
|
417
|
+
"/Library/Application Support",
|
418
|
+
os.path.join(home, "Library", "Application Support"),
|
419
|
+
"/Library/Caches",
|
420
|
+
os.path.join(home, "Library", "Caches"),
|
421
|
+
"/Library/Logs",
|
422
|
+
os.path.join(home, "Library", "Logs"),
|
423
|
+
os.path.join(home, "Library", "Containers"),
|
424
|
+
]
|
425
|
+
app_lower = app_name.lower()
|
426
|
+
for base in scan_dirs:
|
427
|
+
if not os.path.exists(base):
|
428
|
+
continue
|
429
|
+
try:
|
430
|
+
for item in os.listdir(base):
|
431
|
+
item_path = os.path.join(base, item)
|
432
|
+
# 只收集目录或 .app 包
|
433
|
+
if not os.path.isdir(item_path):
|
434
|
+
continue
|
435
|
+
name_lower = item.lower()
|
436
|
+
if app_lower in name_lower:
|
437
|
+
candidates.append(item_path)
|
438
|
+
except (PermissionError, OSError):
|
439
|
+
continue
|
440
|
+
# 去重并按路径长度降序(先删更深层,避免空目录残留)
|
441
|
+
uniq: List[str] = []
|
442
|
+
seen = set()
|
443
|
+
for p in sorted(set(candidates), key=lambda x: len(x), reverse=True):
|
444
|
+
if p not in seen:
|
445
|
+
uniq.append(p)
|
446
|
+
seen.add(p)
|
447
|
+
return uniq
|
448
|
+
|
449
|
+
def _delete_paths_and_sum(self, paths: List[str]) -> Tuple[int, List[Tuple[str, str]]]:
|
450
|
+
"""删除给定路径列表,返回释放的总字节数与失败列表(路径, 原因)。"""
|
451
|
+
total_freed = 0
|
452
|
+
failures: List[Tuple[str, str]] = []
|
453
|
+
|
454
|
+
def _try_fix_permissions(path: str) -> None:
|
455
|
+
"""尝试修复权限与不可变标记以便删除。"""
|
456
|
+
try:
|
457
|
+
# 去除不可变标记(普通用户能去除的场景)
|
458
|
+
subprocess.run(["chflags", "-R", "nouchg", path], capture_output=True)
|
459
|
+
except Exception:
|
460
|
+
pass
|
461
|
+
try:
|
462
|
+
os.chmod(path, 0o777)
|
463
|
+
except Exception:
|
464
|
+
pass
|
465
|
+
|
466
|
+
def _onerror(func, path, exc_info):
|
467
|
+
# 当 rmtree 无法删除时,尝试修复权限并重试一次
|
468
|
+
_try_fix_permissions(path)
|
469
|
+
try:
|
470
|
+
func(path)
|
471
|
+
except Exception:
|
472
|
+
# 让上层捕获
|
473
|
+
raise
|
474
|
+
for p in paths:
|
475
|
+
try:
|
476
|
+
size_before = 0
|
477
|
+
try:
|
478
|
+
if os.path.isdir(p):
|
479
|
+
size_before = self.analyzer.get_directory_size(p)
|
480
|
+
elif os.path.isfile(p):
|
481
|
+
size_before = os.path.getsize(p)
|
482
|
+
except Exception:
|
483
|
+
size_before = 0
|
484
|
+
if os.path.isdir(p) and not os.path.islink(p):
|
485
|
+
try:
|
486
|
+
shutil.rmtree(p, ignore_errors=False, onerror=_onerror)
|
487
|
+
except Exception:
|
488
|
+
# 目录删除失败,降级为逐项尝试删除(尽量清理可删部分)
|
489
|
+
for dirpath, dirnames, filenames in os.walk(p, topdown=False):
|
490
|
+
for name in filenames:
|
491
|
+
fpath = os.path.join(dirpath, name)
|
492
|
+
try:
|
493
|
+
_try_fix_permissions(fpath)
|
494
|
+
os.remove(fpath)
|
495
|
+
except Exception:
|
496
|
+
continue
|
497
|
+
for name in dirnames:
|
498
|
+
dpath = os.path.join(dirpath, name)
|
499
|
+
try:
|
500
|
+
_try_fix_permissions(dpath)
|
501
|
+
os.rmdir(dpath)
|
502
|
+
except Exception:
|
503
|
+
continue
|
504
|
+
# 最后尝试删除顶层目录
|
505
|
+
_try_fix_permissions(p)
|
506
|
+
os.rmdir(p)
|
507
|
+
else:
|
508
|
+
os.remove(p)
|
509
|
+
total_freed += size_before
|
510
|
+
except Exception as e:
|
511
|
+
failures.append((p, str(e)))
|
512
|
+
return total_freed, failures
|
513
|
+
|
514
|
+
def _offer_app_delete(self, apps: List[Tuple[str, int]]) -> None:
|
515
|
+
"""在已打印的应用列表后,提供按序号一键删除功能。"""
|
516
|
+
if not sys.stdin.isatty() or getattr(self.args, 'no_prompt', False):
|
517
|
+
return
|
518
|
+
try:
|
519
|
+
ans = input("是否要一键删除某个应用?输入序号或回车跳过: ").strip()
|
520
|
+
except EOFError:
|
521
|
+
ans = ""
|
522
|
+
if not ans:
|
523
|
+
return
|
524
|
+
try:
|
525
|
+
idx = int(ans)
|
526
|
+
except ValueError:
|
527
|
+
print("❌ 无效的输入(应为数字序号)")
|
528
|
+
return
|
529
|
+
if idx < 1 or idx > len(apps):
|
530
|
+
print("❌ 序号超出范围")
|
531
|
+
return
|
532
|
+
app_name, app_size = apps[idx - 1]
|
533
|
+
size_str = self.analyzer.format_bytes(app_size)
|
534
|
+
try:
|
535
|
+
confirm = input(f"确认删除应用及相关缓存: {app_name} (约 {size_str})?[y/N]: ").strip().lower()
|
536
|
+
except EOFError:
|
537
|
+
confirm = ""
|
538
|
+
if confirm not in ("y", "yes"):
|
539
|
+
print("已取消删除")
|
540
|
+
return
|
541
|
+
related_paths = self._candidate_app_paths(app_name)
|
542
|
+
if not related_paths:
|
543
|
+
print("未找到可删除的相关目录/文件")
|
544
|
+
return
|
545
|
+
print("将尝试删除以下路径:")
|
546
|
+
for p in related_paths:
|
547
|
+
print(f" - {p}")
|
548
|
+
try:
|
549
|
+
confirm2 = input("再次确认删除以上路径?[y/N]: ").strip().lower()
|
550
|
+
except EOFError:
|
551
|
+
confirm2 = ""
|
552
|
+
if confirm2 not in ("y", "yes"):
|
553
|
+
print("已取消删除")
|
554
|
+
return
|
555
|
+
freed, failures = self._delete_paths_and_sum(related_paths)
|
556
|
+
print(f"✅ 删除完成,预计释放空间: {self.analyzer.format_bytes(freed)}")
|
557
|
+
if failures:
|
558
|
+
print("以下路径删除失败,可能需要手动或管理员权限:")
|
559
|
+
for p, reason in failures:
|
560
|
+
print(f" - {p} -> {reason}")
|
561
|
+
# 常见提示:Operation not permitted(SIP/容器元数据等)
|
562
|
+
if any("Operation not permitted" in r for _, r in failures):
|
563
|
+
print("提示:部分系统受保护或容器元数据文件无法删除。可尝试:")
|
564
|
+
print(" - 先退出相关应用(如 Docker)再重试")
|
565
|
+
print(" - 给予当前终端“完全磁盘访问权限”(系统设置 → 隐私与安全性)")
|
566
|
+
print(" - 仅删除用户目录下缓存,保留系统级容器元数据")
|
567
|
+
|
568
|
+
# 通用渲染:目录与应用(减少重复)
|
569
|
+
def _render_dirs(self, entries: List[Tuple[str, int]], total_bytes: int) -> None:
|
570
|
+
for i, (dir_path, size) in enumerate(entries, 1):
|
571
|
+
size_str = self.analyzer.format_bytes(size)
|
572
|
+
percentage = (size / total_bytes) * 100 if total_bytes else 0
|
573
|
+
# 1G 以上红色,否则绿色
|
574
|
+
color = "\033[31m" if size >= 1024**3 else "\033[32m"
|
575
|
+
print(f"{i:2d}. \033[36m{dir_path}\033[0m -- 大小: {color}{size_str}\033[0m (\033[33m{percentage:.2f}%\033[0m)")
|
576
|
+
|
577
|
+
def _render_apps(self, entries: List[Tuple[str, int]], disk_total: int) -> None:
|
578
|
+
for i, (app, size) in enumerate(entries, 1):
|
579
|
+
size_str = self.analyzer.format_bytes(size)
|
580
|
+
pct = (size / disk_total) * 100 if disk_total else 0
|
581
|
+
suggestion = "建议卸载或清理缓存" if size >= 5 * 1024**3 else "可保留,定期清理缓存"
|
582
|
+
# 3G 以上红色,否则绿色
|
583
|
+
color = "\033[31m" if size >= 3 * 1024**3 else "\033[32m"
|
584
|
+
print(f"{i:2d}. \033[36m{app}\033[0m -- 占用: {color}{size_str}\033[0m ({pct:.2f}%) — {suggestion}")
|
585
|
+
|
314
586
|
def analyze_app_directories(self, top_n: int = 20,
|
315
587
|
index: IndexStore = None,
|
316
588
|
use_index: bool = True,
|
@@ -369,7 +641,8 @@ class SpaceCli:
|
|
369
641
|
continue
|
370
642
|
key = app_key_from_path(item_path)
|
371
643
|
# 进度提示:当前应用相关目录(单行覆盖)
|
372
|
-
|
644
|
+
item_path = item_path[:100]
|
645
|
+
sys.stdout.write(f"\r\033[K-> 正在读取: \033[36m{item_path}\033[0m")
|
373
646
|
sys.stdout.flush()
|
374
647
|
size = self.analyzer.get_directory_size(item_path)
|
375
648
|
scanned_dirs.append(item_path)
|
@@ -419,37 +692,99 @@ class SpaceCli:
|
|
419
692
|
print(f"建议: \033[36m{message}\033[0m")
|
420
693
|
print()
|
421
694
|
|
422
|
-
def print_largest_directories(self, path: str = "/", top_n: int = 20):
|
695
|
+
def print_largest_directories(self, path: str = "/Library", top_n: int = 20):
|
423
696
|
"""打印占用空间最大的目录"""
|
424
697
|
print("=" * 60)
|
425
698
|
print("📊 占用空间最大的目录")
|
426
699
|
print("=" * 60)
|
427
700
|
|
701
|
+
# 若有缓存:直接显示缓存,然后再询问是否重新分析
|
702
|
+
if self.args.use_index:
|
703
|
+
cached = self.index.get(path)
|
704
|
+
if cached and cached.get("entries"):
|
705
|
+
cached_entries = [(e["path"], int(e["size"])) for e in cached["entries"]][:top_n]
|
706
|
+
total_info = self.analyzer.get_disk_usage(path)
|
707
|
+
total_bytes = total_info['total'] if total_info else 1
|
708
|
+
print(f"(来自索引) 显示前 {min(len(cached_entries), top_n)} 个最大的目录:\n")
|
709
|
+
self._render_dirs(cached_entries, total_bytes)
|
710
|
+
if sys.stdin.isatty() and not self.args.no_prompt:
|
711
|
+
try:
|
712
|
+
ans = input("是否重新分析以刷新索引?[y/N]: ").strip().lower()
|
713
|
+
except EOFError:
|
714
|
+
ans = ""
|
715
|
+
if ans not in ("y", "yes"):
|
716
|
+
# 提供下探分析选项
|
717
|
+
self._offer_drill_down_analysis(cached_entries, path)
|
718
|
+
return
|
719
|
+
else:
|
720
|
+
return
|
721
|
+
|
428
722
|
directories = self.analyzer.analyze_largest_directories(
|
429
723
|
path,
|
430
724
|
top_n=top_n,
|
431
725
|
index=self.index,
|
432
726
|
use_index=self.args.use_index,
|
433
|
-
reindex=
|
727
|
+
reindex=True, # 走到这里表示要刷新
|
434
728
|
index_ttl_hours=self.args.index_ttl,
|
435
|
-
prompt=
|
729
|
+
prompt=False,
|
436
730
|
)
|
437
|
-
|
438
731
|
if not directories:
|
439
732
|
print("❌ 无法分析目录大小")
|
440
733
|
return
|
734
|
+
total_info = self.analyzer.get_disk_usage(path)
|
735
|
+
total_bytes = total_info['total'] if total_info else 1
|
736
|
+
print("\n已重新分析,最新结果:\n")
|
737
|
+
self._render_dirs(directories, total_bytes)
|
738
|
+
|
739
|
+
# 提供下探分析选项
|
740
|
+
self._offer_drill_down_analysis(directories, path)
|
741
|
+
|
742
|
+
def _offer_drill_down_analysis(self, directories: List[Tuple[str, int]], current_path: str) -> None:
|
743
|
+
"""提供交互式下探分析选项"""
|
744
|
+
if not sys.stdin.isatty() or getattr(self.args, 'no_prompt', False):
|
745
|
+
return
|
441
746
|
|
442
|
-
print(
|
747
|
+
print("\n" + "=" * 60)
|
748
|
+
print("🔍 下探分析选项")
|
749
|
+
print("=" * 60)
|
750
|
+
print("选择序号进行深度分析,选择0返回上一级,直接回车退出:")
|
443
751
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
752
|
+
try:
|
753
|
+
choice = input("请输入选择 [回车=退出]: ").strip()
|
754
|
+
except EOFError:
|
755
|
+
return
|
756
|
+
|
757
|
+
if not choice:
|
758
|
+
return
|
759
|
+
|
760
|
+
try:
|
761
|
+
idx = int(choice)
|
762
|
+
except ValueError:
|
763
|
+
print("❌ 无效的输入(应为数字序号)")
|
764
|
+
return
|
765
|
+
|
766
|
+
if idx == 0:
|
767
|
+
# 返回上一级
|
768
|
+
parent_path = os.path.dirname(current_path.rstrip('/'))
|
769
|
+
if parent_path != current_path and parent_path != '/':
|
770
|
+
print(f"\n🔄 返回上一级: {parent_path}")
|
771
|
+
self.print_largest_directories(parent_path, self.args.top_n)
|
772
|
+
else:
|
773
|
+
print("❌ 已在根目录,无法返回上一级")
|
774
|
+
return
|
775
|
+
|
776
|
+
if idx < 1 or idx > len(directories):
|
777
|
+
print("❌ 序号超出范围")
|
778
|
+
return
|
779
|
+
|
780
|
+
selected_path, selected_size = directories[idx - 1]
|
781
|
+
size_str = self.analyzer.format_bytes(selected_size)
|
782
|
+
|
783
|
+
print(f"\n🔍 正在分析: {selected_path} ({size_str})")
|
784
|
+
print("=" * 60)
|
785
|
+
|
786
|
+
# 递归调用下探分析
|
787
|
+
self.print_largest_directories(selected_path, self.args.top_n)
|
453
788
|
|
454
789
|
def print_app_analysis(self, top_n: int = 20):
|
455
790
|
"""打印应用目录占用分析,并给出卸载建议"""
|
@@ -457,29 +792,44 @@ class SpaceCli:
|
|
457
792
|
print("🧩 应用目录空间分析与卸载建议")
|
458
793
|
print("=" * 60)
|
459
794
|
|
795
|
+
# 先显示缓存,再决定是否刷新
|
796
|
+
if self.args.use_index:
|
797
|
+
cached = self.app_index.get_named("apps_aggregate")
|
798
|
+
if cached and cached.get("entries"):
|
799
|
+
cached_entries = [(e["name"], int(e["size"])) for e in cached["entries"]][:top_n]
|
800
|
+
total = self.analyzer.get_disk_usage("/")
|
801
|
+
disk_total = total['total'] if total else 1
|
802
|
+
print(f"(来自索引) 显示前 {min(len(cached_entries), top_n)} 个空间占用最高的应用:\n")
|
803
|
+
self._render_apps(cached_entries, disk_total)
|
804
|
+
# 提供一键删除
|
805
|
+
self._offer_app_delete(cached_entries)
|
806
|
+
if sys.stdin.isatty() and not self.args.no_prompt:
|
807
|
+
try:
|
808
|
+
ans = input("是否重新分析应用以刷新索引?[y/N]: ").strip().lower()
|
809
|
+
except EOFError:
|
810
|
+
ans = ""
|
811
|
+
if ans not in ("y", "yes"):
|
812
|
+
return
|
813
|
+
else:
|
814
|
+
return
|
815
|
+
|
460
816
|
apps = self.analyze_app_directories(
|
461
817
|
top_n=top_n,
|
462
818
|
index=self.app_index,
|
463
819
|
use_index=self.args.use_index,
|
464
|
-
reindex=
|
820
|
+
reindex=True,
|
465
821
|
index_ttl_hours=self.args.index_ttl,
|
466
|
-
prompt=
|
822
|
+
prompt=False,
|
467
823
|
)
|
468
824
|
if not apps:
|
469
825
|
print("❌ 未发现可分析的应用目录")
|
470
826
|
return
|
471
|
-
|
472
827
|
total = self.analyzer.get_disk_usage("/")
|
473
828
|
disk_total = total['total'] if total else 1
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
pct = (size / disk_total) * 100
|
479
|
-
suggestion = "建议卸载或清理缓存" if size >= 5 * 1024**3 else "可保留,定期清理缓存"
|
480
|
-
print(f"{i:2d}. \033[36m{app}\033[0m -- 占用: {size_str} ({pct:.2f}%) — {suggestion}")
|
481
|
-
##print(f" 占用: {size_str} ({pct:.2f}%) — {suggestion}")
|
482
|
-
#print()
|
829
|
+
print("\n已重新分析,最新应用占用结果:\n")
|
830
|
+
self._render_apps(apps, disk_total)
|
831
|
+
# 提供一键删除
|
832
|
+
self._offer_app_delete(apps)
|
483
833
|
|
484
834
|
def print_home_deep_analysis(self, top_n: int = 20):
|
485
835
|
"""对用户目录的 Library / Downloads / Documents 分别下探分析"""
|
@@ -545,7 +895,7 @@ class SpaceCli:
|
|
545
895
|
system_info = self.analyzer.get_system_info()
|
546
896
|
|
547
897
|
for key, value in system_info.items():
|
548
|
-
print(f"{key}: {value}")
|
898
|
+
print(f"{key}: \033[36m{value}\033[0m")
|
549
899
|
print()
|
550
900
|
|
551
901
|
def export_report(self, output_file: str, path: str = "/"):
|
@@ -744,50 +1094,57 @@ def main():
|
|
744
1094
|
print("🧭 SpaceCli 菜单(直接回车 = 执行全部项目)")
|
745
1095
|
print("=" * 60)
|
746
1096
|
home_path = str(Path.home())
|
747
|
-
print("1) \033[36m
|
1097
|
+
print("1) \033[36m执行主要项目(系统信息 + 健康 + 应用)\033[0m")
|
748
1098
|
print(f"2) \033[36m当前用户目录分析(路径: {home_path})\033[0m")
|
749
1099
|
print("3) \033[36m仅显示系统信息\033[0m")
|
750
1100
|
print("4) \033[36m仅显示磁盘健康状态\033[0m")
|
751
|
-
print("5) \033[36m
|
752
|
-
print("6) \033[36m
|
753
|
-
print("7) \033[36m
|
1101
|
+
print("5) \033[36m交互式目录空间分析\033[0m")
|
1102
|
+
print("6) \033[36m仅分析程序应用目录空间\033[0m")
|
1103
|
+
print("7) \033[36m仅进行大文件分析(很耗时,可随时终止)\033[0m")
|
754
1104
|
print("0) \033[36m退出\033[0m")
|
755
1105
|
try:
|
756
1106
|
choice = input("请选择 [回车=1]: ").strip()
|
757
1107
|
except EOFError:
|
758
1108
|
choice = ""
|
759
1109
|
|
760
|
-
if choice == "0":
|
1110
|
+
if choice == "0": # 退出
|
761
1111
|
sys.exit(0)
|
762
|
-
elif choice == "2":
|
1112
|
+
elif choice == "2": # 仅显示当前用户目录分析
|
763
1113
|
args.path = home_path
|
764
1114
|
args.apps = False
|
765
1115
|
args.health_only = False
|
766
1116
|
args.directories_only = False
|
767
|
-
elif choice == "3":
|
1117
|
+
elif choice == "3": # 仅显示系统信息
|
1118
|
+
args.health_only = False
|
1119
|
+
args.directories_only = False
|
1120
|
+
args.apps = False
|
1121
|
+
args.big_files = False
|
1122
|
+
elif choice == "4": # 仅显示磁盘健康状态
|
768
1123
|
args.health_only = True
|
769
1124
|
args.directories_only = False
|
770
1125
|
args.apps = False
|
771
|
-
|
1126
|
+
args.big_files = False
|
1127
|
+
elif choice == "5": # 仅显示最大目录列表
|
772
1128
|
args.health_only = False
|
773
1129
|
args.directories_only = True
|
774
1130
|
args.apps = False
|
775
|
-
|
1131
|
+
args.big_files = False
|
1132
|
+
elif choice == "6": # 仅显示应用目录分析与建议
|
776
1133
|
args.health_only = False
|
777
1134
|
args.directories_only = False
|
778
|
-
args.apps = False
|
779
|
-
elif choice == "6":
|
780
|
-
args.health_only = False
|
781
|
-
args.directories_only = True
|
782
1135
|
args.apps = True
|
783
|
-
|
1136
|
+
args.big_files = False
|
1137
|
+
elif choice == "7": # 仅显示大文件分析
|
784
1138
|
args.health_only = False
|
785
|
-
args.directories_only =
|
1139
|
+
args.directories_only = False
|
786
1140
|
args.apps = False
|
787
1141
|
args.big_files = True
|
788
|
-
else:
|
789
|
-
|
790
|
-
args.
|
1142
|
+
else: # 默认执行全部(用户不选择,或者选择1)
|
1143
|
+
args.health_only = True
|
1144
|
+
args.directories_only = False
|
1145
|
+
args.big_files = False
|
1146
|
+
args.apps = True
|
1147
|
+
|
791
1148
|
|
792
1149
|
# --home 优先设置路径
|
793
1150
|
if getattr(args, 'home', False):
|
@@ -805,15 +1162,14 @@ def main():
|
|
805
1162
|
|
806
1163
|
try:
|
807
1164
|
# 显示系统信息
|
808
|
-
|
809
|
-
space_cli.print_system_info()
|
1165
|
+
space_cli.print_system_info()
|
810
1166
|
|
811
1167
|
# 显示磁盘健康状态
|
812
|
-
if
|
1168
|
+
if args.health_only:
|
813
1169
|
space_cli.print_disk_health(args.path)
|
814
1170
|
|
815
1171
|
# 显示目录分析
|
816
|
-
if
|
1172
|
+
if args.directories_only or args.path !='/':
|
817
1173
|
space_cli.print_largest_directories(args.path, args.top_n)
|
818
1174
|
# 若分析路径为当前用户目录,做深度分析
|
819
1175
|
if os.path.abspath(args.path) == os.path.abspath(str(Path.home())):
|
@@ -824,7 +1180,8 @@ def main():
|
|
824
1180
|
space_cli.print_app_analysis(args.top_n)
|
825
1181
|
|
826
1182
|
# 大文件分析
|
827
|
-
if getattr(args, 'big_files', False):
|
1183
|
+
#if getattr(args, 'big_files', False):
|
1184
|
+
if args.big_files:
|
828
1185
|
space_cli.print_big_files(args.path, top_n=args.big_files_top, min_size_bytes=args.big_files_min_bytes)
|
829
1186
|
|
830
1187
|
# 导出报告
|
@@ -1,6 +0,0 @@
|
|
1
|
-
space_cli.py,sha256=cQR64qsQyjGkkOskkm3EH59dx-T2idfv_YYuI6VPk9A,32260
|
2
|
-
myspace_cli-1.2.0.dist-info/METADATA,sha256=AKVi6Os8zDw6h8z_St0DAgaYy3PpCT3HUOzhCe9cUHY,8284
|
3
|
-
myspace_cli-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
4
|
-
myspace_cli-1.2.0.dist-info/entry_points.txt,sha256=sIVEmPf6W8aNa7KiqOwAI6JrsgHlQciO5xH2G29tKPQ,45
|
5
|
-
myspace_cli-1.2.0.dist-info/top_level.txt,sha256=lnUfbQX9h-9ZjecMVCOWucS2kx69FwTndlrb48S20Sc,10
|
6
|
-
myspace_cli-1.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|