3

I'm trying to organize some old photos that are split into many different folders. All of the folder names do contain the year, but almost always at the end of the folder name. This doesn't work very well when I'm trying to sort all of my photos from the past 20 years. I'm trying to write a script that would loop through all of the folder names and move the year (YYYY) to the beginning of the folder name.

Current Folders:

  • The best trip ever 2012
  • Visiting relatives 2010
  • 2017 trip
  • Old photos from 2001

Convert to:

  • 2012 The best trip ever
  • 2010 Visiting relatives
  • 2017 trip
  • 2001 Old photos from

I am not very familiar with powershell so I've spent a few hours fiddling with regex and the necessary scripts to filter to the right subset of folder names (that start with a letter and contain a 4 digit year) but I'm struggling to actually rename these successfully.

Here's what I have:

$folders = Get-ChildItem -Path C:\Users\User\pictures\ | Where-Object { $_.Name -match '^[a-zA-Z].+[0-9]{4}' }
foreach ($folder in $folders) 
        { $folder.Name.Split() | Where {$_ -match "[0-9]{4}"}
            Rename-Item -Path $folder-NewName  "$($Matches[0])_$folder.Name"
}

Any help is appreciated!

1 Answers1

3

If you use the -match operator with a regex that captures the name parts of interest via capture groups ((...)), you can rearrange these name parts, as reflected in the automatic $Matches variable variable, in a delay-bind script block passed to the Rename-Item call:

Get-ChildItem -Directory C:\Users\User\pictures |
  Where-Object Name -match '^(.+?) ?\b(\d{4})\b(.*?)$' |
    Rename-Item -WhatIf -NewName { 
      '{0} {1}{2}' -f $Matches.2, $Matches.1, $Matches.3 
    }

Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.

For an explanation of the regex and the ability to interact with it, see this regex101.com page.


Note: The following simplification, which uses the -replace operator, works in principle, but, unfortunately, reports spurious Source and destination path must be different errors as of PowerShell 7.2.1, for all directories whose name either doesn't contain a 4-digit year or already has it at the start of the name:

# !! Works, but reports spurious errors as of PowerShell 7.2.1
Get-ChildItem -Directory C:\Users\User\pictures
  Rename-Item -WhatIf -NewName { $_.Name -replace '^(.+?) ?\b(\d{4})\b(.*?)$' }

The reason is Rename-Item's unfortunate behavior of complaining when trying to rename a directory to its existing name (which happens when the -replace operation doesn't find a regex match and therefore returns the input string as-is), which should be a quiet no-op, as it already is for files - see GitHub issue #14903.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Thanks, @Santiago. Thanks for inspiring me to generalize the regex to also handle years _inside_ the names, not just at the end - please see my update (which also fundamentally restructured the answer). As for your regex: Note that `\1` inside `[...]` refers to the Unicode character with code point `1`. Given that such control characters are unlikely to be part of input string, `[^\1]` is in effect equivalent to `.` – mklement0 Jan 03 '22 at 14:42
  • Just saw this comment, I thought `[^\1]` while having a capturing group would mean something like _"match anything that is not the capture group 1"_. Here is another example: https://regex101.com/r/BzBAlg/1 – Santiago Squarzon Jan 03 '22 at 15:38
  • This is a better example actually, the one before is confusing: https://regex101.com/r/BnDXYf/1 – Santiago Squarzon Jan 03 '22 at 15:43
  • 1
    @Santiago, yes, `\1` _outside_ `[...]` is a backreference (which only works if the capture group referred to comes _before_ the reference). Inside `[...]` it is an octal escape sequence encoding a character by its code point; try `[char] 27 -match '[\33]'` To _negate_ a backreference, use a negative lookahead assertion; e.g.: `'aa', 'ab' | % { $_ -match '^(a)(?!\1)' }` – mklement0 Jan 03 '22 at 16:21
  • 1
    I totally see what you mean, I was using it before the capture group. Makes sense. Thanks for the explanation! – Santiago Squarzon Jan 03 '22 at 16:30
  • 1
    I tested this script on the test folders I had setup and it seems to work perfectly! Thanks for your help. I was struggling to figure out how to successfully use the $Matches variable but that definitely seems to have been the key. – Dr Saurus Rex Jan 03 '22 at 23:10
  • Glad to hear it helped, @DrSaurusRex. – mklement0 Jan 03 '22 at 23:20