Come sostituire più stringhe in un file utilizzando PowerShell


107

Sto scrivendo uno script per personalizzare un file di configurazione. Voglio sostituire più istanze di stringhe all'interno di questo file e ho provato a utilizzare PowerShell per eseguire il lavoro.

Funziona bene per una singola sostituzione, ma eseguire più sostituzioni è molto lento perché ogni volta deve analizzare di nuovo l'intero file e questo file è molto grande. Lo script ha questo aspetto:

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

Voglio qualcosa di simile, ma non so come scriverlo:

$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

Risposte:


167

Un'opzione è concatenare le -replaceoperazioni insieme. Alla `fine di ogni riga sfugge la nuova riga, facendo sì che PowerShell continui ad analizzare l'espressione nella riga successiva:

$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

Un'altra opzione sarebbe assegnare una variabile intermedia:

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

Può $ original_file == $ destination_file? Come in sto modificando lo stesso file della mia fonte?
cquadrini

A causa del modo in cui i cmdlet di PowerShell trasmettono il loro input / output, non credo che funzionerebbe per scrivere sullo stesso file nella stessa pipeline. Tuttavia, potresti fare qualcosa di simile $c = Get-Content $original_file; $c | ... | Set-Content $original_file.
dahlbyk

Hai problemi con la codifica dei file utilizzando Set-Content che non mantiene la codifica originale? Ad esempio, codifiche UTF-8 o ANSI.
Kiquenet

1
Sì, PowerShell è ... inutile in questo modo. Devi rilevare la codifica da solo, ad esempio github.com/dahlbyk/posh-git/blob/…
dahlbyk

24

Per fare in modo che il post di George Howarth funzioni correttamente con più di una sostituzione, è necessario rimuovere l'interruzione, assegnare l'output a una variabile ($ line) e quindi visualizzare la variabile:

$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

Questo è di gran lunga l'approccio migliore che ho visto finora. L'unico problema è che ho dovuto leggere prima l'intero contenuto del file su una variabile per utilizzare gli stessi percorsi di file di origine / destinazione.
angularsen

questa sembra la risposta migliore, anche se ho visto un comportamento strano con una corrispondenza errata. cioè nel caso in cui si dispone di una tabella hash con valori esadecimali come stringhe (0x0, 0x1, 0x100, 0x10000) e 0x10000 corrisponderà a 0x1.
Lorek

13

Con la versione 3 di PowerShell puoi concatenare le chiamate di sostituzione insieme:

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

Funziona bene + sapore fluente
hdoghmen

10

Supponendo che tu possa avere solo uno 'something1'o 'something2', ecc. Per riga, puoi utilizzare una tabella di ricerca:

$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

Se si può avere più di uno di questi, è sufficiente rimuovere la breaknella ifdichiarazione.


Vedo che TroyBramley ha aggiunto $ line appena prima dell'ultima riga per scrivere qualsiasi riga che non avesse modifiche. Va bene. Nel mio caso ho cambiato solo ogni riga che necessitava di sostituzioni.
cliffcl del

8

Una terza opzione, per un one-liner pipeline, è annidare le -sostituzioni:

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

E:

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

Ciò preserva l'ordine di esecuzione, è facile da leggere e si adatta perfettamente a una pipeline. Preferisco usare le parentesi per il controllo esplicito, l'auto-documentazione, ecc. Funziona senza di loro, ma fino a che punto ti fidi?

-Replace è un operatore di confronto, che accetta un oggetto e restituisce un oggetto presumibilmente modificato. Questo è il motivo per cui puoi impilarli o annidarli come mostrato sopra.

Perfavore guarda:

help about_operators
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.