Сравнить содержимое ~100 папок с подпапками

Есть одна папка (Папка0) с папками (Папки 1, 2, 3, ..., 100, ...), папок 100+ штук, в папках есть файлы и подпапки и их очень много.

Гугл не помог. Есть ли программа, которой можно сравнить их содержимое и получить список совпадений?

К примеру, у меня есть 3 папки с файлами и подпапками:

Папка 0 содержит:

Папка1 (Игнорирую название) содержит **ПапкуА** (**ФайлА**, ФайлБ), ПапкуБ(ФайлВ, ФайлГ) и **ФайлД** (Сравниваю структуру содержащуюся внутри папки 1 с содержимым папок 2, 3, ...)

Папка2 (Игнорирую название) содержит **ПапкуА** (**ФайлА**, ФайлЯ), ПапкуУ(ФайлЦ, ФайлЩ) и **ФайлД** (Сравниваю структуру содержащуюся внутри папки 2 с содержимым папок 3, 4, ...)

Папка3 (Игнорирую название) содержит **ПапкуА** (**ФайлА**) (Сравниваю структуру содержащуюся внутри папки 3 с содержимым папок 4, 5, ...)

На выходе я хочу получить какой то список с адресами вида:

ДискД://Папка0/Папка1/ПапкаА/ФайлА (ПОЛНЫЙ адрес чтобы я это смог найти)
ДискД://Папка0/Папка2/ПапкаА/ФайлА
ДискД://Папка0/Папка3/ПапкаА/ФайлА

ДискД://Папка0/Папка1/ФайлД
ДискД://Папка0/Папка2/ФайлД

Upd: Примерный алгоритм того как это должно работать:

  1. Берем список папок. Те 100+ папок, в сравнении названия этих 100 папок не учитываем, сравниваем только содержимое.
  2. Получаем типа полное дерево файлов в каждой из папок.
  3. Сравниваем деревья.
  4. Отобразить совпавшие адреса файлов. Если совпадений 3, выводим 3, если 100, выводим 100, и.т.п.

Upd2: Приведу пример еще проще. Я беру содержимое Папка1 и копирую в Папка2. Как винда копирует. Со слиянием папок. Если есть совпадения, винда выведет диалог о замене. Потом я беру содержимое которое уже общее папки1 и папки2 и копирую в Папка3. Опять винда копирует, если есть совпадения выводит диалог о замене. Вот такие совпадения которые винда БЫ обозначила как запрос на замену мне и нужны. Только с сохранением оригинальной структуры папок.


Ответы (4 шт):

Автор решения: ganz

