0

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.

preachers
  • 373
  • 1
  • 5
  • 15
  • 1
    i don't know WHY, but i do know how to get past it - enclose the entire `-replace` in parens. something like this ... `($array -replace "(\w{1,2})", '$1').PadLeft(2, "0")`. i presume there is something that isn't quite ready when the `.PadLeft()` is triggered. – Lee_Dailey Dec 31 '18 at 22:18
  • @Lee_Dailey That surely solved the problem in my example, but what if the code becomes to this? `@("Data Types _ C# _ Tutorial 5", "If Statements (con't) _ C# _ Tutorial 15") | %{$_ -replace '^([^_]+)_[^\d]+(\d{1,2})$', ('$2'.PadLeft(2, "0") + ". " + '$1')}` – preachers Dec 31 '18 at 22:32
  • you are NOT doing it the way that i showed ... [*grin*] the `.PadLeft()` _MUST_ be applied to the **_result_** of the replace, not as part of the replace. so you need to wrap the _entire_ replace in parens and _then_ apply the `.PadLeft()`. ///// the sequence of operations is the key ... [*grin*] i///// if your capture groups doh't allow that, then you will need to use a format string to build the desired result from the capture groups. – Lee_Dailey Dec 31 '18 at 23:17
  • @Lee_Dailey I see your point, but the whole replace contain both the index number and the file name which is a sequence of words. What I really want to do is to just pad the number part. I've tried formatting string in my last question LotPings mentioned, but the capture group with pure number matches won't be converted to an integer by `[int]'$2'`. – preachers Jan 01 '19 at 00:28
  • look into using the `$Matches[ – Lee_Dailey Jan 01 '19 at 00:38

2 Answers2

2

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
  • See my edited answer. If the output looks OK remove the trailing `-WhatIf`.BTW If an answer solves your question or you find it helpful you should consider to [accept the answer](http://stackoverflow.com/help/accepted-answer) and/or [vote up](https://stackoverflow.com/help/why-vote) –  Jan 01 '19 at 00:56
  • Thanks! It works! Please bear with me, I'm not _insist on using the -replace operator_, I'm new to PS :) – preachers Jan 01 '19 at 01:07
0

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
preachers
  • 373
  • 1
  • 5
  • 15