5

I'm struggling to write a Powershell Command that does the following. Assume A folder which has a bunch of files with random names that match a regex pattern. I would like to capture the part that matches the pattern and rename the file to that part only.

E.g. "asdjlk-c12aa13-.pdf" should become "c12aa13.pdf" if the pattern is \w\d+\w+\d+ (or similiar).

My current idea looks something like this:

Get-ChildItem | Rename-Item -NewName { $_.Name -match $pattern ... } -WhatIf

where ... needs to be replaced with something that sets the "value" of the codeblock (i.e. the NewName) to the matched group. I.e. I don't know how to access $matched directly after the -match command.

Also, I wonder if it's possible to do lazy matching using -match, .*? doesn't seem to do the trick.

Xaser
  • 2,066
  • 2
  • 22
  • 45
  • 1
    For the regex you could capture in a group what you want and use those groups in the replacement [`example`](https://regex101.com/r/BRXPzE/1) – The fourth bird Feb 20 '18 at 15:19
  • exactly what i had in mind, yes. the powershell poses more of a problem :/ – Xaser Feb 20 '18 at 17:14
  • Heh... random names that match a regular expression. I'm not positive that you know what random means. :) – EBGreen Feb 20 '18 at 18:22
  • @EBGreen possibly poor choice of words, granted. Random in the sense that there is a random part in the filename not governed by the pattern and that the pattern could be anything and that the symbols constituting the pattern can be random. – Xaser Feb 26 '18 at 12:26

4 Answers4

5

While you could follow the -match operation with subsequent extraction of the matched part(s) via the automatic $Matches variable, it's often easier to combine the two operations with the help of the -replace operator:

You just need to make sure that in order to return only the parts of interest, you must match the input string in full and then ignore the parts you don't care about:

PS> 'asdjlk-c12aa13-.pdf' -replace '^.*?(\w\d+\w+\d+).*?(\.pdf)$', '$1$2'
c12aa13.pdf
  • ^.*? (lazily) matches the prefix before the part of interest.

  • (\w\d+\w+\d+) matches the part of interest, wrapped in a capture group; since it is the 1st capture group in the regex, you can refer to what it captured as $1 in the replacement operand.

  • .*? (lazily) matches everything after up to the .pdf filename extension.

  • (\.pdf)$ matches filename extension .pdf at the end of the name and, as the 2nd capture group, can be referenced as $2 in the replacement operand.

  • $1$2 simply concatenates the 2 capture-group matches to output the desired name.

    • Note: Generally, use single-quoted strings for both the regex and the replacement operand, so that $ isn't accidentally interpreted by PowerShell beforehand.

    • For more information about -replace and the syntax of the replacement operand, see this answer of mine.


The solution in the context of your command:

Get-ChildItem |
  Rename-Item -NewName { $_.Name -replace '^.*?(\w\d+\w+\d+).*?(\.pdf)$', '$1$2' } -WhatIf
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

A safer method is to do so with a test (similar to -WhatIf) This example renames files from DSC12345 - X-1.jpg => DSC12345-X1.jpg

# first verify what your files will convert too
# - gets files
# - pipes to % (foreach)
# - creates $a variable for replacement
# - echo replacement
Get-ChildItem . | % { $a = $_.name -replace "^DSC(\d+)\s-\s([A-Z])-(\d).jpg$",'DSC$1-$2$3.jpg'; echo "$_.name => $a"; }

# example output:
# DSC04975-W1.jpg.name => DSC04975-W1.jpg
# DSC04976-W2.jpg.name => DSC04976-W2.jpg
# DSC04977-W3.jpg.name => DSC04977-W3.jpg
# ...

# use the same command and replace "echo" with "ren"
Get-ChildItem . | % { $a = $_.name -replace "^DSC(\d+)\s-\s([A-Z])-(\d).jpg$",'DSC$1-$2$3.jpg'; ren $_.name $a; }

This is much safer as renames can be disastrous when run incorrectly.

sonjz
  • 4,870
  • 3
  • 42
  • 60
0

To be honest, I am not sure if your line above will work. If "\w\d+\w+\d+" is the pattern you are looking for, I would do something like this:

[regex]$regex = "\w\d+\w+\d+"    
Get-ChildItem | ?{$_.name -match $regex} | %{rename-item $_ "$($regex.Matches($_).value).pdf"}

In this case, you pipeline the output of the Get-ChildItem to the "foreach where loop" (?{...}), and after that you pipeline this outpout to the "foreach loop" (%{...}) to rename every object.

Luka Klarić
  • 323
  • 2
  • 16
0

You can put as much as you want in the scriptblock. Also hiding the output of the -match. The regex is lazy with the "?".

Get-ChildItem | Rename-Item -NewName { [void]($_.Name -match '.+?'); $matches.0 } -WhatIf

What if: Performing the operation "Rename File" on target "Item: /Users/js/foo/afile Destination: /Users/js/foo/a".
What if: Performing the operation "Rename File" on target "Item: /Users/js/foo/bfile Destination: /Users/js/foo/b".
What if: Performing the operation "Rename File" on target "Item: /Users/js/foo/cfile Destination: /Users/js/foo/c".

js2010
  • 23,033
  • 6
  • 64
  • 66