Скрипт поиска одинаковых файлов

Обсуждение вопросов программирования (WSH/JScript/VBScript, CMD), проблемы и решения
Ответить
nhutils
Сообщения: 591
Зарегистрирован: 09 дек 2009, 18:08
Контактная информация:

Скрипт поиска одинаковых файлов

Сообщение nhutils »

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

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

Сканирование и подготовка списка файлов выполняются рекурсивно через соответствующие методы File System Object.

Код: Выделить всё

var fso = WScript.CreateObject("Scripting.FileSystemObject");
var files = new Array;
scan(path); 

function scan(path) {
  var folder = fso.GetFolder(path);
  for(var e = new Enumerator(folder.Files); !e.atEnd(); e.moveNext())
    if (e.item().Size >= min_file_size)
      files.push(e.item());
  for(var e = new Enumerator(folder.SubFolders); !e.atEnd(); e.moveNext())
    scan(e.item().Path);
}
В результате files содержит список файлов.
Теперь необходимо выбрать из списка файлы, которые имеют одинаковое содержимое. Для этого список файлов сортируется по размеру файлов:

Код: Выделить всё

function sort_by_size(a,b){
  if (a.size < b.size)
    return (-1);
  if (a.size > b.size)
    return 1;
  return 0;
}

files.sort(sort_by_size);
Далее производится сравнение содержимого файлов, имеющих одинаковый размер, с помощью стандартной программы FC.

Код: Выделить всё

  while (files.length > 1) {
    var count = 0;    
    var fc = 0;

    for (var i = 1; i < files.length && files[0].Size == files[i].Size;) {
      fc = shell.Run("fc /b \"" + 
        files[0].ParentFolder + "\\" + files[0].Name + "\" \"" + 
        files[i].ParentFolder + "\\" + files[i].Name + "\"",0,true);
      if (fc == 0) { // файлы одинаковые
        WScript.StdOut.WriteLine(files[i].ParentFolder + "\\" + files[i].Name);
        files.splice(i,1);
        ++count;
      } 
      else
        ++i;
    }
    if (count) { // найдены копии файла files[0]
      // выводим информацию о файлах или выполняем какие-то действия...
    }
    files.shift();  
  }
Достоинством скрипта является то, что он использует только стандартные компоненты (WSH, FC), т.е. может быть запущен на любом компьютере, не требуя установки дополнительного ПО.
Недостатком является небольшая скорость работы из-за многократных сравнений.
Также скрипт не проверяет файлы в архивах (ZIP и т.п.), что было бы полезно для выявления файлов, копии которых есть и в папках, и в архивах.

Далее приведён полный текст скрипта с некоторыми изменениями и дополнениями:
- путь для проверки берётся из первого параметра командной строки.
- добавлено ограничение на минимальный размер файла (чтобы не проверять мелкие файлы).
- сортировка списка сделана в обратном порядке (по убыванию размера файлов), чтобы вывод программы начинался с более интересных "тяжёлых" файлов; сортировку легко изменить, заменив вызов files.sort(sort_by_size_desc) на files.sort(sort_by_size_asc).
- добавлен подсчёт, сколько места в байтах занимают копии.

Код: Выделить всё

var path = WScript.Arguments(0);
var fso = WScript.CreateObject("Scripting.FileSystemObject");
var shell = WScript.CreateObject("WScript.Shell");
var min_file_size = 1024; // минимальный размер файла для проверки

var files = new Array;
WScript.StdOut.WriteLine("Поиск одинаковых файлов в " + path);  

scan(path);
WScript.StdOut.WriteLine("Выбрано файлов для сравнения " + files.length);  
process();
WScript.Quit(0);

function scan(path) {
  var folder = fso.GetFolder(path);
  for(var e = new Enumerator(folder.Files); !e.atEnd(); e.moveNext())
    if (e.item().Size >= min_file_size)
      files.push(e.item());
  for(var e = new Enumerator(folder.SubFolders); !e.atEnd(); e.moveNext())
    scan(e.item().Path);
}

// сравнивает по возрастанию размера файла
function sort_by_size_asc(a,b) {
  if (a.size < b.size)
    return (-1);
  if (a.size > b.size)
    return 1;
  return 0;
}

// сравнивает по убыванию размера файла
function sort_by_size_desc(a,b) {
  if (a.size > b.size)
    return (-1);
  if (a.size < b.size)
    return 1;
  return 0;
}

