Renaming files by reformatting existing filenames - placeholders in replacement strings used with the -replace operator
file, powershell, rename, replace
Solution
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@@'`
- If you want to replace a literal string, you must either individually `\`-escape any regex metacharacters (e.g. `.`) or use the result from a `[regex]::Escape()` call:
- `'bar.none' -replace '\.', '-'` -> `'bar-none'`
- `'bar.none' -replace [regex]::Escape('.'), '-'` -> `'bar-none'`
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:
- `'Level 41' -replace '\d+', { 1 + $_.Value }` -> `'Level 42'`
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 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` 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 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
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 '\$', '$$$$')`
Problem
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.