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

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

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

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

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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.