// обработка списка файлов
function process(){
  var total_count = 0, total_bytes = 0;
  files.sort(sort_by_size_desc);

  while (files.length > 1) {
    var count = 0;    
    var fc = 0;

    for (var i = 1; i < files.length && files[0].Size == files[i].Size;) {
      fc = shell.Run("fc /b \"" + 
        files[0].ParentFolder + "\\" + files[0].Name + "\" \"" + 
        files[i].ParentFolder + "\\" + files[i].Name + "\"",0,true);
      if (fc == 0) { // файлы одинаковые
        WScript.StdOut.WriteLine(files[i].ParentFolder + "\\" + files[i].Name);
        files.splice(i,1);
        ++count;
      } 
      else
        ++i;
    }
    if (count) { // найдены копии файла files[0]
      WScript.StdOut.WriteLine(files[0].ParentFolder + "\\" + files[0].Name);
      var extra_bytes = files[0].Size * count;
      WScript.StdOut.WriteLine("> " + ++count + " одинаковых файлов (" + 
        files[0].Size + " байт в каждом, " + 
        extra_bytes + " байт в копиях)");
      ++total_count;
     total_bytes += extra_bytes;
    }
    files.shift();  
  }
  WScript.StdOut.WriteLine("Всего " + total_bytes + " байт в " + 
    total_count + " копиях файлов");  
}
Этот скрипт необходимо запускать с указанием пути и с перенаправлением вывода в файл:

Код: Выделить всё

cscript.exe //nologo files.js \\fileserver\users > users_duplicates.txt
nhutils
Сообщения: 591
Зарегистрирован: 09 дек 2009, 18:08
Контактная информация:

Re: Скрипт поиска одинаковых файлов

Сообщение nhutils »

Найдена причина медленной работы скрипта. Оказалось, используемая для сравнения файлов программа fc из Windows имеет интересную особенность: в процессе сравнения файлов в binary режиме (с ключом /b) программа выводит все несовпадающие байты в сравниваемых файлах, примерно так:

Код: Выделить всё

D:\Temp>fc /b 1.dat 2.dat 
Comparing files 1.dat and 2.DAT
00000D40: A0 7E
00000D41: 05 00
00000D48: 2D 05
00000DC0: 73 27
00000DC1: 16 01
00000DC8: 09 01
Это хорошо, пока несовпадающих байтов мало, и программа выводит небольшой объём информации.

Но если сравниваемые файлы имеют размер побольше, мегабайты, десятки мегабайт, и различий в них много, то процесс сравнения превращается в побайтовый вывод содержимого мегабайтных файлов, в результате сравнение двух таких файлов может занимать аж до минуты! Соответственно, скрипт сравнения, многократно вызывающий FC для сравнения файлов, может тратить часы на обработку гигабайтной папки. К сожалению, вывод различий в FC отключить не удаётся, для побайтового сравнения такой возможности нет:

Код: Выделить всё

Compares two files or sets of files and displays the differences between 
them


FC [/A] [/C] [/L] [/LBn] [/N] [/OFF[LINE]] [/T] [/U] [/W] [/nnnn]
   [drive1:][path1]filename1 [drive2:][path2]filename2
FC /B [drive1:][path1]filename1 [drive2:][path2]filename2

  /A         Displays only first and last lines for each set of differences.
  /B         Performs a binary comparison.
  /C         Disregards the case of letters.
  /L         Compares files as ASCII text.
  /LBn       Sets the maximum consecutive mismatches to the specified
             number of lines.
  /N         Displays the line numbers on an ASCII comparison.
  /OFF[LINE] Do not skip files with offline attribute set.
  /T         Does not expand tabs to spaces.
  /U         Compare files as UNICODE text files.
  /W         Compresses white space (tabs and spaces) for comparison.
  /nnnn      Specifies the number of consecutive lines that must match
             after a mismatch.
  [drive1:][path1]filename1
             Specifies the first file or set of files to compare.
  [drive2:][path2]filename2
             Specifies the second file or set of files to compare.
Пришлось сделать небольшую программу для сравнения файлов в побайтовом режиме. Программа называется fcb.exe, и ей нужны только два аргумента - имена файлов для сравнения. Осталось только исправить скрипт в части вызова программы сравнения:

Код: Выделить всё

      fc = shell.Run("fcb \"" + 
        files[0].ParentFolder + "\\" + files[0].Name + "\" \"" + 
        files[i].ParentFolder + "\\" + files[i].Name + "\"",0,true);
С программой fcb.exe время поиска одинаковых файлов существенно уменьшилось.
Вложения
fcb.zip
(59.05 КБ) 567 скачиваний
Ответить