Регулярное выражение, которое заменит блок текста во всех файлах

#powershell

#powershell

Вопрос:

У меня есть список файлов с расширением .pkg как часть продукта. В каждом из этих файлов есть раздел текста, который выглядит следующим образом:

 //VERSION
1.73
//END VERSION
  

Я пытаюсь написать сценарий PowerShell, который найдет этот блок кода и заменит 1.73 на 1.74.

 //VERSION
1.74
//END VERSION
  

Я нашел следующее, но, похоже, я не могу получить запись части регулярного выражения для идентификации блока кода.

 $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 '(?<=//VERSIONr").*?(?="r//END VERSION)', '1.73' |
         # and write it to the correct folder and file name
         Out-File $copyto             
   }
  

Я знаю, что базовый синтаксис работает, поскольку я использую этот точно такой же код для перебора и замены чего-то другого, более легко идентифицируемого.

ОБНОВЛЕННЫЙ РАБОЧИЙ КОД

 $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

   }
  

Ответ №1:

По умолчанию Get-Content возвращает файл в виде массива строк. Каждая строка проверяется независимо от вашего регулярного выражения. Поскольку шаблон, который вы пытаетесь сопоставить, пересекает более одной строки, ни одна строка никогда не будет соответствовать всему этому.

Чтобы обработать весь файл как одну строку со встроенными символами новой строки, передайте -Raw параметр

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

Вот альтернативное решение, которое работает построчно и не использует регулярное выражение. Дольше набирать, но легче понять людям, не привыкшим к регулярным выражениям.

 $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')
  

Несвязанный: при объединении путей отдавайте предпочтение Join-Path конкатенации строк. Это помогает избежать проблем с отсутствием обратной косой черты и делает ваш код более надежным.

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

Комментарии:

1. Примечание: Join-Path отлично работает для двух строк пути. Добавление третьего приведет к неожиданным результатам. [io.path]::combine() было бы неплохо предложить и здесь.

2. Хорошая мысль @Matt. Оба варианта лучше, чем объединение строк.

3. Спасибо…. Я успешно модифицировал свой код, чтобы заменить этот раздел кода. Я тоже добавил некоторый код для переименования обратно в исходное имя файла, но теперь доволен тем, что я получил.

Ответ №2:

Как показал вам Райан, вы должны использовать -raw переключатель для командлета Get-Content. Однако в вашем регулярном выражении есть еще несколько ошибок:

  1. Вы должны избегать косых черт, используя обратную косую черту.
  2. Вы должны использовать r?n для чтения новой строки, если вы не знаете, какой символ новой строки используется.
  3. В вашем регулярном выражении есть две двойные кавычки, которые туда не помещаются.
  4. Вы заменяете фактическое значение на 1.73, но, вероятно, захотите изменить его на 1.74

Поэтому замените эту строку:

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

Комментарии:

1. # 1 кажется ненужным. [Прямая] косая черта — это не мета-символы, а обратная косая черта. Верно?