Программа искать повторяющиеся файлы в директории (рекурсивно)


Это мой первый идти программа. Я учу язык, но это немного сложно разобраться во всех понятиях, так что на практике я это написал. Это простая программа, которая рекурсивно проверить дублированные файлы в каталоге.

Он использует SHA256 хэш файлов для того, чтобы определить, если два файла имеют одинаковые или нет. Я наплодил несколько рабочих, чтобы справиться с этим хеширования.

Вот как это работает:

  • Н рабочие (горутины) породил, каждая из них ждет путь к файлам процесса на том же канале, по имени input в моем коде.
  • 1 горутина выметывается для рекурсивного поиска файлов в direvtory, и заполнить input канал с именами файлов.
  • Основной процесс горутина результаты как только они будут доступны, и добавить их в карту и SHA256->[файл, файл, ...].

Наконец, мы просто отобразить дубликаты.

Пожалуйста чувствуйте что-либо комментировать, я действительно хочу, чтобы прогресс в ходу, и особенно "идиоматических" иди.

Изменения: улучшено мой первоначальный код с флагами и управления ошибками.

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "os"
    "path/filepath"
    "sync"
    "flag"
    "runtime"
    "io"
)

var dir string
var workers int

type Result struct {
    file   string
    sha256 [32]byte
}

func worker(input chan string, results chan<- *Result, wg *sync.WaitGroup) {
    for file := range input {
        var h = sha256.New()
        var sum [32]byte
        f, err := os.Open(file)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            continue
        }
        if _, err = io.Copy(h, f); err != nil {
            fmt.Fprintln(os.Stderr, err)
            f.Close()
            continue
        }
        f.Close()
        copy(sum[:], h.Sum(nil))
        results <- &Result{
            file:   file,
            sha256: sum,
        }
    }
    wg.Done()
}

func search(input chan string) {
    filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
        } else if info.Mode().IsRegular() {
            input <- path
        }
        return nil
    })
    close(input)
}

func main() {

    flag.StringVar(&dir, "dir", ".", "directory to search")
    flag.IntVar(&workers, "workers", runtime.NumCPU(), "number of workers")
    flag.Parse()

    fmt.Printf("Searching in %s using %d workers...\n", dir, workers)

    input := make(chan string)
    results := make(chan *Result)

    wg := sync.WaitGroup{}
    wg.Add(workers)

    for i := 0; i < workers; i++ {
        go worker(input, results, &wg)
    }

    go search(input)
    go func() {
        wg.Wait()
        close(results)
    }()

    counter := make(map[[32]byte][]string)
    for result := range results {
        counter[result.sha256] = append(counter[result.sha256], result.file)
    }

    for sha, files := range counter {
        if len(files) > 1 {
            fmt.Printf("Found %d duplicates for %s: \n", len(files), hex.EncodeToString(sha[:]))
            for _, f := range files {
                fmt.Println("-> ", f)
            }
        }
    }

}


399
2
задан 10 февраля 2018 в 12:02 Источник Поделиться
Комментарии
1 ответ

1. объявить всех "переменных" сразу

вместо

var dir string
var workers int

вы можете сделать

var (
dir string
workers int
)

или даже лучше, использовать местные ВАР, а не глобальный ВАР irectly в main() функция

dir := flag.String("dir", ".", "directory to search")
workers := flag.Int("workers", runtime.NumCPU(), "number of workers")

2. Убедитесь, что рассуждения верны

если worker это <= 0, то программа будет паника. Небольшая проверка после flag.Parse() может предотвратить это:

if workers <= 0 {
fmt.Printf("workers has to be > 0, was %d", workers)
}

3. Улучшить хэш вычислений:

Во-первых, каждого работника нужно только один экземпляр хэш.Хэш, как вы можете называть Reset() на нем после каждого файла:

h := sha256.New()
for file := range input {
...
results <- &Result{...}
h.Reset()
}

Кроме того, хэш каждого файла может быть сохранен как string вместо [32]byte чтобы избежать некоторых операций:

results <- &Result{
file: file,
sha256: fmt.Sprintf("%x", h.Sum(nil)),
}

4. Всегда указывать направление канала, когда вы можете

От golang спецификации:


Канал обеспечивает механизм для одновременного выполнения функций
для отправки и получения значений из указанного элемента
тип. Значение неинициализированной канала равна нулю.

ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

Факультативный <- оператор задает направление канала, отправить или
получите. Если нет направления, то канал является двунаправленным. А
канал может быть ограничен только отправить или получить только по
преобразование или назначение.

Укажите направление канала помогает понять, что делает метод


Вот новая версия кода:

package main

import (
"crypto/sha256"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"sync"
)

type Result struct {
file string
sha256 string
}

func search(dir string, input chan<- string) {
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else if info.Mode().IsRegular() {
input <- path
}
return nil
})
close(input)
}

func startWorker(input <-chan string, results chan<- *Result, wg *sync.WaitGroup) {
h := sha256.New()
for file := range input {
f, err := os.Open("file.txt")
if err != nil {
fmt.Fprintln(os.Stderr, err)
continue
}
if _, err := io.Copy(h, f); err != nil {
fmt.Fprintln(os.Stderr, err)
f.Close()
continue
}
f.Close()
results <- &Result{
file: file,
sha256: fmt.Sprintf("%x", h.Sum(nil)),
}
h.Reset()
}
wg.Done()
}

func run(dir string, workers int) (map[string][]string, error) {

input := make(chan string)
go search(dir, input)

counter := make(map[string][]string)
results := make(chan *Result)
go func() {
for r := range results {
counter[r.sha256] = append(counter[r.sha256], r.file)
}
}()

var wg sync.WaitGroup
wg.Add(workers)
for i := 0; i < workers; i++ {
go startWorker(input, results, &wg)
}
wg.Wait()
close(results)

return counter, nil
}

func main() {

dir := flag.String("dir", ".", "directory to search")
workers := flag.Int("workers", runtime.NumCPU(), "number of workers")
flag.Parse()

if *workers <= 0 {
fmt.Printf("workers has to be > 0, was %d \n", workers)
os.Exit(1)
}
fmt.Printf("Searching in %s using %d workers...\n", *dir, *workers)

counter, err := run(*dir, *workers)
if err != nil {
fmt.Printf("failed! %v\n", err)
os.Exit(1)
}

for sha, files := range counter {
if len(files) > 1 {
fmt.Printf("Found %d duplicates for %v: \n", len(files), sha)
for _, f := range files {
fmt.Println("-> ", f)
}
}
}
}


Возможные улучшения:

В настоящее время, если выдается ошибка где-то в коде, программа не останавливается, а просто написать об ошибке os.Stderr. Может быть лучше вернуть эту ошибку, а затем вызвать os.Exit(1)

6
ответ дан 14 февраля 2018 в 12:02 Источник Поделиться