Renaming files by reformatting existing filenames - placeholders in replacement strings used with the -replace operator
I have a few video file like this:
VideoName_s01e01.mp4
where the season and episodes are variables. I want to add an underscore ("_") between the s??
and e??
.
I have been using powershell for renaming purposes, I have a starting point:
GCI $path -filter '*_s??e??*' -rec | Ren -new { $_.name -replace '_s[0-9][0-9]', '_s[0-9]_[0-9]_' } -passthru
This actually renamed my files VideoName_s[0-9]_e[0-9].mp4
.
Basically, I am looking for the characters s??e??
I just don't know how to make them variables in the replace section.
I think the best method would be:
- Find the position of
e??s??
(let's call it X). - split the string at
X-3
. - concatenate the string with a "
_
" in the middle.
Solution 1:
Martin Brandl's answer provides an elegant and effective solution, but it's worth digging deeper:
PowerShell's -replace
operator (... -replace <search>[, <replace>]
):
-
Takes a regular expression as its first operand,
<search>
(the search expression), and invariably matches globally, i.e., it replaces all matches.-
'bar' -replace '[ra]', '@'
->'b@@'
-
-
Specifying a replacement expression,
<replace>
, is optional, in which case the empty string is substituted for what<search>
matched, resulting in its effective removal.-
'bar' -replace '[ra]'
->'b'
-
-
If
<replace>
is specified, it supports two forms:-
v6.1+ (PowerShell Core only): A script block (
{ ... }
) as the replacement operand, which offers fully dynamic calculation of the replacement string on a per-match basis - see this answer for an example.-
'bar' -replace '[ra]', { '{' + $_.Value + '}' }
->'b{a}{r}'
-
-
A string containing an expression that can reference what the regular expression captured (and didn't capture) - such as
$&
to refer to what was matched - explained in detail below.-
'bar' -replace '[ra]', '{$&}'
->'b{a}{r}'
-
-
-
-replace
matches case-insensitively (and can also be written as-ireplace
); to perform case-sensitive matching, use the form-creplace
.
The "replacement language" for referencing regex captures in a string-typed <replace>
operand is itself not a regular expression - no matching happens there, only references to the results of the regex matching are supported, via $
-prefixed placeholders that are not to be confused with PowerShell variables.
-
PowerShell's documentation (now) briefly explains the syntax of replacement strings in its conceptual
about_Comparison_Operators
help topic. -
For the full picture, refer to the Substitutions in Regular Expressions .NET framework help topic, which is applicable because PowerShell's
-replace
uses theRegex.Replace()
method behind the scenes.
For convenience, here are the references supported in the <replace>
string (excerpted from the page linked above, with emphasis and annotations added):
-
$number
(e.g.,$1
) ... Includes the last substring matched by the capture group that is identified by the1
-basednumber
in the replacement string:-
Including
(...)
, a parenthesized subexpression, in the regex implicitly creates a capture group (capturing group). By default, such capture groups are unnamed and must be referenced by their1
-based (decimal) index reflecting the order in which they appear in the regex, so that$1
refers to what the 1st group in your regex captured,$2
to what the 2nd captured, ... -
The form
${number}
(e.g.,${1}
) for disambiguation of the number is also supported (e.g., to make sure that$1
is recognized even if followed by, say,000
, use${1}000
). -
Instead of relying on indices to refer to unnamed capture groups, you can name capture groups and refer to them by name - see next point.
-
If you're not interested in what the capture group matched, you can opt to ignore it by turning it into a non-capturing group with
(?:...)
.
-
-
${name}
... Includes the last substring matched by the named capture group that is designated by(?<name>...)
in the replacement string. -
$$
... Includes a single"$"
literal in the replacement string. -
$&
... Includes a copy of the entire match in the replacement string ($0
works too, even though it isn't directly documented). -
$`
... Includes the text of the input string before the match in the replacement string. -
$'
... Includes the text of the input string after the match in the replacement string. -
$+
... Includes the last group captured in the replacement string. [This relieves you of the need to know the last group's specific index.] -
$_
... Includes the entire input string in the replacement string.
Finally, note that:
-
-replace
invariably matches globally, so if the input string contains multiple matches, the replacements above apply to each match. -
It is generally preferable to use
'...'
(single quotes) for both the regex and the replacement string, because single-quoted strings are non-expanding (non-interpolating), and therefore avoid confusion with PowerShell's own up-front expansions of$
-prefixed tokens and interpretation of`
chars.
If you do need to include PowerShell variables or expressions, you have three options:-
Use
"..."
(expandable strings) and`
-escape$
instances that are meant for the regex engine; e.g.,`$1
in the following example:'abc' -replace '(a)', "[`$1]-$HOME-"
which yields something like[a]-C:\Users\jdoe-bc
-
Build your string from literal pieces and variable references using string concatenation (
+
); e.g.:'abc' -replace '(a)', ('[$1]-' + $HOME + '-')
-
Use
-f
, the string-formatting operator string concatenation; e.g.:'abc' -replace '(a)', ('[$1]-{0}-' -f $HOME)
-
-
Given that you need to use
$$
to escape a literal$
in the replacement string, use the following idiom to use a variable whose value you want to use literally:... -replace <search>, $var.Replace('$', '$$')
- This relies on the
[string]::Replace()
method performing literal substring replacements.
On a side note, this method is an alternative to-replace
in simple cases, but note that it is case-sensitive by default. - Alternatively, use a nested
-replace
operation, but the syntax is tricky due to the escaping requirements:... -replace <search>, ($var -replace '\$', '$$$$')
Solution 2:
I highly recommend regex101.com for learning regex.
Something like this can work:
"VideoName_s01e01.mp4" -replace '(.*s)(\d+)(e.*)', '$1$2_$3'
Gives:
VideoName_s01_e01.mp4
Solution 3:
-replace
is using regex, not wildcards. Thus you have to change the replace to:
-replace '_s([0-9]{1,2})e([0-9]{1,2})', '_s$1_e$2_'