Пакетное программирование файлов с помощью Findstr и Regex

Во-первых, я не программист пакетных файлов и/или регулярных выражений. ;)

Использование пакетного программирования файлов для анализа имен файлов, начинающихся с «T», за которыми сразу может следовать «2» И за «2» может сразу следовать любое из целых чисел 0–9. Все файлы в папке будут называться одинаково, начиная с буквы «T», за которой следуют целые числа от 1 до 29. Например: T1_... или T12_... или T2_... или T22_....

В данном конкретном случае...

а) Имена файлов с T2 не должны соответствовать регулярному выражению. Уровень ошибки должен быть 1.

б) Имена файлов с T12 не должны соответствовать регулярному выражению. Уровень ошибки должен быть 1.

c) Имена файлов с T20 - T29 должны проходить регулярное выражение. Уровень ошибки должен быть равен 0.

Проблема: эти результаты не видны. Уровень ошибки всегда равен 0.

echo on

dir *.bin /b | findstr /r /c:"^[T][2][?=0123456789]*"
if errorlevel 0 goto :regular

:special
   echo special
   pause
   exit /B 0

:regular
   echo regular
   pause
   exit /B 0

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

Что происходит с регулярным выражением «findstr», что оно не приводит к описанному поведению (a-c)? Или я неправильно использую уровень ошибок?

Спасибо за любое понимание.


person Ernie    schedule 05.12.2017    source источник
comment
Начальный или конечный x* в регулярном выражении (где x — любое регулярное выражение) можно было бы опустить, если речь идет о сопоставлении. foox* и foo оба соответствуют foo и не ошибаются при совпадении, если за foo следует что-то отличное от x. (Если вы захватите совпадающую строку для дальнейшей обработки, могут возникнуть ситуации, когда вы захотите включить в сопоставление как можно больше; но здесь вы ничего подобного не делаете.)   -  person tripleee    schedule 05.12.2017
comment
Вы можете не осознавать, что [?=0123] соответствует любому символу, который является ? или = или 0 или 1 или 2 или 3. Нет необходимости помещать T или 2 в класс символов.   -  person tripleee    schedule 05.12.2017


Ответы (3)


Во-первых, ваше регулярное выражение неверно. findstr имеет ограниченные возможности регулярных выражений, но даже в полноценном движке регулярных выражений это было бы не совсем корректно. Вот лучше:

^T2[0-9]_

Начинается с T, за которым следует 2, за которым следует один символ из диапазона 0-9, за которым следует символ подчеркивания _ (цифра I), за которым следует что угодно (неявное).


Далее, параметр /c в findstr означает "Использует указанную строку как литеральную строку поиска", а это противоречит тому, что мы на самом деле хотим.

При этом мы получаем:

findstr /r "^T2[0-9]_"

Затем фильтруем список файлов в каталоге:

dir /b *.bin 2>NUL | findstr /r "^T2[0-9]_"

Здесь 2>NUL перенаправляет STDERR из dir в NUL, поэтому «файл не найден» или тому подобное не печатается.

Кстати, следующее никогда не будет генерировать ошибки в команде dir, просто переместив проверку расширения файла в регулярное выражение:

dir /b | findstr /r "^T2[0-9]_.*\.bin$"

Далее делаем что-то в зависимости от того, совпали мы или не совпали:

dir /b *.bin 2>NUL | findstr /r "^T2[0-9]_" >NUL && (
    @echo Match!
) || (
    @echo No Match!
)

Это использует свойства && и ||, а именно, чтобы проверить уровень ошибки и выполнить следующую команду, когда он равен 0 (&&) или иначе (||). Мне эта конструкция нравится больше, чем goto.

Также это использует >NUL для подавления обычного вывода findstr.


Затем делаем что-то для каждого подходящего имени файла (это лучше, чем использовать goto):

for /f "usebackq delims=" %f in (`dir /b *.bin 2^>NUL ^| findstr /r "^T2[0-9]_"`) do (
    @echo Match: %f
)

Здесь usebackq позволяет использовать обратные кавычки для включения команды, delims= отключает стандартное поведение «разделить пробелы» для for. Таким образом, имена файлов с пробелом не будут разбиваться на токены. Также обратите внимание, что в команде, заключенной в кавычки, мы должны экранировать специальные символы, такие как вертикальная черта | и >, с помощью ^.


Далее, выше, но для использования в пакетном файле:

