RUN.sh
· 394 B · Bash
Surowy
# 保存脚本为 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
Surowy
#!/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 |