Что нужно сделать
В
предыдущем посте я описывал, как читаю учебник-сайт «
LearnCpp.com» и пишу множество маленьких программ на языке программирования C++, в которых проверяю работу примеров из учебника, а также решаю упражнения оттуда же. Пока что я прочел первые три главы этого учебника, а написанные программы храню на жестком диске своего компьютера, организованные следующим образом (я использую операционную систему «Windows 10»):
C:\Users\Илья\source\repos\learncpp-com-examples\
chapter_00\
00-00_helloworld.cpp
00-01_helloworld-wait.cpp
...
chapter_01\
01-00_snippet.cpp
01-01_line-comment.cpp
...
chapter_02\
lesson_02.01\
02-00_functions.cpp
02-01_functions-many-times.cpp
...
lesson_02.02\
...
К этим первым трем главам учебника я уже написал больше ста маленьких программ (см. в
моем репозитории на «Гитхабе»), и тут выяснилось, что отведенных мною двух разрядов для нумерации написанных программ в рамках одной главы не хватает. Я рассчитывал, что в рамках одной главы понадобится написать не более 99 программ, а уже выходит больше. Значит, для этой нумерации требуется три разряда. При этом в нумерации я использую ведущие нули, следовательно потребуется переименование всех уже написанных файлов по следующему принципу:
00-00_имя-файла.ext # было
00-000_имя-файла.ext # стало
Добавленный ноль я пометил в блоке кода выше красным цветом. При этом следует учесть, что структура папок имеет разную глубину в разных главах. Еще следует иметь в виду, что расширения файлов у меня встречаются разные, как минимум это «.cpp» (для файлов с исходным кодом) и «.h» (для заголовочных файлов). Могут быть и другие расширения.
В общем-то, не так сложно вручную переименовать несколько сотен файлов (нудно, но не сложно). Но мне было интересно попробовать автоматизировать эту задачу. Для ее автоматизации я выбрал написание скрипта на языке PowerShell. Этот скрипт я рассчитываю использовать в программе-оболочке «PowerShell» версии 7 или выше.
База для скрипта
Для переименования (файлов и не только) в программе-оболочке «PowerShell» есть командлет «
Rename-Item». В документации к этому командлету есть
пример 4, в котором демонстрируется переименование сразу группы файлов. Этот код я взял за основу своего скрипта. Вот код из этого примера:
Get-ChildItem *.txt | Rename-Item -NewName { $_.Name -replace '.txt','.log' }
Здесь с помощью командлета «
Get-ChildItem» мы сначала получаем коллекцию (массив) объектов, представляющих файлы и папки (в данном примере это все файлы с расширением «.txt» в текущей папке). Символ звездочки * - это один из
символов-джокеров (по-английски «wildcard»), в данном случае он обозначает любое количество любых символов (в том числе возможное отсутствие каких-либо символов). Затем мы отправляем эти объекты по одному через конвейер (обозначается оператором |) на вход командлету «Rename-Item». Командлет «Rename-Item» меняет название каждого полученного по конвейеру файла на название, указанное с помощью параметра -NewName.
На месте, в котором должно быть указано новое название файла, помещен
скриптовый блок (по-английски «script block»). Этот скриптовый блок возвращает строку, которая станет значением для параметра -NewName командлета «Rename-Item». Возвращаемое скриптовым блоком значение в данном примере является результатом, возвращаемым оператором -replace, который выполняет
операцию замены. Как видно из кода выше, в данном примере оператор замены слева от себя получает переменную со строкой (название файла), в которой следует произвести замену; справа от себя он получает строку, которую следует искать, и строку, которой следует заменить найденное (эти две строки отделяются друг от друга запятой).
В результате приведенный выше код находит все файлы с расширением «.txt» в текущей папке и заменяет расширение у найденных файлов на «.log».
Для удобства я переписал приведенный выше код следующим образом:
Get-ChildItem -Path "*.txt" |
Rename-Item -NewName {
$_.Name -replace '.txt', '.log'
}
Это тот же самый код, что и выше. Но теперь в него стало удобнее добавлять новые утверждения (statement), чтобы расширить функционал этого кода под мои требования. Символ новой строки входит в состав пробельных символов языка PowerShell, его можно вставлять в некоторые места (не в любое место) чересчур длинных утверждений, регулируя их по ширине и таким образом улучшая удобочитаемость кода.
Кроме вставки нескольких символов новой строки, я еще добавил название параметра -Path командлета «Get-ChildItem», а значение этого параметра взял в двойные кавычки (мне так удобнее). (Из-за того, что параметр -Path является
позиционным, его имя можно опускать, но при написании скриптов это не одобряется.)
Начинаю изменять и дополнять код, регулярные выражения
Мне нужно отобрать все файлы в текущей папке, независимо от расширения, поэтому параметр -Path (и его значение) командлета «Get-ChildItem» я убираю. При отсутствии этого параметра (и его значения), по умолчанию, командлет «Get-ChildItem» будет выбирать объекты (файлы и папки) для итоговой коллекции (массива) из текущей папки.
При этом мне нужны только файлы, а папки необходимо отсеять, так как командлет «Rename-Item» переименовывает файлы, но не может менять пути к файлам (для изменения пути к файлу, то есть для перемещения файла в другое место, можно использовать командлет «
Move-Item»). Для отсеивания папок я добавляю в командлет «Get-ChildItem» параметр -File.
Далее мне нужно изменить строки, передаваемые оператору -replace справа, чтобы обеспечить нужные мне переименования файлов. При этом я использую способность оператора -replace работать с
регулярными выражениями. Вот что у меня получилось:
Get-ChildItem -File |
Rename-Item -NewName {
$_.Name -replace '^(\d\d)-(\d\d)_(.*)', '$1-0$2_$3'
}
Этот код уже делает то, что мне нужно, для всех файлов в текущей папке (добавляемый ноль я обозначил в блоке кода выше красным цветом). Этого еще недостаточно для решения моей задачи, но это уже большой шаг в нужном мне направлении.
Символ «карет» ^ в регулярном выражении обозначает начало строки, в которой производится поиск. Последовательность \d обозначает одну любую цифру (по-английски «digit»). Символ точки . обозначает один любой символ. Символ звездочки * в регулярном выражении является одним из показателей количества (по-английски «quantifier»). Поэтому последовательность .* обозначает любое количество любых символов (в том числе возможное отсутствие каких-либо символов). Используя эти обозначения, я представил имя файла, подлежащего переименованию, в следующем виде:
00-00_имя-файла.ext # так выглядят имена файлов, которые я хочу переименовать
'^\d\d-\d\d_.*' # соответствующее регулярное выражение
Чтобы сконструировать из старого названия файла новое, я использую такое понятие в регулярных выражениях, которое называется «захватывающей группой» (по-английски «capturing group»). С помощью захватывающих групп я выделяю (захватываю) из старого названия файла нужные мне куски, сохраняю их в предопределенные переменные и конструирую из этих кусков новое название файла (
тут подробнее). «Захват» обозначается с помощью круглых скобок. Вот как это выглядит:
00-00_имя-файла.ext # так выглядят имена файлов, которые я хочу переименовать
'^(\d\d)-(\d\d)_(.*)' # соответствующее регулярное выражение
$1 $2 $3 # названия захватывающих групп
'$1-0$2_$3' # конструирую новое название файла
00-000_имя-файла.ext # так будут выглядеть имена файлов после переименования
Названия захватывающих групп предопределены, я их не назначал, это делает программа-оболочка «PowerShell». Захватывающие группы нумеруются слева направо по порядку.
Работа с вложенными папками, рекурсия
Продолжение следует...