Leftpad doesn't work when using with regex in Powershell

330 Views Asked by At

Here are two code blocks which show the strange behavior that Leftpad does.

$array = "z", "fg"
$array -replace "(\w{1,2})", '$1'.PadLeft(2, "0")
# output: z, fg

$array = "z", "fg"
$array -replace "(\w{1,2})", '$1'.PadLeft(3, "0")
# output: 0z, 0fg

How comes the pad length not fixed?

EDIT: @LotPings

I ask this another question is because of your way of doing it when applied to rename-item statement will not affect files with brackets in its names.

$file_names = ls "G:\Donwloads\Srt Downloads\15" -Filter *.txt
# the files are: "Data Types _ C# _ Tutorial 5.txt", "If Statements (con't) _ C# _ Tutorial 15.txt"
$file_names | 
    ForEach{
        if ($_.basename -match '^([^_]+)_[^\d]+(\d{1,2})$')
            { 
            $file_names | 
                Rename-Item -NewName `
                    { ($_.BaseName -replace $matches[0], ("{0:D2}. {1}" -f [int]$matches[2],$matches[1])) + $_.Extension }
            } 
           }

# output: 05. Data Types.txt
#         If Statements (con't) _ C# _ Tutorial 15.txt

As for the .PadLeft, I thought that the regex replacement group is of string type, it should work with .PadLeft but it doesn't.

2

There are 2 best solutions below

2
On BEST ANSWER

Ansgars comment to your last question should have shown that your assumption on the order of actions was wrong.
And Lee_Dailey proved you wrong another time.

My answer to your previous question presented an alternative which also works here:

("Data Types _ C# _ Tutorial 5", "If Statements (con't) _ C# _ Tutorial 15") |
  ForEach{ 
    if ($_ -match '^([^_]+)_[^\d]+(\d{1,2})$'){
      "{0:D2}. {1}" -f [int]$matches[2],$matches[1]
    } 
  }

Sample output:

05. Data Types
15. If Statements (con't)

The last edit to your question is in fact a new question...

  • Rename-Item accepts piped input, so no ForEach-Object neccessary when also using
  • Where-Object with the -match operator to replace the if.
    the $Matches collection is supplied the same way.
  • I really don't know why you insist on using the -replace operator when building the NewName from scratch with the -format operator.

$file_names = Get-ChildItem "G:\Donwloads\Srt Downloads\15" -Filter *.txt

$file_names | Where-Object BaseName -match '^([^_]+)_[^\d]+(\d{1,2})$' |
    Rename-Item -NewName {"{0:D2}. {1}{2}" -f [int]$matches[2],$matches[1].Trim(),$_.Extension} -WhatIf
0
On

Several days after asking this question, I happen to figure out the problem.

The $number capture group references in -replace syntax are merely literal strings!

Powershell never treats them as anything special, but the Regex engine does. Look at the example below:

$array = "z", "fg"  
$array -replace "(\w{1,2})", '$1'.Length
#output: 2
#        2

Looks strange? How comes the $1 capture group has both Lengths of 2 with "z" and "fg"? The answer is that the length being calculated is the string $1 rather than "z","fg"!
Let's look at another example, this time lets replace a letter within the capture group, see what happens:

$array -replace "(\w{1,2})", '$1'.Replace("z", "6")
#output: z
#        fg

The output shows the .replace didn't apply to the capture group 1.

$array -replace "(\w{1,2})", '$1'.Replace("1", "6")
#output: $6
#        $6

See? The string being replaced is $1 itself.
Now the cause of the .padleft problem should be understood. PS pad the literal string $1 and show the result with the content of the group.
When I pad it with .Padleft(2, "0"), nothing happens because the "$1" itself has the length of 2.

$array -replace "(\w{1,2})", '$1'.PadLeft(2, "0")
# output: z
#         fg

If instead, I pad it with .Padleft(3, "0"), this time the pad method does take effect, it applies the extra "0" to $1 but shows the result with the "0" preceding the content of $1.

$array -replace "(\w{1,2})", '$1'.PadLeft(3, "0")
#output: 0z
#        0fg