RUN.sh
· 394 B · Bash
Raw
# 保存脚本为 compare_manifests.py,并赋可执行权限
chmod +x compare_manifests.py
# 运行脚本,依次传入 Ubuntu、Zorin、Anduin 三个 manifest 文件路径
./compare_manifests.py \
"/media/anduin/Ubuntu 25.04 amd64/casper/filesystem.manifest" \
"/media/anduin/Zorin OS 17.3 Core 64bit/casper/filesystem.manifest" \
"/media/anduin/anduinos/casper/filesystem.manifest"
1 | # 保存脚本为 compare_manifests.py,并赋可执行权限 |
2 | chmod +x compare_manifests.py |
3 | |
4 | # 运行脚本,依次传入 Ubuntu、Zorin、Anduin 三个 manifest 文件路径 |
5 | ./compare_manifests.py \ |
6 | "/media/anduin/Ubuntu 25.04 amd64/casper/filesystem.manifest" \ |
7 | "/media/anduin/Zorin OS 17.3 Core 64bit/casper/filesystem.manifest" \ |
8 | "/media/anduin/anduinos/casper/filesystem.manifest" |
9 |
compare_manifest.sh
· 5.1 KiB · Bash
Raw
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
compare_manifests.py
读取三个 filesystem.manifest 文件,比较包的并集,并输出包含发行版勾选标记、Priority、Section、Description 的 Markdown 表格。
增加详细日志输出,并使用多线程加速 apt show 调用。
修复:自动剥离包名中的 ":<arch>" 后缀。
"""
import sys
import subprocess
import logging
import concurrent.futures
import time
# 日志配置
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
def parse_manifest(path):
"""
读取 manifest 文件,返回其中所有包名的集合。
自动剥离包名中的 ":<arch>" 后缀。
"""
pkgs = set()
logging.info(f"Parsing manifest: {path}")
try:
with open(path, encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
raw_pkg = line.split()[0]
pkg = raw_pkg.split(':', 1)[0]
pkgs.add(pkg)
logging.info(f"Found {len(pkgs)} unique packages in {path}")
except Exception as e:
logging.error(f"Error reading manifest {path}: {e}")
sys.exit(1)
return pkgs
def get_pkg_info(pkg):
"""
调用 `apt show pkg`,解析 Priority、Section、Description(首段),
返回 dict 包含信息。
若调用失败,返回空字段。
"""
logging.debug(f"Fetching apt info for package: {pkg}")
try:
p = subprocess.run(
["apt", "show", pkg],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
encoding='utf-8',
timeout=30
)
except subprocess.TimeoutExpired:
logging.warning(f"Timeout fetching info for {pkg}")
return {"Priority": "", "Section": "", "Description": ""}
if p.returncode != 0:
logging.debug(f"apt show failed for {pkg}")
return {"Priority": "", "Section": "", "Description": ""}
priority = ""
section = ""
desc_lines = []
in_desc = False
for line in p.stdout.splitlines():
if in_desc:
if not line.strip():
break
desc_lines.append(line.strip())
else:
if line.startswith("Priority:"):
priority = line.split(":", 1)[1].strip()
elif line.startswith("Section:"):
section = line.split(":", 1)[1].strip()
elif line.startswith("Description:"):
desc = line.split(":", 1)[1].strip()
desc_lines.append(desc)
in_desc = True
full_desc = " ".join(desc_lines)
logging.debug(f"Obtained info for {pkg}: priority={priority}, section={section}")
return {"Priority": priority, "Section": section, "Description": full_desc}
def main():
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <ubuntu_manifest> <zorin_manifest> <anduin_manifest>")
sys.exit(1)
start_time = time.time()
paths = {
"Ubuntu": sys.argv[1],
"Zorin": sys.argv[2],
"Anduin": sys.argv[3],
}
# 解析 manifests
sets = {name: parse_manifest(path) for name, path in paths.items()}
all_pkgs = sorted(set.union(*sets.values()))
total = len(all_pkgs)
logging.info(f"Total unique packages to process: {total}")
# 多线程获取 apt 信息
pkg_infos = {}
max_workers = min(10, total)
logging.info(f"Starting ThreadPoolExecutor with {max_workers} workers")
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_pkg = {executor.submit(get_pkg_info, pkg): pkg for pkg in all_pkgs}
for idx, future in enumerate(concurrent.futures.as_completed(future_to_pkg), 1):
pkg = future_to_pkg[future]
try:
pkg_infos[pkg] = future.result()
logging.info(f"[{idx}/{total}] Processed {pkg}")
except Exception as e:
logging.error(f"Error processing {pkg}: {e}")
pkg_infos[pkg] = {"Priority": "", "Section": "", "Description": ""}
# 写入 Markdown
out_path = "comp_result.md"
logging.info(f"Writing results to {out_path}")
with open(out_path, "w", encoding="utf-8") as out:
# 新表头:检查标记 -> Priority -> Section -> Description
out.write("| Package | Ubuntu | Zorin | Anduin | Priority | Section | Description |\n")
out.write("|---------|--------|-------|--------|----------|---------|-------------|\n")
for pkg in all_pkgs:
info = pkg_infos.get(pkg, {"Priority": "", "Section": "", "Description": ""})
mark = lambda name: "√" if pkg in sets[name] else ""
desc = info["Description"].replace("|", "\\|")
out.write(
f"| {pkg} | {mark('Ubuntu')} | {mark('Zorin')} | {mark('Anduin')} |"
f" {info['Priority']} | {info['Section']} | {desc} |\n"
)
elapsed = time.time() - start_time
logging.info(f"Completed in {elapsed:.2f}s")
if __name__ == "__main__":
main()
1 | #!/usr/bin/env python3 |
2 | # -*- coding: utf-8 -*- |
3 | """ |
4 | compare_manifests.py |
5 | |
6 | 读取三个 filesystem.manifest 文件,比较包的并集,并输出包含发行版勾选标记、Priority、Section、Description 的 Markdown 表格。 |
7 | 增加详细日志输出,并使用多线程加速 apt show 调用。 |
8 | 修复:自动剥离包名中的 ":<arch>" 后缀。 |
9 | """ |
10 | import sys |
11 | import subprocess |
12 | import logging |
13 | import concurrent.futures |
14 | import time |
15 | |
16 | # 日志配置 |
17 | logging.basicConfig( |
18 | level=logging.INFO, |
19 | format="%(asctime)s [%(levelname)s] %(message)s", |
20 | datefmt="%Y-%m-%d %H:%M:%S" |
21 | ) |
22 | |
23 | |
24 | def parse_manifest(path): |
25 | """ |
26 | 读取 manifest 文件,返回其中所有包名的集合。 |
27 | 自动剥离包名中的 ":<arch>" 后缀。 |
28 | """ |
29 | pkgs = set() |
30 | logging.info(f"Parsing manifest: {path}") |
31 | try: |
32 | with open(path, encoding='utf-8') as f: |
33 | for line in f: |
34 | line = line.strip() |
35 | if not line or line.startswith('#'): |
36 | continue |
37 | raw_pkg = line.split()[0] |
38 | pkg = raw_pkg.split(':', 1)[0] |
39 | pkgs.add(pkg) |
40 | logging.info(f"Found {len(pkgs)} unique packages in {path}") |
41 | except Exception as e: |
42 | logging.error(f"Error reading manifest {path}: {e}") |
43 | sys.exit(1) |
44 | return pkgs |
45 | |
46 | |
47 | def get_pkg_info(pkg): |
48 | """ |
49 | 调用 `apt show pkg`,解析 Priority、Section、Description(首段), |
50 | 返回 dict 包含信息。 |
51 | 若调用失败,返回空字段。 |
52 | """ |
53 | logging.debug(f"Fetching apt info for package: {pkg}") |
54 | try: |
55 | p = subprocess.run( |
56 | ["apt", "show", pkg], |
57 | stdout=subprocess.PIPE, |
58 | stderr=subprocess.DEVNULL, |
59 | text=True, |
60 | encoding='utf-8', |
61 | timeout=30 |
62 | ) |
63 | except subprocess.TimeoutExpired: |
64 | logging.warning(f"Timeout fetching info for {pkg}") |
65 | return {"Priority": "", "Section": "", "Description": ""} |
66 | |
67 | if p.returncode != 0: |
68 | logging.debug(f"apt show failed for {pkg}") |
69 | return {"Priority": "", "Section": "", "Description": ""} |
70 | |
71 | priority = "" |
72 | section = "" |
73 | desc_lines = [] |
74 | in_desc = False |
75 | |
76 | for line in p.stdout.splitlines(): |
77 | if in_desc: |
78 | if not line.strip(): |
79 | break |
80 | desc_lines.append(line.strip()) |
81 | else: |
82 | if line.startswith("Priority:"): |
83 | priority = line.split(":", 1)[1].strip() |
84 | elif line.startswith("Section:"): |
85 | section = line.split(":", 1)[1].strip() |
86 | elif line.startswith("Description:"): |
87 | desc = line.split(":", 1)[1].strip() |
88 | desc_lines.append(desc) |
89 | in_desc = True |
90 | |
91 | full_desc = " ".join(desc_lines) |
92 | logging.debug(f"Obtained info for {pkg}: priority={priority}, section={section}") |
93 | return {"Priority": priority, "Section": section, "Description": full_desc} |
94 | |
95 | |
96 | def main(): |
97 | if len(sys.argv) != 4: |
98 | print(f"Usage: {sys.argv[0]} <ubuntu_manifest> <zorin_manifest> <anduin_manifest>") |
99 | sys.exit(1) |
100 | |
101 | start_time = time.time() |
102 | paths = { |
103 | "Ubuntu": sys.argv[1], |
104 | "Zorin": sys.argv[2], |
105 | "Anduin": sys.argv[3], |
106 | } |
107 | |
108 | # 解析 manifests |
109 | sets = {name: parse_manifest(path) for name, path in paths.items()} |
110 | all_pkgs = sorted(set.union(*sets.values())) |
111 | total = len(all_pkgs) |
112 | logging.info(f"Total unique packages to process: {total}") |
113 | |
114 | # 多线程获取 apt 信息 |
115 | pkg_infos = {} |
116 | max_workers = min(10, total) |
117 | logging.info(f"Starting ThreadPoolExecutor with {max_workers} workers") |
118 | |
119 | with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: |
120 | future_to_pkg = {executor.submit(get_pkg_info, pkg): pkg for pkg in all_pkgs} |
121 | for idx, future in enumerate(concurrent.futures.as_completed(future_to_pkg), 1): |
122 | pkg = future_to_pkg[future] |
123 | try: |
124 | pkg_infos[pkg] = future.result() |
125 | logging.info(f"[{idx}/{total}] Processed {pkg}") |
126 | except Exception as e: |
127 | logging.error(f"Error processing {pkg}: {e}") |
128 | pkg_infos[pkg] = {"Priority": "", "Section": "", "Description": ""} |
129 | |
130 | # 写入 Markdown |
131 | out_path = "comp_result.md" |
132 | logging.info(f"Writing results to {out_path}") |
133 | with open(out_path, "w", encoding="utf-8") as out: |
134 | # 新表头:检查标记 -> Priority -> Section -> Description |
135 | out.write("| Package | Ubuntu | Zorin | Anduin | Priority | Section | Description |\n") |
136 | out.write("|---------|--------|-------|--------|----------|---------|-------------|\n") |
137 | |
138 | for pkg in all_pkgs: |
139 | info = pkg_infos.get(pkg, {"Priority": "", "Section": "", "Description": ""}) |
140 | mark = lambda name: "√" if pkg in sets[name] else "" |
141 | desc = info["Description"].replace("|", "\\|") |
142 | out.write( |
143 | f"| {pkg} | {mark('Ubuntu')} | {mark('Zorin')} | {mark('Anduin')} |" |
144 | f" {info['Priority']} | {info['Section']} | {desc} |\n" |
145 | ) |
146 | |
147 | elapsed = time.time() - start_time |
148 | logging.info(f"Completed in {elapsed:.2f}s") |
149 | |
150 | if __name__ == "__main__": |
151 | main() |
152 |