Matt Holmen
Matt Holmen

Reputation: 47

Regex that will replace a block of text in all files

I have a list of files all with the extension .pkg as part of a product. Within each of those files is a section of text that looks like this:

//VERSION
1.73
//END VERSION

I am trying to write a PowerShell script that will find that block of code and replace the 1.73 to 1.74.

//VERSION
1.74
//END VERSION

What I have found is the following, but I cannot seem to get the regex portion write to identify the block of code.

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$dir = $scriptPath + "\Sandbox\" 

Write-Host "*** Modifying Files In $dir to Update Version ***"
Get-ChildItem -Path $dir | ?{$_.Extension -eq ".pkg"} | 
  ForEach-Object {
      $copyto = $dir + $_
      # Load the file's contents, replace commas with spaces
      (Get-Content $copyto) -replace '(?<=//VERSION\r").*?(?="\r//END VERSION)', '1.73' |
         # and write it to the correct folder and file name
         Out-File $copyto             
   }

I know the basic syntax works as I am using this exact same code to loop through and replace something else more easily identified.

UPDATED WORKING CODE

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$dir = Join-Path $scriptPath "Sandbox"


Write-Host "*** Modifying Files In $dir to Update Version ***"
Get-ChildItem -Path $dir | ?{$_.Extension -eq ".pkg"} | 
  ForEach-Object {
      $copyto = Join-Path $dir $_
      $foundLine = $false
      Get-Content $copyto | foreach {

        if ($foundLine) {
          # Flag is set. Output the following instead of the line from the file
          '1.74'

          # And clear the flag
          $foundLine = $false

        } else { 

          # Output current line
          $_

          # If we find the version line, set the flag so that
          # we enter the replacement on the next line.
          if ($_ -eq '//VERSION') {
            $foundLine = $true
          }
        }
      } | Out-File ($copyto + '.new')

      Write-Host "*** Deleting $copyto ***"
      Remove-Item $copyto

      Write-Host "*** Renaming ($copyto + '.new') ***"
      Rename-Item -Path ($copyto + '.new') -NewName $copyto

   }

Upvotes: 1

Views: 547

Answers (2)

Ryan Bemrose
Ryan Bemrose

Reputation: 9266

By default, Get-Content returns the file as an array of strings. Each line is checked independently against your regex. Since the pattern you're trying to match crosses more than one line, no single line will ever match the whole thing.

To treat the entire file as one string with embedded newlines, pass the -Raw parameter

(Get-Content $copyto -Raw) -replace ...

Here is an alternate solution that operates line by line and doesn't use a regex. Longer to type, but easier to understand for people not used to regular expressions.

$foundLine = $false
Get-Content $copyto | foreach {

  if ($foundLine) {
    # Flag is set. Output the following instead of the line from the file
    '1.74'

    # And clear the flag
    $foundLine = $false

  } else { 

    # Output current line
    $_

    # If we find the version line, set the flag so that
    # we enter the replacement on the next line.
    if ($_ -eq '//VERSION') {
      $foundLine = $true
    }
  }
} | Out-File ($copyto + '.new')

Unrelated: When concatenating paths, prefer Join-Path over string concatenation. It helps avoid missing backslash issues and makes your code more robust.

$dir = Join-Path $scriptPath "Sandbox"
...
$copyto = Join-Path $dir $_

Upvotes: 1

Martin Brandl
Martin Brandl

Reputation: 59031

Like Ryan showed you, you have to use the -raw switch for the Get-Content cmdlet. However, there are some more errors within your regex:

  1. You have to escape the slashes using a backslash.
  2. You should use \r?\n to read a new line unless you are know what new line character is used.
  3. You have two double quotes in your regex which doesn't fit there.
  4. You are replacing the actual value with 1.73 but you probably want to change it to 1.74

So replace this line:

(Get-Content $copyto -raw) -replace '(?<=\/\/VERSION)(\r?\n).*?(\r?\n)(?=\/\/END VERSION)', '${1}1.74${2}'

Upvotes: 1

Related Questions