Як замінити кілька рядків у файлі за допомогою 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 == $ призначення_файл? Як я змінюю той самий файл, що і мій джерело?
cquadrini

Через те, як командлети PowerShell передають свій вхід / вихід, я не вірю, що це буде працювати над записом у той самий файл у тому ж конвеєрі. Однак ти можеш зробити щось подібне $c = Get-Content $original_file; $c | ... | Set-Content $original_file.
дальбик

У вас є проблеми з кодуванням файлів за допомогою Set-Content, який не підтримує оригінальне кодування? Наприклад, кодування UTF-8 або ANSI.
Кікенет

1
Так, PowerShell - це так недобре. Ви повинні виявити кодування самостійно, наприклад, github.com/dahlbyk/posh-git/blob/…
dahlbyk

24

Щоб посада Джорджа Хоарта працювала належним чином з декількома замінами, потрібно усунути перерву, призначити вихідну змінну (рядок $) та вивести змінну:

$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.
Лорек

13

За допомогою версії 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виписці.


Я бачу, як TroyBramley додав $ рядок перед останнім рядком, щоб написати будь-який рядок, в якому не було змін. Добре. У моєму випадку я змінив лише кожен рядок, який потребував заміни.
cliffclof

8

Третім варіантом для конвеєра з одним конвеєром є вкладання гнізд:

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.