Files
sfdupes/main.go
sneak 4b1c3cbf70
All checks were successful
check / check (push) Successful in 4s
Make scan paths required operands; add -x/--one-file-system
scan now takes one or more PATH operands (directories or regular
files) via cobra flags instead of the -root flag with its /srv
default; invoking scan with no operand is a usage error and a
nonexistent operand is fatal. Filesystem boundaries are crossed by
default; the new -x/--one-file-system flag (GNU du/rsync convention)
stops the walk at each operand's filesystem, implemented by comparing
lstat device IDs with build-tagged helpers for darwin's int32 Dev.
Verified against a real mounted disk image: default crosses, -x does
not, --one-file-system is identical to -x.
2026-07-23 08:55:17 +07:00

107 lines
2.9 KiB
Go

// Command sfdupes quickly identifies candidate duplicate files across
// very large filesystems without reading full file contents. Files are
// considered duplicates when they have identical size, identical SHA-256
// of their first 1024 bytes, and identical SHA-256 of their last 1024
// bytes.
//
// Usage:
//
// sfdupes scan [--workers N] [-x] PATH... > files.dat
// sfdupes report [files.dat|-] > dupes.tsv
// sfdupes trees [files.dat|-] > dupetrees.tsv
//
// See README.md for the complete specification.
package main
import (
"fmt"
"os"
"runtime"
"github.com/spf13/cobra"
)
// Exit codes: 0 is success (even with per-file warnings), exitFatal is
// a fatal error, exitUsage is a usage error.
const (
exitFatal = 1
exitUsage = 2
)
// Version is the build version, injected at link time via -ldflags
// (see the Makefile); "dev" for a plain go build.
//
//nolint:gochecknoglobals // written only by the linker
var Version = "dev"
func main() {
root := &cobra.Command{
Use: "sfdupes",
Short: "Find candidate duplicate files by size and head/tail SHA-256",
Version: Version,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
// A missing subcommand prints usage and exits 2.
_ = cmd.Usage()
os.Exit(exitUsage)
},
}
// Everything on stdout is machine-readable data; all human-facing
// output (help, usage, errors) goes to stderr.
root.SetOut(os.Stderr)
root.SetErr(os.Stderr)
root.CompletionOptions.DisableDefaultCmd = true
var (
scanWorkers int
scanOneFS bool
)
scanCmd := &cobra.Command{
Use: "scan [--workers N] [-x] PATH...",
Short: "Walk trees and emit one record per regular file on stdout",
Args: cobra.MinimumNArgs(1),
Run: func(_ *cobra.Command, args []string) {
runScan(args, scanWorkers, scanOneFS)
},
}
scanCmd.Flags().IntVar(&scanWorkers, "workers", runtime.NumCPU(),
"concurrent workers for the stat and hash passes")
scanCmd.Flags().BoolVarP(&scanOneFS, "one-file-system", "x", false,
"do not cross filesystem boundaries")
reportCmd := &cobra.Command{
Use: "report [files.dat|-]",
Short: "Read a scan stream and print the file-level duplicates report",
Args: cobra.MaximumNArgs(1),
Run: func(_ *cobra.Command, args []string) {
runReport(args)
},
}
treesCmd := &cobra.Command{
Use: "trees [files.dat|-]",
Short: "Read a scan stream and print the duplicate-tree report",
Args: cobra.MaximumNArgs(1),
Run: func(_ *cobra.Command, args []string) {
runTrees(args)
},
}
root.AddCommand(scanCmd, reportCmd, treesCmd)
err := root.Execute()
if err != nil {
// Cobra has already printed the error and usage to stderr;
// an invalid subcommand or bad arguments is a usage error.
os.Exit(exitUsage)
}
}
// fatalf reports a fatal error and exits 1.
func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "sfdupes: "+format+"\n", args...)
os.Exit(exitFatal)
}