Вот решение на питоне (http://python.org), заменить значения dir1 итд на нужные названия папок

dir1='c:/'
dir2='c:/'
dir3='c:/'
import filecmp
res12=filecmp.dircmp(dir1, dir2, ignore=None).common_files
res13=filecmp.dircmp(dir1,dir3).common_files
for it in res12:
  if it in res13:
     print(it)

если не ищет не корректно то нужно будет работать с os.walk

→ Ссылка
Автор решения: Gh0sTG0

Написал сам, писал на непопулярном здесь ahk.

    DBFileName := "D:\data.db"
    DB := New SQLiteDB
    If !DB.OpenDB(DBFileName) {
       MsgBox, 16, SQLite Error, % "Msg:`t" . DB.ErrorMsg . "`nCode:`t" . DB.ErrorCode
    }


    SQL := "DELETE FROM file_structure;" ; обнулил специально созданную под это дело таблицу в бд
    If !DB.Exec(SQL)
    {
            MsgBox, 16, SQLite Error, % "Msg:`t" . DB.ErrorMsg . "`nCode:`t" . DB.ErrorCode
    }


    yourFolder = D:\folder_way
    Loop, % yourFolder "\*", 0, 1
    {
        ToolTip, %A_Index%
        full_adr := A_LoopFileFullPath
        inter_adr := SubStr(full_adr, InStr(full_adr, "\",,, 7) + 1) ; определил правую часть адреса.
        StringReplace, inter_adr, inter_adr, ', '', All
        StringReplace, full_adr, full_adr, ', '', All
        SQL := "INSERT INTO file_structure VALUES('" . full_adr . "', '" . inter_adr . "');"
        If !DB.Exec(SQL)
            MsgBox, 16, SQLite Error, % "Msg:`t" . DB.ErrorMsg . "`nCode:`t" . DB.ErrorCode
    }


    SQL := 
    (
"SELECT a.*
FROM file_structure a
JOIN (SELECT full_adr, inter_adr, COUNT(*)
FROM file_structure 
GROUP BY inter_adr
HAVING count(*) > 1 ) b
ON a.inter_adr = b.inter_adr
ORDER BY a.inter_adr"
    )

    Result := ""
    If !DB.GetTable(SQL, Result)
        MsgBox, 16, SQLite Error: GetTable, % "Msg:`t" . DB.ErrorMsg . "`nCode:`t" . DB.ErrorCode
    Else
    {
        oldCheck := ""
        newCheck := ""
        Excel := ComObjActive("Excel.Application") ; вставляю в эксель для дальнейшей ручной обработки.
        Cell := Excel.Cells(Excel.Rows.Count, 1).End(-4162).Offset(1, 0)
        index = 0
        For Each, Row In Result.Rows
        {
            IfWinActive, ahk_exe EXCEL.EXE
            {
                ToolTip, %A_Index%
                newCheck := Row[2] ; newCheck и oldCheck являются правой частью адреса

                if (newCheck <> oldCheck)
                {
                    index += 2 ; если новый файл не совпадает с предыдущим делаю пропуск
                }
                oldCheck := newCheck

                Cell.Offset(index, 0).Value := Row[1]
                index += 1
            } else {
                MsgBox, ERROR!!!
            }
        }
        Excel := Cell := ""
    }

    DB.CloseDB()
→ Ссылка
Автор решения: Дмитрий

Если устравивает решение на джаве, то это выглядит примерно так (в агрументы метода main передаете список корневых каталогов, которые хотите проверить) :

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {

    public static void main(String[] args) {
        
        for (String rootDirPath : args) {

            System.out.println("===================" + rootDirPath + "===================");

            Map<String, Set<String>> pathMap = new HashMap<>();
            for (File file : new File(rootDirPath).listFiles()) {
                final int len = file.isDirectory() ? file.getName().length() + 1 : 0;
                Map<String, Set<String>> collect = listFiles(file).stream()
                        .map(f -> f.getPath().substring(rootDirPath.length() + len + 1, f.getPath().length()))
                        .collect(Collectors.groupingBy(f -> file.getName(),
                                Collectors.mapping(Function.identity(), Collectors.toSet())));
                pathMap.putAll(collect);
            }

            Set<String> pathSet = new HashSet<>();
            for (Set<String> set : pathMap.values()) pathSet.addAll(set);
            for (String path : pathSet) print(getPath(path, pathMap));

        }

    }

    private static void print(List<String> result) {
        if (result.size() < 2) return;
        result.forEach(System.out::println);
        System.out.println("***********************************************");
    }

    private static List<String> getPath(String path, Map<String, Set<String>> pathMap) {
        List<String> result = new ArrayList<>();
        for (String p : pathMap.keySet()) {
            if (pathMap.get(p).contains(path)) result.add(p + File.separator + path);
        }
        return result;
    }

    private static List<File> listFiles(File file) {
        final List<File> fileList = new ArrayList<>();
        if (file.isDirectory()) {
            for (File includeFile : file.listFiles()) {
                fileList.addAll(listFiles(includeFile));
            }
        }
        else fileList.add(file);
        return fileList;
    }

}
→ Ссылка
Автор решения: Zombotron

Вообще, подобные задачи нужно решать способом выявления неуникальных имен путем установки их ключами ассоциативной структуры данных: ассоциативный массив, список, словарь, таблица в БД или табличном редакторе с одним столбиком уникальных ключей.

В общем, "слева" ключи (имена файлов), "справа" - список, массив из путей или строка через разделитель.

Алгоритм такой:

  • Проходим все файлы всех папок.
  • Создаем запись в ассоциативной структуре с ключом == имени файла, если такой записи нет.
  • Добавляем в значение путь к файлу.

По итогу имеем структуру вида:

{
  ['filename1' => ['path1']],
  ['filename2' => ['path2','path3','path6']],
  ['filename3' => ['path1','path5','path110',...]],
  ['filename4' => ['path2','path7','path200',...]],
  ...
}

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

Сей алгоритм позволит вывести все совпадения за <= 2n итераций, где n - кол-во файлов во всех папках.

→ Ссылка