{"id":65,"date":"2026-04-15T22:59:33","date_gmt":"2026-04-15T14:59:33","guid":{"rendered":"https:\/\/ichinoseyy.cn\/?p=65"},"modified":"2026-04-15T22:59:35","modified_gmt":"2026-04-15T14:59:35","slug":"%e4%b8%80%e4%b8%aa%e6%a3%80%e7%b4%a2%e8%bd%af%e4%bb%b6%ef%bc%88%e9%80%82%e7%94%a8%e4%ba%8elinuxwindows%ef%bc%89%e8%bd%bb%e9%87%8f","status":"publish","type":"post","link":"https:\/\/ichinoseyy.cn\/index.php\/2026\/04\/15\/%e4%b8%80%e4%b8%aa%e6%a3%80%e7%b4%a2%e8%bd%af%e4%bb%b6%ef%bc%88%e9%80%82%e7%94%a8%e4%ba%8elinuxwindows%ef%bc%89%e8%bd%bb%e9%87%8f\/","title":{"rendered":"\u4e00\u4e2a\u68c0\u7d22\u8f6f\u4ef6\uff08\u9002\u7528\u4e8elinux,windows\uff09\u8f7b\u91cf"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\n# -*- coding: utf-8 -*-\n\n\"\"\"\n\u8f7b\u91cf\u672c\u5730\u6587\u4ef6\u7d22\u5f15\/\u68c0\u7d22\uff08Windows + Linux\uff09\n\n\u7528\u6cd5:\n  python ff.py scan &lt;path1> &#91;path2 ...] &#91;--full]\n  python ff.py search &#91;\u5173\u952e\u8bcd] &#91;-c \u5206\u7c7b] &#91;-e \u6269\u5c55\u540d] &#91;-l \u6570\u91cf]\n  python ff.py stats\n  python ff.py clean\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport os\nimport sqlite3\nimport sys\nfrom datetime import datetime\n\n\nCATEGORY_MAP = {\n    \"\u6587\u6863\": &#91;\".pdf\", \".doc\", \".docx\", \".txt\", \".md\", \".xls\", \".xlsx\", \".ppt\", \".pptx\"],\n    \"\u56fe\u7247\": &#91;\".jpg\", \".jpeg\", \".png\", \".gif\", \".bmp\", \".svg\", \".webp\"],\n    \"\u89c6\u9891\": &#91;\".mp4\", \".avi\", \".mkv\", \".mov\", \".wmv\", \".flv\"],\n    \"\u97f3\u9891\": &#91;\".mp3\", \".wav\", \".flac\", \".aac\", \".ogg\"],\n    \"\u538b\u7f29\u5305\": &#91;\".zip\", \".rar\", \".7z\", \".tar\", \".gz\", \".bz2\"],\n    \"\u4ee3\u7801\": &#91;\".py\", \".js\", \".html\", \".css\", \".cpp\", \".c\", \".java\", \".go\", \".rs\", \".sh\"],\n    \"\u53ef\u6267\u884c\": &#91;\".exe\", \".msi\", \".deb\", \".rpm\", \".sh\", \".bat\", \".cmd\"],\n    \"CAD\u56fe\u7eb8\": &#91;\".dwg\", \".dxf\", \".stp\", \".step\", \".igs\", \".iges\"],\n    \"Unity\u5de5\u7a0b\": &#91;\".unity\", \".prefab\", \".asset\", \".mat\", \".controller\", \".anim\"],\n}\n\nEXCLUDE_DIRS = {\n    \"AppData\",\n    \".cache\",\n    \"node_modules\",\n    \"__pycache__\",\n    \"System Volume Information\",\n    \"$RECYCLE.BIN\",\n    \"lost+found\",\n    \".git\",\n    \".svn\",\n    \"tmp\",\n    \"temp\",\n    \"cache\",\n}\n\nWIN_SYSTEM_DIRS = {\"Windows\", \"Program Files\", \"Program Files (x86)\", \"ProgramData\", \"System32\", \"SysWOW64\"}\nLINUX_SYSTEM_PREFIXES = (\"\/bin\", \"\/boot\", \"\/dev\", \"\/etc\", \"\/lib\", \"\/proc\", \"\/sys\", \"\/usr\", \"\/var\")\n\nBASE_DIR = os.path.dirname(os.path.abspath(__file__))\nDB_PATH = os.path.join(BASE_DIR, \"file_index.db\")\nEXT_TO_CATEGORY = {ext.lower(): cat for cat, exts in CATEGORY_MAP.items() for ext in exts}\n\n\ndef init_db() -> None:\n    with sqlite3.connect(DB_PATH) as conn:\n        conn.execute(\n            \"\"\"\n            CREATE TABLE IF NOT EXISTS files (\n                path TEXT PRIMARY KEY,\n                name TEXT NOT NULL,\n                ext TEXT,\n                size INTEGER,\n                mtime REAL,\n                category TEXT\n            )\n            \"\"\"\n        )\n        conn.execute(\"CREATE INDEX IF NOT EXISTS idx_files_name ON files(name)\")\n        conn.execute(\"CREATE INDEX IF NOT EXISTS idx_files_ext ON files(ext)\")\n        conn.execute(\"CREATE INDEX IF NOT EXISTS idx_files_category ON files(category)\")\n        conn.execute(\"CREATE INDEX IF NOT EXISTS idx_files_mtime ON files(mtime)\")\n\n\ndef classify(ext: str) -> str:\n    return EXT_TO_CATEGORY.get(ext.lower(), \"\u5176\u4ed6\")\n\n\ndef format_size(size: int) -> str:\n    value = float(size)\n    for unit in (\"B\", \"KB\", \"MB\", \"GB\"):\n        if value &lt; 1024:\n            return f\"{value:.2f} {unit}\"\n        value \/= 1024\n    return f\"{value:.2f} TB\"\n\n\ndef should_skip_root(path: str) -> bool:\n    abs_path = os.path.abspath(path)\n    if sys.platform.startswith(\"win\"):\n        parts = {p.lower() for p in abs_path.split(os.sep) if p}\n        return any(item.lower() in parts for item in WIN_SYSTEM_DIRS)\n    return any(abs_path == p or abs_path.startswith(p + os.sep) for p in LINUX_SYSTEM_PREFIXES)\n\n\ndef iter_files(root_path: str):\n    for current, dirs, files in os.walk(root_path, topdown=True):\n        dirs&#91;:] = &#91;d for d in dirs if d not in EXCLUDE_DIRS and not d.startswith(\".\")]\n        if sys.platform.startswith(\"win\"):\n            dirs&#91;:] = &#91;d for d in dirs if d not in WIN_SYSTEM_DIRS]\n        for name in files:\n            yield os.path.join(current, name), name\n\n\ndef scan(paths: list&#91;str], full_scan: bool = False) -> None:\n    init_db()\n    checked = 0\n    updated = 0\n\n    with sqlite3.connect(DB_PATH) as conn:\n        conn.execute(\"PRAGMA synchronous=OFF\")\n        conn.execute(\"PRAGMA journal_mode=MEMORY\")\n\n        for root in paths:\n            if not os.path.exists(root):\n                print(f\"\u8def\u5f84\u4e0d\u5b58\u5728: {root}\")\n                continue\n            if should_skip_root(root):\n                print(f\"\u8df3\u8fc7\u7cfb\u7edf\u76ee\u5f55: {root}\")\n                continue\n\n            root = os.path.abspath(root)\n            print(f\"\u626b\u63cf: {root}\")\n            for file_path, name in iter_files(root):\n                checked += 1\n                try:\n                    st = os.stat(file_path)\n                except OSError:\n                    continue\n\n                ext = os.path.splitext(name)&#91;1].lower()\n                if not full_scan:\n                    old = conn.execute(\"SELECT size, mtime FROM files WHERE path=?\", (file_path,)).fetchone()\n                    if old and old&#91;0] == st.st_size and old&#91;1] == st.st_mtime:\n                        continue\n\n                conn.execute(\n                    \"\"\"\n                    INSERT OR REPLACE INTO files (path, name, ext, size, mtime, category)\n                    VALUES (?, ?, ?, ?, ?, ?)\n                    \"\"\",\n                    (file_path, name, ext, st.st_size, st.st_mtime, classify(ext)),\n                )\n                updated += 1\n\n        conn.commit()\n        conn.execute(\"PRAGMA synchronous=FULL\")\n\n    print(f\"\u5b8c\u6210: \u626b\u63cf {checked} \u4e2a\u6587\u4ef6\uff0c\u66f4\u65b0 {updated} \u6761\u8bb0\u5f55\")\n\n\ndef search(keyword: str = \"\", category: str | None = None, ext: str | None = None, limit: int = 50) -> None:\n    init_db()\n\n    sql = \"SELECT path, name, size, mtime, category FROM files WHERE 1=1\"\n    params: list&#91;object] = &#91;]\n\n    if keyword:\n        sql += \" AND (name LIKE ? OR path LIKE ?)\"\n        kw = f\"%{keyword}%\"\n        params.extend(&#91;kw, kw])\n    if category:\n        sql += \" AND category=?\"\n        params.append(category)\n    if ext:\n        normalized_ext = ext.lower() if ext.startswith(\".\") else f\".{ext.lower()}\"\n        sql += \" AND ext=?\"\n        params.append(normalized_ext)\n\n    sql += \" ORDER BY mtime DESC LIMIT ?\"\n    params.append(max(1, limit))\n\n    with sqlite3.connect(DB_PATH) as conn:\n        rows = conn.execute(sql, params).fetchall()\n\n    if not rows:\n        print(\"\u6ca1\u6709\u627e\u5230\u5339\u914d\u6587\u4ef6\")\n        return\n\n    print(f\"\u627e\u5230 {len(rows)} \u4e2a\u6587\u4ef6\")\n    print(f\"{'\u6587\u4ef6\u540d':&lt;36} {'\u5927\u5c0f':&lt;10} {'\u4fee\u6539\u65f6\u95f4':&lt;16} {'\u5206\u7c7b':&lt;8}\")\n    print(\"-\" * 90)\n    for path, name, size, mtime, cat in rows:\n        t = datetime.fromtimestamp(mtime).strftime(\"%Y-%m-%d %H:%M\")\n        short_name = name if len(name) &lt;= 34 else name&#91;:31] + \"...\"\n        print(f\"{short_name:&lt;36} {format_size(size):&lt;10} {t:&lt;16} {cat:&lt;8}\")\n        print(path)\n\n\ndef stats() -> None:\n    init_db()\n    with sqlite3.connect(DB_PATH) as conn:\n        total = conn.execute(\"SELECT COUNT(*) FROM files\").fetchone()&#91;0]\n        total_size = conn.execute(\"SELECT COALESCE(SUM(size), 0) FROM files\").fetchone()&#91;0]\n        categories = conn.execute(\n            \"SELECT category, COUNT(*) FROM files GROUP BY category ORDER BY COUNT(*) DESC\"\n        ).fetchall()\n\n    print(f\"\u7d22\u5f15\u6587\u4ef6\u603b\u6570: {total}\")\n    print(f\"\u603b\u5927\u5c0f: {format_size(total_size)}\")\n    if categories:\n        print(\"\u5206\u7c7b\u7edf\u8ba1:\")\n        for cat, count in categories:\n            print(f\"  {cat}: {count}\")\n\n\ndef clean() -> None:\n    init_db()\n    with sqlite3.connect(DB_PATH) as conn:\n        all_paths = &#91;row&#91;0] for row in conn.execute(\"SELECT path FROM files\").fetchall()]\n        missing = &#91;p for p in all_paths if not os.path.exists(p)]\n        if not missing:\n            print(\"\u6ca1\u6709\u65e0\u6548\u8bb0\u5f55\")\n            return\n        conn.executemany(\"DELETE FROM files WHERE path=?\", &#91;(p,) for p in missing])\n        conn.commit()\n    print(f\"\u5df2\u6e05\u7406 {len(missing)} \u6761\u65e0\u6548\u8bb0\u5f55\")\n\n\ndef build_parser() -> argparse.ArgumentParser:\n    parser = argparse.ArgumentParser(description=\"\u8f7b\u91cf\u672c\u5730\u6587\u4ef6\u68c0\u7d22\u5de5\u5177\")\n    sub = parser.add_subparsers(dest=\"cmd\", required=True)\n\n    p_scan = sub.add_parser(\"scan\", help=\"\u626b\u63cf\u76ee\u5f55\u5e76\u5efa\u7acb\u7d22\u5f15\")\n    p_scan.add_argument(\"paths\", nargs=\"+\", help=\"\u8981\u626b\u63cf\u7684\u76ee\u5f55\u8def\u5f84\")\n    p_scan.add_argument(\"--full\", action=\"store_true\", help=\"\u5168\u91cf\u626b\u63cf\uff08\u5ffd\u7565\u589e\u91cf\u5224\u65ad\uff09\")\n\n    p_search = sub.add_parser(\"search\", help=\"\u641c\u7d22\u6587\u4ef6\")\n    p_search.add_argument(\"keyword\", nargs=\"?\", default=\"\", help=\"\u6587\u4ef6\u540d\/\u8def\u5f84\u5173\u952e\u8bcd\")\n    p_search.add_argument(\"-c\", \"--category\", help=\"\u5206\u7c7b\uff08\u5982 \u6587\u6863\/\u56fe\u7247\/\u89c6\u9891\uff09\")\n    p_search.add_argument(\"-e\", \"--ext\", help=\"\u6269\u5c55\u540d\uff08\u5982 pdf \u6216 .pdf\uff09\")\n    p_search.add_argument(\"-l\", \"--limit\", type=int, default=50, help=\"\u8fd4\u56de\u6570\u91cf\u4e0a\u9650\")\n\n    sub.add_parser(\"stats\", help=\"\u67e5\u770b\u7d22\u5f15\u7edf\u8ba1\")\n    sub.add_parser(\"clean\", help=\"\u6e05\u7406\u5df2\u4e0d\u5b58\u5728\u6587\u4ef6\u7684\u7d22\u5f15\u8bb0\u5f55\")\n    return parser\n\n\ndef main() -> None:\n    args = build_parser().parse_args()\n    if args.cmd == \"scan\":\n        scan(args.paths, args.full)\n    elif args.cmd == \"search\":\n        search(args.keyword, args.category, args.ext, args.limit)\n    elif args.cmd == \"stats\":\n        stats()\n    elif args.cmd == \"clean\":\n        clean()\n\n\nif __name__ == \"__main__\":\n    main()\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-65","post","type-post","status-publish","format-standard","hentry","category-earn"],"_links":{"self":[{"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/posts\/65","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/comments?post=65"}],"version-history":[{"count":1,"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/posts\/65\/revisions"}],"predecessor-version":[{"id":66,"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/posts\/65\/revisions\/66"}],"wp:attachment":[{"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/media?parent=65"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/categories?post=65"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ichinoseyy.cn\/index.php\/wp-json\/wp\/v2\/tags?post=65"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}