Как правильно вложить два цикла `forfiles` (чтобы внутреннее тело расширяло переменные обоих уровней итерации)?

Я пытаюсь вложить два forfiles циклы, так что команда внутреннего цикла получает @ переменные из внешней и внутренней итерации цикла. Для последнего @ замена переменной должна быть экранирована для внешнего цикла, чтобы внутренний forfiles Команда получает имя переменной.

У меня есть фрагмент кода, который перечисляет данный каталог (C:\root), и если повторяющийся элемент является каталогом сам по себе, все содержащиеся в нем текстовые файлы (*.txt) перечислены.
Тем не менее, это не работает, как ожидалось: я пытался избежать расширения @file с \, но он расширяется до значения внешнего цикла:

2> nul forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE forfiles /P @path /M *.txt /C \"cmd /C echo @relpath -- \@file\""

Кроме того \@file, также ^@file, ^^@file, ^^^@file, 0x40file (0x40 будучи шестнадцатеричным символьным представлением кода @) расширить до значения внешнего forfiles переменная.
\\@file а также ^^^^@file расширить до внешнего значения тоже, с \ а также ^ предшествовал соответственно.
Четное @^file, @^^file (см. этот пост) не работает (последний расширяется до @file в прямом смысле).

Итак: есть ли способ избежать замены @ переменные (например, @file...) из внешнего forfiles петля так, чтобы внутренний forfiles Итерация получает буквальное имя переменной и расширяет его до значения?

Я работаю на Windows 7 64-битной.


Замечания:
2> nul перенаправление должно избегать многочисленных сообщений об ошибках ERROR: Files of type "*.txt" not found. когда ни один файл в текущем обработанном каталоге не соответствует заданной маске (*.txt).

2 ответа

Решение

Хитрость заключается в том, чтобы использовать функцию замены кода шестнадцатеричного символа в forfiles в формате 0xHH, который может быть вложен сам по себе. В данном контексте, 00x7840 используется, следовательно, первый (внешний) forfiles цикл заменяет 0x78 порция по x, в результате чего 0x40, который в свою очередь разрешается вторым (внутренним) forfiles цикл, заменив его @,

Просто 0x40 не работает как forfiles заменяет шестнадцатеричные коды в первом проходе, а затем обрабатывает @ переменные за второй проход, так 0x40file будет заменен на @file сначала, а затем расширяется до текущего элемента с итерацией по внешнему forfiles цикл оба.


Следующая командная строка проходит по заданному корневому каталогу и отображает относительный путь каждого непосредственного подкаталога (повторяется внешним forfiles цикл) и все текстовые файлы, найденные в нем (повторяется внутренним forfiles петля):

2> nul forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE forfiles /P @path /M *.txt /C 0x22cmd /C echo @relpath -- 00x7840file0x22"

Вывод может выглядеть следующим образом (относительные пути подкаталогов слева, текстовые файлы справа):

.\data_dir -- "text_msg.txt"

.\logs_dir -- "log_book.txt"
.\logs_dir -- "log_file.txt"

Объяснение кода:

  • как описано выше, 00x7840file часть скрывает @file имя переменной из внешнего forfiles команда и передает его замену на внутренний forfiles команда;
  • чтобы избежать проблем с кавычками " а также cmd /C, кавычки в строке после /C переключатель внешнего forfiles избегать путем указания своего шестнадцатеричного кода 0x22;
    (forfiles поддерживает экранирование кавычек, таких как \", тем не мение cmd /C не заботится о \ и поэтому он обнаруживает "; 0x22 не имеет особого значения для cmd и так безопасно)
  • if оператор проверяет, является ли элемент, перечисленный внешним forfiles цикл является каталогом и, если нет, пропускает внутренний forfiles петля;
  • если перечисляемый подкаталог не содержит элементов, соответствующих данному шаблону, forfiles возвращает сообщение об ошибке вроде ERROR: Files of type "*.txt" not found. в STDERR; чтобы избежать таких сообщений, перенаправление 2> nul был применен;

Пошаговая замена:

Вот снова приведенная выше командная строка, но с удаленным перенаправлением, просто для демонстрации:

forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE forfiles /P @path /M *.txt /C 0x22cmd /C echo @relpath -- 00x7840file0x22"

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

Взятие элементов первой строки вышеприведенного примера вывода (.\data_dir -- "text_msg.txt"), командная строка выполняется внешним forfiles команда:

cmd /C if TRUE==TRUE forfiles /P "C:\root" /M *.txt /C "cmd /C echo ".\data_dir" -- 0x40file"

Так что внутренний forfiles командная строка выглядит так (cmd /C удалены, а if условие выполнено):

forfiles /P "C:\root" /M *.txt /C "cmd /C echo ".\data_dir" -- 0x40file"

Теперь командная строка выполняется внутренним forfiles команда (обратите внимание на удаленные буквенные кавычки вокруг .\data_dir и мгновенная замена 0x40file по значению переменной @file):

cmd /C echo .\data_dir -- "text_msg.txt"

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


Замечания:

Весь путь или имя файла связано @ переменные заменяются на строки в кавычках; однако приведенный выше пример выходных данных не содержит окружающих кавычек для путей к каталогам; это потому что forfiles удаляет любые буквенные (не экранированные) кавычки " из строки после /C переключатель; чтобы вернуть их сюда, замените @relpath в командной строке 00x7822@relpath00x7822; \\\"@relpath\\\" тоже работает (но не рекомендуется, хотя не путать cmd).


Приложение:

поскольку forfiles не является внутренней командой, ее можно использовать без cmd /C префикс, как forfiles /C "forfiles /M *" Например, (если не используется какая-либо дополнительная внутренняя или внешняя команда, объединение команд, перенаправление или конвейерная передача, где cmd /C является обязательным).
Однако из-за ошибочной обработки аргументов командной строки после /C переключатель forfiles вам на самом деле нужно заявить, что forfiles /C "forfiles forfiles /M *" так что внутренняя forfiles Команда удвоилась. В противном случае сообщение об ошибке (ERROR: Invalid argument/option) брошен.
Этот лучший обходной путь был найден в этом посте: forfiles без cmd / c (прокрутите вниз).

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

@echo off
for /f "delims=" %%a in ('2^>nul forfiles /P "C:\root" /M "*" /C "cmd /C if @isdir==TRUE echo @path"') do forfiles /P %%a /M *.txt /C "cmd /C echo @relpath -- @file"
pause
Другие вопросы по тегам