Тема: JScript: расширенная функция поиска и фильтрации файлов по шаблону
Выношу на обсуждение коллег функцию поиска файлов.
Функция понимает следующие параметры, задаваемые в виде объекта-списка ключей:
-- path - строка, определяет каталог, где поиск поиска
-- pattern - строка/массив строк шаблонов поиска
-- included - строка/массив строк шаблонов для фильтрации требуемых файлов
-- excluded - строка/массив строк шаблонов для "отбраковки" ненужных файлов
-- fiolders - логическое, задает поиск каталогов вместо файлов
-- recursive -- логическое, задает поиск в глубину для всех подкатлогов данного каталога
-- filter - функция, которая вызывается для более тонкой фильтрации файлов. Функция принимает имя файла и возвращает true если файл следует сохранить в результирующем файле, иначе - false
-- codepage - строка, при необходимости задает другую кодовую страницу для команды DOS. Идея заимствована из поста #12 коллеги wisgest в теме JS: Быстрый поиск файлов с заданными расширениями
Все параметры необязательны. При вызове функции без параметров по умолчанию производится поиск всех файлов в текущем каталоге.
Функция поиска реализована в виде статичного метода объекта FileSystem. При реализации функции использовались некоторые идеи из вики-статьи http://en.wikipedia.org/wiki/Glob_(programming).
var FileSystem = FileSystem || {};
FileSystem.find = function(options)
{
options = options || {};
// Normalize a path
var path = (new ActiveXObject('Scripting.FileSystemObject')).GetAbsolutePathName(options.path);
if ( path.slice(-2) != ':\\' ) {
path += '\\';
}
// Set auxiliary parameters:
// f - find folders only
var f;
if ( options.folders ) {
f = ' /ad ';
} else {
f = ' /a-d ';
}
// p - path to an object of FS
// s - recursive search
// b - left border of a pattern
var p;
var s;
var b;
if ( options.recursive ) {
p = '';
s = ' /s ';
b = ' \\\\';
} else {
p = path;
s = '';
b = ' ^';
}
// Construct the shell command that would be executed
var cmd = '';
if ( options.codepage ) {
cmd += 'cmd /c chcp ' + options.codepage + ' && ';
}
cmd += 'cmd /c dir /b ' + f + s + '"' + path + [].concat(options.pattern || '*').join('" "' + path) + '"';
if ( options.included ) {
cmd += ' | findstr /i /e "' + b + FileSystem.wildcard2regex(options.included, true, true).join(b) + '"';
}
if ( options.excluded ) {
cmd += ' | findstr /v /i /e "' + b + FileSystem.wildcard2regex(options.excluded, true, true).join(b) + '"';
}
// Storing the command for debugging reasons
arguments.callee.cmd = cmd;
// Perform the shell command ...
var sh = new ActiveXObject('WScript.Shell');
var ex = sh.Exec(cmd);
// ... and collect each string from the output to the resulting array
var result = [];
while ( ex.Status == 0 ) {
if ( ex.StdOut.AtEndOfStream ) {
break;
}
result.push(p + ex.StdOut.ReadLine());
}
while ( ! ex.StdOut.AtEndOfStream ) {
result.push(p + ex.StdOut.ReadLine());
}
if ( typeof options.filter != 'function' ) {
return result;
}
// Perform the filtering function
var filtered = [];
for (var i = 0; i < result.length; i++) {
if ( options.filter(result[i], i, result) ) {
filtered.push(result[i]);
}
}
return filtered;
};
FileSystem.wildcard2regex = function(wildcard, skipRegexp, strictly)
{
var convert = arguments.callee[strictly ? 'strictly' : 'std'];
var result = [];
if ( Object.prototype.toString.call(wildcard) == '[object String]' ) {
result.push(convert(wildcard));
} else {
for (var i = 0; i < wildcard.length; i++) {
result.push(convert(wildcard[i]));
}
}
return skipRegexp
? result
: new RegExp('^(' + result.join('|') + ')$', 'i');
};
FileSystem.wildcard2regex.strictly = function(wildcard)
{
var result = wildcard
.replace(/([\^\$\\\/\|\.\+\!\[\]\(\)\{\}])/g, '\\$1')
.replace(/\?/g, '[^\\\\]?')
.replace(/\*/g, '[^\\\\]*')
;
return result;
};
FileSystem.wildcard2regex.std = function(wildcard)
{
var result = wildcard
.replace(/([\^\$\\\/\|\.\+\!\[\]\(\)\{\}])/g, '\\$1')
.replace(/\?/g, '.?')
.replace(/\*/g, '.*?')
;
return result;
};.
Примеры использования.
1. Поиск всех файлов в текущем каталоге
var f = FileSystem.find();.
2. Поиск заданных файлов в указанном каталоге
вариант 1
var options = {
path: 'C:\\Windows\\System32',
pattern: 'z*'
};
var f = FileSystem.find(options);.
вариант 2
var options = {
path: 'C:\\Windows\\System32',
pattern: '*',
included: 'z*'
};
var f = FileSystem.find(options);.
вариант 3
var options = {
path: 'C:\\Windows\\System32',
filter: function(filename) { return filename.match(/z[^\\]*]$/i); }
};
var f = FileSystem.find(options);.
3. Поиск всех подкаталогов в указанном каталоге и фильтрация с помощью функции
var options = {
path: 'C:\\Windows\\System32',
folders: true,
recursive: true,
filter: function(filename) { return filename.match(/z[^\\]*$/i); }
};
var f = FileSystem.find(options);.
Объемное послесловие
В WSH для получения списка файлов/каталогов по заданному шаблону (или нескольким шаблонам) можно воспользоваться одним из трех способов:
1. ActiveX - например, JScript: Поиск файлов/каталогов по шаблону
2. WQL-запрос к классу CIM_DataFile (подробности здесь http://www.script-coding.com/WMI_FileSystem.html)
3. чтение стандартного вывода внешней команды, например, способ коллеги JSman - JScript: поиск файлов по расширению
Из всех трех способов третий обладает следующими преимуществами перед остальными:
1. наибольшая скорость исполнения
2. достаточно гибкая фильтрация необходимых данных по шаблону
3. лаконичность алгоритма
4. простота использования при получении списка объектов любой глубины вложенности
Тем не менее, этот способ имеет несколько существенных недостатков:
1. ограничение буфера вывода в 4 КБ. Это проблема решается с помощью метода, описанного в теме JScript: Проблема с WshScriptExec.Exec, пост №#22, "расширенный" алгоритм
2. алгоритм основан на чтении вывода встроенной команды DIR, который отличается при указании опции /S - поиск файлов из указанного каталога и всех его подкаталогов (поиск в глубину). Проблема заключается в том, что команда DIR /B /S возвращает список файлов, включая путь до каждого из них, в то время как DIR /B - только имена файлов без пути к ним. Для "нормализации" списка требуется дополнительное действие по добавлению префикса (путь к файлу) во втором случае. Но решая проблему 1 можно решить и проблему 2.
3. Для фильтрации определенной группы файлов используется внешняя команда FINDSTR, которая отфильтровывает требуемые строки входного потока. Однако, принимая во внимание проблему 2, необходимо учитывать различия вывода команды DIR в обоих случаях.
4. Видимо может возникнуть проблема читабельности полученного текста в случае использования другой кодовой страницы (см. описание параметра codepage в начале поста)
В предложенном алгоритме все указанные проблемы были учтены и решены. Текущая реализация не использует сторонние либо внешние компоненты, классы, библиотеки, однако использует удобную (на мой взгляд) функцию конвертации символов-джокеров (wildcards * и ?) в правильное регулярное выражение. Суть этой вспомогательной функции в том, что она принимает на входе строку/массив строк, содержащие джокеры и возвращает правильное регулярное выражение, например:
var re = FileSystem.wildcard2regex(['*.js', '*.vbs']); // по умолчанию вернет /^(.*?\.js|.*?\.vbs)$/i
