У моей компании миллионы старых отчетов в формате pdf. Обычно они называются в формате: 2018-09-18 - ReportName.pdf
.
Организация, в которую мы должны их отправить, теперь требует, чтобы мы называли файлы в следующем формате: Report Name - 2018-09.pdf
.
Мне нужно переместить первые 7 символов имени файла в конец. Я думаю, что, вероятно, есть простой код для выполнения этой задачи, но я не могу его понять. Может кто-нибудь мне помочь.
Спасибо!
Get-ChildItem
с некоторыми RegEx и Rename-Item
может это сделать:
Get-ChildItem -Path "C:\temp" | foreach {
$newName = $_.Name -replace '(^.{7}).*?-\s(.*?)\.(.*$)','$2 - $1.$3'
$_ | Rename-Item -NewName $newName
}
RegEx
'(^.{7}).*?-\s(.*?)\.(.*$)'
/ $2 - $1.$3
(^.{7})
соответствует первым 7 символам.*?-\s
соответствует любым символам до (включительно) первого найденного -
(пробел, пробел)(.*?)\.
соответствует чему угодно до первой найденной точки (.)(.*$)
соответствует расширению файла в этом случае$2 - $1.$3
собирает все вместе в измененном порядкеЭто не будет работать должным образом, если в нем есть имена файлов с несколькими точками (.).
Это должно работать (добавлены некоторые тестовые данные):
$test = '2018-09-18 - ReportName.pdf','2018-09-18 - Other name.pdf','other pattern.pdf','2018-09-18 - double.extension.pdf'
$test | % {
$match = [Regex]::Match($_, '(?<Date>\d{4}-\d\d)-\d\d - (?<Name>.+)\.pdf')
if ($match.Success) {
"$($match.Groups['Name'].Value) - $($match.Groups['Date'].Value).pdf"
} else {
$_
}
}
Что-то вроде этого -
Get-ChildItem -path $path | Rename-Item -NewName {$_.BaseName.Split(' - ')[-1] + ' - ' + $_.BaseName.SubString(0,7) + $_.Extension} -WhatIf
Объяснение -
Split
будет разделять имя файла на основе параметра -
, а [-1]
указывает PowerShell выбрать последнее из разделенных значений.SubString(0,7)
выберет 7 символов, начиная с первого символа BaseName
файла.-WhatIf
, чтобы применить переименование.Обязательно проверяйте наличие коллизий, чтобы избежать потери данных (например, два отчета, один от 02.09.2018 и один от 28.09.2018 будут переименованы в один и тот же файл). Рассмотрите возможность записи файлов в новое место вместо переименования (перезаписи) старых файлов.
Даже если он перейдет в новое место, все равно будут конфликты в именах, поскольку OP хочет, чтобы к имени отчета добавлялась только часть YYYY-MM
. Так что новой локации, наверное, тоже не хватит. Следовательно, предусмотрена опция -WhatIf
.
Да, но вы не [потенциально безвозвратно] потеряете данные таким образом, и сможете легко проверить, были ли у вас конфликты, например, проверив количество файлов в обоих местах.
Это близко, но устраняет почти все имя файла. Например, он меняет что-то с названием «2018-09-17 Report Name» на «Name - 2018-09». Мне нужно сохранить полное имя отчета.
Предостережение:
Как указывает джаз, желаемая операция переименования может привести к коллизии имен, учитывая, что вы удаляете компонент дня из своих дат; например, 2018-09-18 - ReportName.pdf
и 2018-09-19 - ReportName.pdf
приведут к имени файла тем же, Report Name - 2018-09.pdf
.
В любом случае, я предполагаю, что операция переименования выполняется над копии исходных файлов. В качестве альтернативы вы можете создать копии с новыми именами в другом месте с Copy-Item
при перечислении оригиналов, но преимущество Rename-Item
в том, что он будет сообщать ошибка в случае конфликта имен.
Get-ChildItem -Filter *.pdf | Rename-Item -NewName {
$_.Name -replace '^(\d{4}-\d{2})-\d{2} - (.*?)\.pdf$', '$2 - $1.pdf'
} -WhatIf
-WhatIf
previews the renaming operation; remove it to perform actual renaming.
Add -Recurse
to the Get-CildItem
call to process an entire directory subtree.
The use of -Filter
is optional, but it speeds up processing.
блок скрипта ({ ... }
) передается в параметр Rename-Item
-NewName
, который позволяет динамически переименовывать каждый входной файл ($_
), полученный от Get-ChildItem
, с использованием выражения преобразования (замены) строки.
Оператор -replace
использует регулярное выражение (регулярное выражение) в качестве своего первого операнда для выполнения замены строк на основе шаблонов; здесь регулярное выражение разбивается следующим образом:
^(\d{4}-\d{2})
соответствует чему-то вроде 2018-09
в начале (^
) имени и - в силу того, что он заключен в (...)
- записывает это совпадение в так называемый группа захвата, на который можно ссылаться в строке замены по его индексу, а именно $1
, потому что это группа захвата первый.
(.*?)
захватывает остальную часть имени файла без учета как расширение в группе захвата $2
.
?
после .*
создает подвыражение не жадный, что означает, что он даст последующим подвыражениям возможность совпадения, а не пытаться сопоставить как можно больше символов (что является поведением по умолчанию, называемым жадный).\.pdf$
соответствует расширению имени файла (.pdf
) в конце ($
) - обратите внимание, что регистр не имеет значения. .
экранирован как \.
, потому что он предназначен для сопоставления здесь буквально (без экранирования .
соответствует одиночному символу любой в однострочной строке).
$2 - $1.pdf
- это строка замены, которая упорядочивает то, что захвачено группами захвата, в желаемой форме.
Обратите внимание, что любой файл, имя которого не соответствует регулярному выражению, незаметно оставляется в покое, потому что оператор -replace
передает строку ввода, если совпадения нет, а Rename-Item
ничего не делает, если новое имя совпадает со старым.
Прекрасно, но вы можете сделать это более идиоматическим для PowerShell:
% { if ($_ -match '(?<Date>\d{4}-\d\d)-\d\d - (?<Name>.+)\.pdf') { "$($Matches.Name) - $($Matches.Date).pdf" } else { $_ } }
или, что еще более кратко, с-replace
:% { $_ -replace '(?<Date>\d{4}-\d\d)-\d\d - (?<Name>.+)\.pdf', '${Name} - ${Date}.pdf' }