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
Ivo Bosticky
fonte

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
Dahlbyk
fonte
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
TroyBramley
fonte
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
Ian Robertson
fonte
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.

George Howarth
fonte
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

fonte