1

My company has millions of old reports in pdf form. They are Typically named in the format: 2018-09-18 - ReportName.pdf

The organization we need to submit these to is now requiring that we name the files in this format: Report Name - 2018-09.pdf

I need to move the first 7 characters of the file name to the end. I'm thinking there is probably an easy code to perform this task, but I cannot figure it out. Can anyone help me.

Thanks!

mklement0
  • 382,024
  • 64
  • 607
  • 775
David Maughn
  • 13
  • 1
  • 3

4 Answers4

1

Caveat:

  • As jazzdelightsme points out, the desired renaming operation can result in name collisions, given that you're removing the day component from your dates; e.g., 2018-09-18 - ReportName.pdf and 2018-09-19 - ReportName.pdf would result in the same filename, Report Name - 2018-09.pdf.

  • Either way, I'm assuming that the renaming operation is performed on copies of the original files. Alternatively, you can create copies with new names elsewhere with Copy-Item while enumerating the originals, but the advantage of Rename-Item is that it will report an error in case of a name collision.

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.

  • A script block ({ ... }) is passed to Rename-Item's -NewName parameter, which enables dynamic renaming of each input file ($_) received from Get-ChildItem using a string-transformation (replacement) expression.

  • The -replace operator uses a regex (regular expression) as its first operand to perform string replacements based on patterns; here, the regex breaks down as follows:

    • ^(\d{4}-\d{2}) matches something like 2018-09 at the start (^) of the name and - by virtue of being enclosed in (...) - captures that match in a so-called capture group, which can be referenced in the replacement string by its index, namely $1, because it is the first capture group.

    • (.*?) captures the rest of the filename excluding the extension in capture group $2.

      • The ? after .* makes the sub-expression non-greedy, meaning that it will give subsequent sub-expressions a chance to match too, as opposed to trying to match as many characters as possible (which is the default behavior, termed greedy).
    • \.pdf$ matches the the filename extension (.pdf) at the end ($) - note that case doesn't matter. . is escaped as \., because it is meant to be matched literally here (without escaping, . matches any single character in a single-line string).

    • $2 - $1.pdf is the replacement string, which arranges what the capture groups captured in the desired form.

Note that any file whose name doesn't match the regex is quietly left alone, because the -replace operator passes the input string through if there is no match, and Rename-Item does nothing if the new name is the same as the old one.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

Get-ChildItem with some RegEx and Rename-Item can do it:

Get-ChildItem -Path "C:\temp" | foreach {

    $newName = $_.Name -replace '(^.{7}).*?-\s(.*?)\.(.*$)','$2 - $1.$3'
    $_ | Rename-Item -NewName $newName
}

The RegEx

'(^.{7}).*?-\s(.*?)\.(.*$)' / $2 - $1.$3

  • (^.{7}) matches the first 7 characters
  • .*?-\s matches any characters until (and including) the first found - (space dash space)
  • (.*?)\. matches anything until the first found dot ( . )
  • (.*$) matches the file extension in this case
  • $2 - $1.$3 puts it all together in the changed order

This won't properly work if there are filenames with multiple dots ( . ) in it.

TobyU
  • 3,718
  • 2
  • 21
  • 32
0

This should work (added some test data):

$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 {
        $_
    }
}
Paweł Dyl
  • 8,888
  • 1
  • 11
  • 27
  • Nice, but you can make this more PowerShell-idiomatic: `% { if ($_ -match '(?\d{4}-\d\d)-\d\d - (?.+)\.pdf') { "$($Matches.Name) - $($Matches.Date).pdf" } else { $_ } }` or, even more concisely, with `-replace`: `% { $_ -replace '(?\d{4}-\d\d)-\d\d - (?.+)\.pdf', '${Name} - ${Date}.pdf' }` – mklement0 Sep 19 '18 at 16:59
0

Something like this -

Get-ChildItem -path $path | Rename-Item -NewName {$_.BaseName.Split(' - ')[-1] + ' - ' + $_.BaseName.SubString(0,7) + $_.Extension} -WhatIf

Explanation -

  1. Split will segregate the name of the file based on the parameter - and [-1] tells PowerShell to select the last of the segregated values.
  2. SubString(0,7) will select 7 characters starting from the first character of the BaseName of the file.
  3. Remove -WhatIf to apply the rename.
Vivek Kumar Singh
  • 3,223
  • 1
  • 14
  • 27
  • Be sure to check for collisions to avoid losing data (e.g. two reports, one from 2018-09-02 and one from 2018-09-28 would both get renamed to the same file). Consider writing the files to a new location instead of renaming (overwriting) the old files. – jazzdelightsme Sep 19 '18 at 13:33
  • Even if it goes to a new location there will still be clashes in names, since the OP wants only the `YYYY-MM` part to be suffixed to the report name. So, a new location would also not suffice, I guess. Hence, provided the `-WhatIf` option. – Vivek Kumar Singh Sep 19 '18 at 13:38
  • 1
    Yes, but you won't [potentially irretrievably] lose data that way, and can easily check if you had any clashes, such as by checking the number of files in both locations. – jazzdelightsme Sep 19 '18 at 13:41
  • This is close, but it is eliminating mostly all of the File Name. For instance, it is changing something named "2018-09-17 Report Name" to "Name - 2018-09" I need to keep the full report name – David Maughn Sep 19 '18 at 14:43