@echo off

for /f "usebackq delims=" %%f in (`dir /b *.bin 2^>NUL ^| findstr /r "^T2[0-9]_"`) do (
    echo Match: %%f
)

Здесь мы должны экранировать % как %% (специальное правило, которое применяется к переменным индекса цикла for).

person Tomalak    schedule 05.12.2017
comment
Спасибо всем за помощь в этом. Я ценю очень быстрый ответ. Я попробовал код совпадения/несовпадения Томалака, и он работал из коробки. Я действительно хочу изучить это дальше и попробовать все варианты от вас обоих. Если у вас есть хорошие ссылки на книги для изучения программирования пакетных файлов и программирования REGEX, я был бы очень благодарен за это. -- Эрни :) - person Ernie; 05.12.2017
comment
Пакетное программирование Windows. Избегайте, если можете, это долгий и болезненный путь. ;) Если вы новичок в программировании, я рекомендую начать с настоящего языка программирования — Python, или, поскольку вы работаете в Windows, с Powershell (хотя это тоже довольно сложно). Powershell предустановлен с каждой Windows, а Python — нет. Python проще в освоении и намного проще. Что касается регулярных выражений, я могу порекомендовать только книгу Джеффри Фридла. - person Tomalak; 05.12.2017

findstr использует подмножество регулярных выражений для поиска строки в файле.

Следовательно, ваша команда вероятно возвращает errorlevel из 0, потому что задается вопрос: "Есть ли строка, соответствующая этому где в "файле", сгенерированном dir?"

Похоже, вы пытаетесь показать те файлы, которые соответствуют, и те, которые не соответствуют вашему шаблону.

Итак, если вам просто нужен список файлов, то

dir /b /A-D |findstr /R "regex"

должен выполнить эту задачу, отметив:

/a-d означает "не показывать имена каталогов"
/r используется по умолчанию, поэтому является избыточным
/r /v может заменить /r, что означает "не не соответствовать регулярному выражению"
переключатели нечувствительны к регистру.

Другой подход был бы

for /f "delims=" %%a in ('dir /b /a-d') do (
 echo %%a|findstr /r "regex" >nul
 if errorlevel 1 (call :nonmatch "%%a"
 ) else (
  call :match "%%a"
 )
)

где [non]match — подпрограммы:

....
goto :eof

:match
echo %~1 matches
goto :eof

%%a присваивается каждое имя из команды dir по очереди, а затем проверяется findstr, выводя в nul и устанавливая errorlevel в 0, если regex передается, и 1, если нет. Затем проверяется errorlevel и выполняется одна из двух подпрограмм, при этом первым параметром %1 является имя файла (в кавычках).

Примечание.
Метапеременная %%a чувствительна к регистру
findstr также может иметь переключатель /i, чтобы сделать анализ regex нечувствительным к регистру.
Конструкция goto :eof (без учета регистра) определяется в cmd как "перейти к концу файла"

Теперь - что касается самого regex, как я уже сказал, это подмножество. Я бы использовал T2[0-9]_ и добавил переключатель /b к findstr, получив findstr /r /i /b "T2[0-9]_", что означает "начинается без учета регистра с regex T2a_digitunderscore"

Вы также можете опустить /b и использовать "^T2[0-9]_" в теории — лично я предпочитаю версию /b.

person Magoo    schedule 05.12.2017

findstr поддерживает ограниченные регулярные выражения, но для вашего приложения вы можете использовать `findstr /r "^T2[0-9]".

Для полностью совместимых с Javascript регулярных выражений как насчет использования cscript вместо этого?

@echo off

dir *.txt /b | cscript //nologo match.js "^T2[0-9]"
if %errorlevel% == 0 goto :regular

:special
   echo special
   pause
   exit /B 0

:regular
   echo regular
   pause
   exit /B 0

Где match.js определяется как:

if (WScript.Arguments.Count() !== 1) {
  WScript.Echo("Syntax: match.js regex");
  WScript.Quit(1);
}
var rx = new RegExp(WScript.Arguments(0), "i");
var matched = false;
while (!WScript.StdIn.AtEndOfStream) {
  var str = WScript.StdIn.ReadLine();
  if (str.match(rx)) {
    WScript.Echo(str);
    matched = true;
  }
}
if (!matched) {
  WScript.Quit(1);
}
person Stephen Quan    schedule 08.12.2017