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??
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
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), explained 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.
Notably,
PowerShell does not explain the syntax of replacement strings in its Get-Help about_comparison_operators help topic,
but the information can be found in the Substitutions in Regular Expressions .NET framework help topic, because PowerShell's -replace
uses the Regex.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 the 1
-based number
, where number is a decimal value, 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 their 1
-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 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 all the text of the input string before the match in the replacement string.
$'
... Includes all 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 a PowerShell variable, you have three options:
Use "..."
(expanding 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('$', '$$')
-replace
in simple cases, but note that it is case-sensitive by default.-replace
call, but the syntax is tricky due to the escaping requirements:... -replace <search>, ($var -replace '\$', '$$$$')
-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_'
regex is the better solution, i propose other with formating number and use Template
$exampleoffile = @"
namev1_s01e01.mp4
namvev2_s03e02.mp4
VideoName_s20e15.mp4
VideoName_s15e1.mp4
VideoName_s1e16.avi
VideoName_s1e23.mv
"@
$template=@"
{file*:{prefix:Test1}_s{season:01}e{episode:01}{extension:.mp4}}
{file*:{prefix:1xxx}_s{season:100}e{episode:5}{extension:.mp4}}
{file*:{prefix:yyy3}_s{season:10}e{episode:02}{extension:.havi}}
"@
$exampleoffile | ConvertFrom-String -TemplateContent $template |
% { "{0}_{1:D2}_e{2:D2}{3}" -f $_.file.prefix, [int]$_.file.season, [int]$_.file.episode, $_.file.extension }