Как узнать, действительно ли "-replace" что-то сделал - PullRequest
0 голосов
/ 28 февраля 2019

Я довольно хорошо знаком с PowerShell, и я использую Get-Content, переданный в выражение для замены строки.Однако я хотел бы записать «новый» контент обратно в файл, если что-то действительно изменилось.

Я использовал простое сравнение содержимого нового / старого файла, но на больших файлах это довольно медленно(ужасно медленно)Мне пришло в голову, что замена регулярного выражения на самом деле выполняется довольно быстро, поэтому, если есть какой-то способ спросить PowerShell после замены, было ли что-то найдено, это было бы идеально.Если не было внесено никаких изменений, просто не записывайте файл обратно.

Я попытался протестировать $Matches.count, но я получил

Переменная '$ Matches' не может быть получена, потому чтооно не было установлено.

Я что-то упустил?Я действительно не хотел искать спички, прежде чем делать замену, потому что это также кажется расточительным (и нарушает принцип «говори-не-спрашивай»).

Вот как я это делаю сейчас:

function Convert-ToUTF8 {
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [string[]] $File)
    process {
        $File |
            %{
                $fileFullName = $_

                Write-Verbose "Loading $filefullname..."
                $content = (Get-Content $fileFullName)

                Write-Verbose "   Fixing xml prolog..."
                $newcontent = $content -replace '^<\?xml version="(\d+\.\d+)" encoding="(.+)"\?>$', '<?xml version="$1" encoding="UTF-8"?>'

                Write-Verbose "   Checking to see if there were changes..."
                $changed = $newcontent -ne $content

                if ($changed) {
                    if ($PSCmdlet.ShouldProcess("Write changes to $filefullname")) {
                        Write-Host "Writing changes to $filefullname..."
                        $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
                        [System.IO.File]::WriteAllLines($fileFullName, $newcontent, $Utf8NoBomEncoding)
                    }
                } else {
                    Write-Host "No changes to $filefullname."
                }
            }
    }
}

Ответы [ 2 ]

0 голосов
/ 28 февраля 2019

Было отмечено, что правильный синтаксический анализ XML, как правило, предпочтительнее, но вы пояснили, что вы сохраняете точное форматирование входного файла для целей без отвлечения рассылки позже.


Действительно, единственный способ определить, действительно ли операция -replace выполнила замену, - сравнить входную строку со строкой результата.

(As Матиас Джессен указывает, что только оператор -matchswitch -regex) заполняет автоматическую переменную $Matches, отражающую результаты операции сопоставления с регулярным выражением).

В простейшем случае:

$original = 'foo'
$potentiallyModified = $original -replace 'x', 'y'

$replacementWasMade = $original -cne $potentiallyModified

Примечание:

  • -cne вместо -ne используется для сравнения с учетом регистра , а также для обнаруженияслучай, когда замена только изменила регистр входной строки.

  • Возможно, могла быть выполнена эффективная безоперационная замена (например, 'foo' -replace 'o', 'o'), который выше не обнаруживает;Тем не менее, в то время как новый экземпляр [string] возвращается в таких случаях, это обычно не имеет значения, учитывая, что строки обычно сравниваются по равенству значение , а не ссылка равенство - см.ниже.

Если производительность имеет первостепенное значение в этом сценарии - я сомневаюсь, что это имеет значение в большинстве случаев - вы можете применить следующую микрооптимизацию , используя ( документированный ) тот факт, что если указанное регулярное выражение не соответствует, входная строка возвращается как есть (то же самое *)1055 * экземпляр):

$original = 'foo'
$potentiallyModified = $original -replace 'x', 'y'

# Test for reference equality.
$replacementWasMade = -not [object]::ReferenceEquals($original, $potentiallyModified)

Ваш конкретный случай использования:

Вы должны использовать $content = (Get-Content -Raw $fileFullName), то есть -Rawпереключите , чтобы прочитать входной файл как одну строку и выполнить операцию -replace с этой одной строкой .

В противном случае вы получите массив строк и поведение -eq изменяет с LHS со значением массива для выполнения фильтраering LHS вместо возврата логического значения.

Кроме того, -eq RHS будет также массивом (массивом строк с потенциально измененными строками), который приведен к одна строка с элементами, разделенными пробелом, что означает, что он не будет работать должным образом:

PS> 'foo', 'bar' -eq 'foo', 'bar'
 # !! NO OUTPUT

То есть RHS был приведен к единственной строке 'foo bar',который не соответствует ни одному элементу LHS, поэтому был возвращен пустой массив .

Что касается производительность :

Чтобы ускорить ввод-вывод файла, избегайтеКомандлеты и использовать типы .NET напрямую:

$content = [IO.File]::ReadAllText($fileFullName)
0 голосов
/ 28 февраля 2019

Попробуйте для сравнения:

$xmlContent = New-Object System.Xml.XmlDocument
$xmlContent = [xml]([System.IO.File]::ReadLines($filePath))

$header     = $xmlContent.xml
$headerNew  = $xmlContent.xml -replace 'version="(\d+\.\d+)" encoding="(.+)"', 'version="$1" encoding="UTF-8"' 

if( $header -ne $headerNew ) {
    $xmlContent.xml    = $headerNew
    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
    [void][System.IO.File]::WriteAllLines($filePath, $xmlContent.OuterXml, $Utf8NoBomEncoding)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...