补给库的目录,是我自己搞乱的。

同一个文件三个备份,分别叫"最终版""最终版2""真的最终版";两个同名安装包大小差 200KB,我分不清哪个新。所以我写点东西,让它替我记着。

目标

扫目录、生成清单、顺手查重名。用 Kotlin,因为我的工具链本来就是它,处理文件集合写起来很舒服。

代码

import java.io.File
import java.security.MessageDigest

fun sha256(f: File): String {
    val d = MessageDigest.getInstance("SHA-256")
    f.inputStream().use { s ->
        val buf = ByteArray(8192)
        while (true) {
            val n = s.read(buf)
            if (n <= 0) break
            d.update(buf, 0, n)
        }
    }
    return d.digest().joinToString("") { "%02x".format(it) }
}

fun main(args: Array<String>) {
    val dryRun = "--dry-run" in args
    val root = File(args.firstOrNull { !it.startsWith("--") } ?: ".")

    val groups = root.walkTopDown()
        .filter { it.isFile }
        .groupBy { it.extension.lowercase().ifBlank { "none" } }
        .toSortedMap()

    println("# 补给库清单\n")
    groups.forEach { (ext, list) ->
        println("## ." + ext + " — " + list.size + " 个\n")
        list.sortedBy { it.name }.forEach { f ->
            println("- " + f.relativeTo(root) + " · " + f.length() / 1024 + " KB")
            if (dryRun) println("  - sha256 " + sha256(f).take(16))
        }
        println()
    }
}

为什么这么写

  • walkTopDown() 递归但不开线程,几千个文件完全够

  • groupBy 配 toSortedMap,扩展名就不会随机乱序

  • --dry-run 先只打印不动手——我吃过亏,这个开关永远先开

  • use { } 保证流被关掉。写脚本最容易漏的就是这个

跑起来

kotlinc check.kt -include-runtime -d check.jar
java -jar check.jar ~/补给库 --dry-run

还没做的

  • 按哈希找真正重复的文件(不只是同名),重叠的标出来,我手动删

  • 输出改成 Markdown 表格,直接贴进补给库页面

  • 跟上次清单对比,新增文件才提醒

一点想法

写脚本的快乐不在代码多漂亮,而在——从今往后这件事不归我管了。

我算过:手动整理一次半小时,一个月两次,一年十二小时。十二小时够我在公园坐四个下午。

给维修工的备注:带删除逻辑的脚本,先在副本目录跑。第一个把真实数据当测试数据的人,后来都成了谨慎的人。

—— 音葵

脚本还在跑,我去睡一会儿。醒了看看它有没有把「真的最终版」认成另一个文件。