Jak zamienić wiele ciągów w pliku przy użyciu programu PowerShell

107

Piszę skrypt do dostosowywania pliku konfiguracyjnego. Chcę zamienić wiele wystąpień ciągów w tym pliku i próbowałem użyć programu PowerShell do wykonania tego zadania.

Działa dobrze w przypadku pojedynczego zastąpienia, ale wykonywanie wielu zastępstw jest bardzo powolne, ponieważ za każdym razem musi ponownie przeanalizować cały plik, a ten plik jest bardzo duży. Skrypt wygląda następująco:

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

Chcę czegoś takiego, ale nie wiem, jak to napisać:

$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
Ivo Bosticky
źródło

Odpowiedzi:

167

Jedną z opcji jest połączenie -replaceoperacji razem. Na `końcu każdego wiersza ucieka znak nowej linii, powodując, że PowerShell kontynuuje analizowanie wyrażenia w następnej linii:

$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

Inną opcją byłoby przypisanie zmiennej pośredniej:

$x = $_ -replace 'something1', 'something1aa'
$x = $x -replace 'something2', 'something2bb'
...
$x
dahlbyk
źródło
Czy $ original_file == $ destination_file? Jak w przypadku modyfikowania tego samego pliku co moje źródło?
cquadrini
Ze względu na sposób, w jaki polecenia cmdlet programu PowerShell przesyłają strumieniowo dane wejściowe / wyjściowe, nie sądzę, aby zapisywanie do tego samego pliku w tym samym potoku działało. Możesz jednak zrobić coś takiego $c = Get-Content $original_file; $c | ... | Set-Content $original_file.
dahlbyk
Czy masz problemy z kodowaniem plików przy użyciu Set-Content, które nie zachowuje oryginalnego kodowania? Na przykład kodowanie UTF-8 lub ANSI.
Kiquenet,
1
Tak, PowerShell jest ... tak niepomocny. Musisz samodzielnie wykryć kodowanie, np. Github.com/dahlbyk/posh-git/blob/ ...
dahlbyk
24

Aby post George'a Howartha działał poprawnie z więcej niż jednym zamiennikiem, musisz usunąć przerwę, przypisać dane wyjściowe do zmiennej (linia $), a następnie wyprowadzić zmienną:

$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
TroyBramley
źródło
To zdecydowanie najlepsze podejście, jakie do tej pory widziałem. Jedynym problemem jest to, że musiałem najpierw odczytać całą zawartość pliku do zmiennej, aby użyć tych samych ścieżek plików źródłowych / docelowych.
angularsen
to wygląda na najlepszą odpowiedź, chociaż widziałem dziwne zachowanie z nieprawidłowym dopasowaniem. tj. w przypadku, gdy masz tablicę haszującą z wartościami szesnastkowymi jako łańcuchami (0x0, 0x1, 0x100, 0x10000) i 0x10000 będzie pasować do 0x1.
Lorek
13

W wersji 3 programu PowerShell można łączyć wywołania zamiany razem:

 (Get-Content $sourceFile) | ForEach-Object {
    $_.replace('something1', 'something1').replace('somethingElse1', 'somethingElse2')
 } | Set-Content $destinationFile
Ian Robertson
źródło
Działa dobrze + płynny smak
hdoghmen
10

Zakładając, że możesz mieć tylko jedną 'something1'lub 'something2'itd. W wierszu, możesz użyć tabeli przeglądowej:

$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

Jeśli możesz mieć więcej niż jeden z nich, po prostu usuń plik break w ifoświadczeniu.

George Howarth
źródło
Widzę, że TroyBramley dodał linię $ tuż przed ostatnią linią, aby zapisać dowolną linię, w której nie było żadnych zmian. W porządku. W moim przypadku zmieniłem tylko każdą linię wymagającą wymiany.
Cliffclof
8

Trzecią opcją w przypadku rurociągu jednowarstwowego jest zagnieżdżenie -zastępuje:

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

I:

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

Pozwala to zachować kolejność wykonania, jest łatwe do odczytania i zgrabnie pasuje do potoku. Wolę używać nawiasów do wyraźnej kontroli, autodokumentacji itp. Działa bez nich, ale jak bardzo temu ufasz?

-Replace to operator porównania, który akceptuje obiekt i zwraca przypuszczalnie zmodyfikowany obiekt. Dlatego możesz je układać w stos lub zagnieżdżać, jak pokazano powyżej.

Proszę zobaczyć:

help about_operators

źródło