Как правильно вложить два цикла `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