1

I have a bunch of files, looking like this:

Prop.houses-2119-1
Prop.houses-2119-03

this should be renamed like this:

Prop<4-digits>house<2-digits>.house
Example:

Prop2119house01.house
Prop2119house03.house

So at the second block of numbers I have to add zeros if there is only one digit.

My code until now looks like this:

$propertyFiles = Get-ChildItem . *.property -rec
foreach ($file in $propertyFiles) {
(Get-Content $file) `
    -replace '(Prop.houses-)(\d+)(-)(\d+)', 'Prop$2house$4.house' `
    -replace 'second regex', 'second replacement' |
  Out-File $file
}

It is working fine, but I don't know how to add padLeft(2,'0') to the second block of numbers. It shows me the padLeft as a string.

1

5 Answers 5

2

You could use -match instead of -replace in this case, then using $Matches you can apply the zero left padding without issues:

$strings = @(
    'Prop.houses-2119-1'
    'Prop.houses-2119-03'
)

$strings | ForEach-Object {
    if ($_ -match '^(\D+)\.(\D+?)s?-(\d{4})-(\d{1,})$') {
        '{0}{1}{2}{3}.{2}' -f $Matches[1], $Matches[3], $Matches[2], $Matches[4].PadLeft(2, '0')
    }
}

In your code, applying this logic it would be:

foreach ($file in Get-ChildItem . *.property -rec) {
    (Get-Content $file) | ForEach-Object {
        # if the line matches the pattern
        if ($_ -match '^(\D+)\.(\D+?)s?-(\d{4})-(\d{1,})$') {
            # output the updated string
            '{0}{1}{2}{3}.{2}' -f $Matches[1], $Matches[3], $Matches[2], $Matches[4].PadLeft(2, '0')
            # and go next line
            return
        }

        # otherwise, output the line as is
        $_
    } | Out-File $file
}
Sign up to request clarification or add additional context in comments.

Comments

0

The potential duplicate indeed shows the technique needed to implement your intent:

  • You want to perform a dynamic operation on a part of the matched string, based on its specific value, so as to be able to use a (conditionally) transformed version of it in the output string produced by a -replace-based string-substitution operation.

  • This can not be done with a string as the substitution operand,[1] and instead requires a callback mechanism; in the context of PowerShell, it requires a script block ({ .. }), whose use with -replace is only supported in PowerShell (Core) 7+, however.

    • In Windows PowerShell, you must call the underlying .NET API, [regex]::Replace(), directly, as shown in the linked post.

    • The only substantial difference is in how the script block receives the match-information object (of type [Match]) that represents the matched string:

      • In PS 7+, with -replace, it is reflected in the automatic $_ variable (aka $PSItem).

      • With a direct [regex]::Replace() call, it is in the first positional argument, $args[0], via the automatic $args variable - though you're free to declare a parameter instead, as shown in the linked post (param($match)).

However, there's a twist to your requirements that to me warrants answering this question separately:

  • The need to not just perform a dynamic transformation, but to do so in the context of matching the input string as a whole and rearranging its - selectively transformed - substrings.

The immediate answer to your question - which is focused on an additional -replace operation, after having rearranged substrings - is the following:

# PS 7+ only
# Make sure that a single digit before '.house' is 0-padded to 2 digits.
# * $_ is a [System.Text.RegularExpressions.Match] instance with information
#   about the matched string.
# * $_.Value is the matched string itself, on which .PadLeft() can be called.
# -> 'Prop2119house01.house'
'Prop2119house1.house' -replace '(?<!\d)\d\b', { $_.Value.PadLeft(2, '0') }

Note:

  • This is essentially the same technique as in the linked post, but with the simplification of using a negative lookbehind assertion ((?<!...)) and a word-boundary assertion to (\b) so as to avoid having to use capture groups ((...)) - see this regex101.com page for an explanation of the regex and the option to experiment with it.

However, you can use this technique to make do with a single -replace operation, which combines rearranging substrings with selective dynamic transformations, based on matching and replacing the whole input string, with the substring of interest captured via capture groups ((...)):

# PS 7+ only

# Sample input lines.
$lines = 'Prop.houses-2119-1', 'Prop.houses-2119-03'

# * $_.Groups[1].Value is the text captured by the 1st capture group, and so on.
# -> 'Prop2119house01.house', 'Prop2119house03.house'
$lines -replace 'Prop\.houses-(\d+)-(\d+)', { 
  'Prop{0}house{1}.house' -f $_.Groups[1].Value, $_.Groups[2].Value.PadLeft(2, '0') 
}

Note the use of -f, the format operator, to assemble the final string; in the template string, {0} is a placeholder for the first RHS operand, and so on.


[1] For a detailed explanation, see the bottom section of this answer

Comments

0

If I understand this correctly...

$string = 'Prop.houses-2119-1'
$prop,$house,$year,$num = $string -split '\.|-'
$house = $house -replace 's$'
$num = [int]$num | % tostring 00
"$prop$year$house$num.$house"

Prop2119house01.house

Comments

-2

This can be achieved with basic string manipulation rather than a regex:

$files = Get-ChildItem "C:\Temp\.so" -File

foreach($f in $files)
{    
#splitting on the dashes isolates the numbers
    $splitOnDashes = $f.Name.split('-')

#splitting on the period isolates the Prop and Houses elements
    $propHouses = $splitOnDashes[0].Split('.')

#if the split element containing the one or two digit number is one number, pad a zero
    if($splitOnDashes[2].Length -eq 1)
    {
        $splitOnDashes[2] = "0$($splitOnDashes[2])"
    }
# bring all the elements together and rename the file, whilst also trimming the 's' from 'houses'.    
    Rename-Item $f.FullName "$($propHouses[0])$($splitOnDashes[1])$($propHouses[1].TrimEnd('s'))$($splitOnDashes[2]).$($propHouses[1].TrimEnd('s'))" -Force
}

Comments

-2

Yet another way is to use switch to loop over the lines in each file and update the lines that match a certain regex pattern:

$propertyFiles = Get-ChildItem . '*.property' -File -Recurse
foreach ($file in $propertyFiles) {
    $newContent = switch -Regex -File $file.FullName {
        # update strings that match this pattern
        '(Prop)\.?(house).*(\d{4})-?(\d+)$' { 
            '{0}{1}{2}{3:D2}.{2}' -f $Matches[1], $Matches[3], $Matches[2], [int]$Matches[4]
        }
        default { $_ }  # output unchanged when no match
    }
    $newContent | Set-Content -Path $file.FullName
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.