-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscan_and_remove_zero_size_files.py
More file actions
executable file
·189 lines (146 loc) · 5.55 KB
/
Copy pathscan_and_remove_zero_size_files.py
File metadata and controls
executable file
·189 lines (146 loc) · 5.55 KB
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/env python3
"""
Scan directories for zero-size files and optionally remove them.
This script recursively searches specified directories for files with zero bytes
and can remove them. Useful for cleaning up corrupted or incomplete data files
(dill, parquet, bbb, etc.) that were created but never written to.
Examples:
# Dry run - scan all files in directory
python scan_and_remove_zero_size_files.py /path/to/data --dry-run
# Remove zero-size parquet files
python scan_and_remove_zero_size_files.py /path/to/data --file-type "*.parquet"
# Scan multiple date directories for zero-size dill files
python scan_and_remove_zero_size_files.py 20251001 20251002 20251003 --file-type "*.dill"
# Remove all zero-size files (any type)
python scan_and_remove_zero_size_files.py /path/to/data
# Verbose output showing each file found
python scan_and_remove_zero_size_files.py /path/to/data --verbose --dry-run
"""
import argparse
import os
import sys
from pathlib import Path
def find_zero_size_files(directory: Path, file_pattern: str = "*") -> list[Path]:
"""
Recursively find all zero-size files matching the pattern.
Args:
directory: Root directory to search
file_pattern: Glob pattern for files (e.g., "*.parquet", "*.dill", "*")
Returns:
List of Path objects for zero-size files
"""
zero_size_files = []
try:
# Use rglob for recursive search with pattern
for file_path in directory.rglob(file_pattern):
if file_path.is_file():
try:
if file_path.stat().st_size == 0:
zero_size_files.append(file_path)
except (OSError, PermissionError) as e:
print(f"[warn] Cannot stat {file_path}: {e}", file=sys.stderr)
except (OSError, PermissionError) as e:
print(f"[error] Cannot access directory {directory}: {e}", file=sys.stderr)
return zero_size_files
def scan_directory(
directory: Path,
file_pattern: str,
dry_run: bool,
verbose: bool
) -> tuple[int, int]:
"""
Scan a directory for zero-size files and optionally remove them.
Args:
directory: Directory to scan
file_pattern: Glob pattern for file matching
dry_run: If True, only report files without removing
verbose: If True, print each file found
Returns:
Tuple of (files_found, files_removed)
"""
if not directory.exists():
print(f"[skip] Directory does not exist: {directory}")
return 0, 0
if not directory.is_dir():
print(f"[skip] Not a directory: {directory}")
return 0, 0
print(f"[scan] {directory}")
zero_files = find_zero_size_files(directory, file_pattern)
if verbose:
for f in zero_files:
print(f" [zero-size] {f.relative_to(directory) if f.is_relative_to(directory) else f}")
removed = 0
if not dry_run and zero_files:
for file_path in zero_files:
try:
file_path.unlink()
removed += 1
if verbose:
print(f" [removed] {file_path.relative_to(directory) if file_path.is_relative_to(directory) else file_path}")
except Exception as e:
print(f"[warn] Failed to remove {file_path}: {e}", file=sys.stderr)
return len(zero_files), removed
def parse_args():
parser = argparse.ArgumentParser(
description="Scan directories for zero-size files and optionally remove them.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Dry run on single directory
%(prog)s /mnt/share/beesbook2025/pi/ --dry-run
# Remove zero-size parquet files from multiple directories
%(prog)s /mnt/share/beesbook2025/pi/20251001 /mnt/share/beesbook2025/pi/20251002 --file-type "*.parquet"
# Remove all zero-size dill files with verbose output
%(prog)s /mnt/share/beesbook2025/results/data_tracked --file-type "*.dill" --verbose
# Scan multiple date directories (e.g., split across machines)
%(prog)s 20251001 20251002 20251003 20251004 20251005 --file-type "*.parquet"
"""
)
parser.add_argument(
"directories",
nargs="+",
help="One or more directories to scan recursively"
)
parser.add_argument(
"--file-type",
default="*",
help='Glob pattern for files to check (default: "*" for all files). Examples: "*.parquet", "*.dill", "*.bbb"'
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Scan only; do not delete files"
)
parser.add_argument(
"--verbose",
action="store_true",
help="Print each zero-size file found"
)
return parser.parse_args()
def main():
args = parse_args()
directories = [Path(d) for d in args.directories]
total_found = 0
total_removed = 0
for directory in directories:
found, removed = scan_directory(
directory,
args.file_type,
args.dry_run,
args.verbose
)
total_found += found
total_removed += removed
if args.dry_run:
print(f"[dir] found={found} (dry-run)")
else:
print(f"[dir] found={found} removed={removed}")
print()
if args.dry_run:
print(f"[total] found={total_found} zero-size files (dry-run)")
if total_found > 0:
print(f"[info] Run without --dry-run to remove these files")
else:
print(f"[total] found={total_found} removed={total_removed}")
if __name__ == "__main__":
main()