Como substituir várias strings em um arquivo usando PowerShell


106

Estou escrevendo um script para personalizar um arquivo de configuração. Quero substituir várias instâncias de strings neste arquivo e tentei usar o PowerShell para fazer o trabalho.

Ele funciona bem para uma única substituição, mas fazer várias substituições é muito lento porque a cada vez é necessário analisar o arquivo inteiro novamente, e esse arquivo é muito grande. O script é semelhante a este:

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

Eu quero algo assim, mas não sei como escrever:

$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

Respostas:


167

Uma opção é encadear as -replaceoperações. O `no final de cada linha escapa da nova linha, fazendo com que o PowerShell continue analisando a expressão na próxima linha:

$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

Outra opção seria atribuir uma variável intermediária:

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

Pode $ original_file == $ destination_file? Como em estou modificando o mesmo arquivo da minha fonte?
cquadrini

Por causa da maneira como os cmdlets do PowerShell transmitem sua entrada / saída, não acredito que funcionaria gravar no mesmo arquivo no mesmo pipeline. No entanto, você pode fazer algo assim $c = Get-Content $original_file; $c | ... | Set-Content $original_file.
dahlbyk

Você tem problemas com a codificação de arquivos usando Set-Content que não mantém a codificação original? Codificações UTF-8 ou ANSI, por exemplo.
Kiquenet

1
Sim, PowerShell é ... inútil assim. Você mesmo deve detectar a codificação, por exemplo, github.com/dahlbyk/posh-git/blob/…
dahlbyk

24

Para fazer a postagem de George Howarth funcionar corretamente com mais de uma substituição, você precisa remover a quebra, atribuir a saída a uma variável (linha $) e, em seguida, gerar a variável:

$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

Esta é de longe a melhor abordagem que vi até agora. O único problema é que primeiro tive que ler todo o conteúdo do arquivo para uma variável, a fim de usar os mesmos caminhos de arquivo de origem / destino.
angularsen

esta parece ser a melhor resposta, embora eu tenha visto um comportamento estranho com ela correspondendo incorretamente. ou seja, no caso de você ter uma tabela hash com valores hexadecimais como strings (0x0, 0x1, 0x100, 0x10000) e 0x10000 corresponderá a 0x1.
Lorek

13

Com a versão 3 do PowerShell, você pode encadear as chamadas de substituição:

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

Funciona bem + sabor fluente
hdoghmen

10

Supondo que você só possa ter um 'something1'ou 'something2', etc. por linha, você pode usar uma tabela de pesquisa:

$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 você pode ter mais de um desses, apenas remova o breakna ifinstrução.


Vejo que TroyBramley adicionou $ linha logo antes da última linha para escrever qualquer linha que não tivesse alterações. OK. No meu caso, só mudei todas as linhas que precisam de substituição.
Cliffclof de

8

Uma terceira opção, para um one-liner com pipeline, é aninhar os -replaces:

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

E:

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

Isso preserva a ordem de execução, é fácil de ler e se encaixa perfeitamente em um pipeline. Eu prefiro usar parênteses para controle explícito, autodocumentação, etc. Funciona sem eles, mas até que ponto você confia nisso?

-Replace é um operador de comparação, que aceita um objeto e retorna um objeto supostamente modificado. É por isso que você pode empilhá-los ou aninhá-los conforme mostrado acima.

Por favor, veja:

help about_operators
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.