Как заменить несколько строк в файле с помощью PowerShell


107

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

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

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
    $_ -replace 'something1', 'something1new'
    } | Set-Content $destination_file

Я хочу что-то подобное, но не знаю, как это написать:

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
    $_ -replace 'something1', 'something1aa'
    $_ -replace 'something2', 'something2bb'
    $_ -replace 'something3', 'something3cc'
    $_ -replace 'something4', 'something4dd'
    $_ -replace 'something5', 'something5dsf'
    $_ -replace 'something6', 'something6dfsfds'
    } | Set-Content $destination_file

Ответы:


167

Один из вариантов - связать -replaceоперации вместе. Знак `в конце каждой строки экранирует новую строку, заставляя PowerShell продолжить синтаксический анализ выражения в следующей строке:

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
    $_ -replace 'something1', 'something1aa' `
       -replace 'something2', 'something2bb' `
       -replace 'something3', 'something3cc' `
       -replace 'something4', 'something4dd' `
       -replace 'something5', 'something5dsf' `
       -replace 'something6', 'something6dfsfds'
    } | Set-Content $destination_file

Другой вариант - назначить промежуточную переменную:

$x = $_ -replace 'something1', 'something1aa'
$x = $x -replace 'something2', 'something2bb'
...
$x

Может $ original_file == $ destination_file? Например, я изменяю тот же файл, что и мой источник?
cquadrini

Из-за того, как командлеты PowerShell передают свой ввод / вывод, я не думаю, что сработает запись в один и тот же файл в том же конвейере. Однако вы могли бы сделать что-то вроде $c = Get-Content $original_file; $c | ... | Set-Content $original_file.
dahlbyk

У вас есть проблемы с кодировкой файлов с помощью Set-Content, которая не поддерживает исходную кодировку? Например, кодировки UTF-8 или ANSI.
Kiquenet

1
Да, PowerShell ... бесполезен. Вы должны сами определить кодировку, например github.com/dahlbyk/posh-git/blob/…
dahlbyk

24

Чтобы сообщение Джорджа Ховарта правильно работало с более чем одной заменой, вам нужно удалить разрыв, назначить вывод переменной ($ line), а затем вывести переменную:

$lookupTable = @{
    'something1' = 'something1aa'
    'something2' = 'something2bb'
    'something3' = 'something3cc'
    'something4' = 'something4dd'
    'something5' = 'something5dsf'
    'something6' = 'something6dfsfds'
}

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'

Get-Content -Path $original_file | ForEach-Object {
    $line = $_

    $lookupTable.GetEnumerator() | ForEach-Object {
        if ($line -match $_.Key)
        {
            $line = $line -replace $_.Key, $_.Value
        }
    }
   $line
} | Set-Content -Path $destination_file

Это, безусловно, лучший подход, который я видел до сих пор. Единственная проблема заключается в том, что мне пришлось сначала прочитать все содержимое файла в переменной, чтобы использовать те же пути к исходному / конечному файлу.
angularsen

это похоже на лучший ответ, хотя я видел какое-то странное поведение с неправильным соответствием. т.е. в случае, когда у вас есть хеш-таблица с шестнадцатеричными значениями в виде строк (0x0, 0x1, 0x100, 0x10000), и 0x10000 будет соответствовать 0x1.
Лорек

14

В версии 3 PowerShell вы можете связать вызовы замены вместе:

 (Get-Content $sourceFile) | ForEach-Object {
    $_.replace('something1', 'something1').replace('somethingElse1', 'somethingElse2')
 } | Set-Content $destinationFile

Прекрасно работает + плавный вкус
hdoghmen

10

Предполагая, что у вас может быть только одно 'something1'или 'something2'и т. Д. В строке, вы можете использовать таблицу поиска:

$lookupTable = @{
    'something1' = 'something1aa'
    'something2' = 'something2bb'
    'something3' = 'something3cc'
    'something4' = 'something4dd'
    'something5' = 'something5dsf'
    'something6' = 'something6dfsfds'
}

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'

Get-Content -Path $original_file | ForEach-Object {
    $line = $_

    $lookupTable.GetEnumerator() | ForEach-Object {
        if ($line -match $_.Key)
        {
            $line -replace $_.Key, $_.Value
            break
        }
    }
} | Set-Content -Path $destination_file

Если у вас может быть несколько из них, просто удалите breakв ifинструкции.


Я вижу, что Трой Брамли добавил $ line непосредственно перед последней строкой, чтобы записать любую строку, в которой не было изменений. Ладно. В моем случае я изменил только каждую строку, требующую замены.
cliffclof

8

Третий вариант для конвейерного однострочника - вложить -replaces:

PS> ("ABC" -replace "B","C") -replace "C","D"
ADD

И:

PS> ("ABC" -replace "C","D") -replace "B","C"
ACD

Это сохраняет порядок выполнения, легко читается и аккуратно вписывается в конвейер. Я предпочитаю использовать круглые скобки для явного контроля, самодокументирования и т.д. Он работает и без них, но насколько вы этому доверяете?

-Replace - это оператор сравнения, который принимает объект и возвращает предположительно измененный объект. Вот почему вы можете складывать или вкладывать их, как показано выше.

Посмотри пожалуйста:

help about_operators
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.