Como posso substituir todas as ocorrências de uma String em um arquivo pelo PowerShell?

289

Usando o PowerShell, desejo substituir todas as ocorrências exatas de [MYID]um determinado arquivo por MyValue. Qual é a maneira mais fácil de fazer isso?

amador
fonte
Para uma solução mais eficaz em termos de consumo de memória do que a oferecida nas respostas a esta pergunta, consulte Localizar e substituir em um arquivo grande .
Martin Prikryl

Respostas:

444

Use (versão V3):

(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt

Ou para V2:

(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
Loïc MICHEL
fonte
3
Obrigado - Recebo um erro "replace: A chamada do método falhou porque [System.Object []] não contém um método chamado 'replace'." Apesar?
amateur
\ como escape funciona no ps v4 que acabei de descobrir. Obrigado.
precisa saber é o seguinte
4
@ Rob tubo o resultado para set-conteúdo ou out-arquivo se você quiser salvar a modificação
Loïc MICHEL
2
Eu recebi o erro "Falha na chamada do método porque [System.Object []] não contém um método chamado 'substituir'." porque eu estava tentando executar a versão V3 em uma máquina que só tinha V2.
SFlagg
5
Aviso: A execução desses scripts em arquivos grandes (algumas centenas de megabytes) pode consumir uma quantidade razoável de memória. Apenas certifique-se que você tem espaço para a cabeça o suficiente se você uma execução em um servidor de produção: D
neoscribe
89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

Observe que os parênteses (Get-Content file.txt)são necessários:

Sem os parênteses, o conteúdo é lido, uma linha de cada vez, e flui pelo pipeline até atingir o arquivo de saída ou o conjunto de conteúdos, que tenta gravar no mesmo arquivo, mas já está aberto pelo get-content e você obtém um erro. O parêntese faz com que a operação de leitura de conteúdo seja executada uma vez (abrir, ler e fechar). Somente então, quando todas as linhas tiverem sido lidas, elas serão canalizadas uma de cada vez e, quando atingirem o último comando no pipeline, poderão ser gravadas no arquivo. É o mesmo que $ content = content; $ conteúdo | Onde ...

Shay Levy
fonte
5
Eu mudaria meu voto positivo para um voto negativo, se pudesse. No PowerShell 3, isso exclui silenciosamente todo o conteúdo do arquivo! Usando em Set-Contentvez de você, Out-Filevocê recebe um aviso como "O processo não pode acessar o arquivo '123.csv' porque está sendo usado por outro processo." .
Iain Samuel McLean Elder
9
Não deve acontecer quando o conteúdo de obtenção está entre parênteses. Eles fazem com que a operação abra, leia e feche o arquivo, para que o erro que você recebe não aconteça. Você pode testá-lo novamente com um arquivo de texto de amostra?
Shay Levy
2
Entre Get-Contentparênteses, funciona. Você pode explicar em sua resposta por que o parêntese é necessário? Eu ainda substituiria Out-Filepor Set-Contentporque é mais seguro; ele protege você de eliminar o arquivo de destino se você esquecer os parênteses.
Iain Samuel McLean Elder
6
Problema com a codificação de arquivo UTF-8 . Quando salva o arquivo, altera a codificação. Não é o mesmo. stackoverflow.com/questions/5596982/… . Acho set-content considera arquivo de codificação (como UTF-8). mas não fora do arquivo
Kiquenet
1
Esta solução é desnecessariamente enganosa e causou problemas quando a usei. Eu estava atualizando um arquivo de configuração que foi usado imediatamente pelo processo de instalação. O arquivo de configuração ainda estava retido pelo processo e a instalação falhou. Usar em Set-Contentvez de Out-Fileé uma solução muito melhor e mais segura. Desculpe ter que votar.
Martin Basista 11/08/2015
81

Prefiro usar a classe File do .NET e seus métodos estáticos, como visto no exemplo a seguir.

$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)

Isso tem a vantagem de trabalhar com uma única String em vez de uma string-array como no Get-Content . Os métodos também cuidam da codificação do arquivo (UTF-8 BOM , etc.) sem que você precise cuidar da maior parte do tempo.

Além disso, os métodos não atrapalham as terminações de linha (finais de linha Unix que podem ser usadas) em contraste com um algoritmo usando Get-Content e passando para Set-Content .

Então, para mim: menos coisas que poderiam quebrar ao longo dos anos.

Uma coisa pouco conhecida ao usar classes .NET é que, quando você digita "[System.IO.File] ::" na janela do PowerShell, pode pressionar a Tabtecla para percorrer os métodos existentes.

rominator007
fonte
Você também pode ver os métodos com o comando [System.IO.File] | gm
fbehrens
Por que esse método assume um caminho relativo C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian
É assim mesmo? Isso provavelmente tem algo a ver com a maneira como um .NET AppDomain é iniciado no PowerShell. Pode ser que o caminho atual não seja atualizado ao usar o cd. Mas isso não passa de um palpite. Eu não testei isso ou procurei.
rominator007
2
Isso também é muito mais fácil do que escrever códigos diferentes para diferentes versões do Powershell.
Willem van Ketwich
Este método também parece ser o mais rápido. Junte isso aos benefícios mencionados e à pergunta: "por que você gostaria de usar mais alguma coisa?"
DBADon
21

O exemplo acima é executado apenas para "Um arquivo", mas você também pode executá-lo para vários arquivos na sua pasta:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}
João V Hobbs Jr
fonte
Repare que eu usei .xml, mas você pode substituir com .txt
John V Hobbs Jr
Agradável. Como alternativa ao uso do interior, foreachvocê pode fazer isso #Get-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD
1
Na verdade, você precisa disso interno foreach, porque Get-Content faz algo que você não pode esperar ... Ele retorna uma matriz de seqüências de caracteres, onde cada sequência é uma linha do arquivo. Se você estiver percorrendo um diretório (e subdiretórios) que estão em um local diferente do seu script em execução, você desejará algo assim: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }onde $Directoryestá o diretório que contém os arquivos que você deseja modificar.
birdamongmen
1
Qual resposta é "a que está acima"?
Peter Mortensen
10

Você pode tentar algo como isto:

$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path 
$newText = $text -replace $word,$replacement
$newText > $path
Richard
fonte
7

É isso que eu uso, mas é lento em arquivos de texto grandes.

get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile

Se você vai substituir cadeias de caracteres em arquivos de texto grandes e a velocidade é uma preocupação, consulte o System.IO.StreamReader e System.IO.StreamWriter .

try
{
   $reader = [System.IO.StreamReader] $pathToFile
   $data = $reader.ReadToEnd()
   $reader.close()
}
finally
{
   if ($reader -ne $null)
   {
       $reader.dispose()
   }
}

$data = $data -replace $stringToReplace, $replaceWith

try
{
   $writer = [System.IO.StreamWriter] $pathToFile
   $writer.write($data)
   $writer.close()
}
finally
{
   if ($writer -ne $null)
   {
       $writer.dispose()
   }
}

(O código acima não foi testado.)

Provavelmente, existe uma maneira mais elegante de usar o StreamReader e o StreamWriter para substituir o texto em um documento, mas isso deve oferecer um bom ponto de partida.

Darrell Lloyd Harvey
fonte
Acho set-content considera arquivo de codificação (como UTF-8). mas não fora do arquivo stackoverflow.com/questions/5596982/…
Kiquenet
2

Eu achei uma maneira um pouco conhecida, mas incrivelmente legal de fazê-lo no Windows Powershell em Ação de Payette . Você pode fazer referência a arquivos como variáveis, semelhantes a $ env: path, mas precisa adicionar chaves.

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
js2010
fonte
E se o nome do arquivo estiver em uma variável como $myFile?
ΩmegaMan
@MegaMan hmm só isso até agora$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010 24/06
2

Se você precisar substituir seqüências de caracteres em vários arquivos:

Deve-se observar que os diferentes métodos publicados aqui podem ser muito diferentes em relação ao tempo que leva para ser concluído. Para mim, eu regularmente tenho um grande número de arquivos pequenos. Para testar o melhor desempenho, extraí 5,52 GB (5.933.604.999 bytes) de XML em 40.693 arquivos separados e executei três das respostas que encontrei aqui:

## 5.52 GB (5,933,604,999 bytes) of XML files (40,693 files) 

#### Test 1 - Plain Replace
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml).replace("'", " ") | Set-Content $xml
}
$end   = get-date
NEW-TIMESPAN Start $Start End $End
<#
TotalMinutes: 103.725113128333
#>

#### Test 2 - Replace with -Raw
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml -Raw).replace("'", " ") | Set-Content $xml
}
$end   = get-date
NEW-TIMESPAN Start $Start End $End
<#
TotalMinutes: 10.1600227983333
#>

#### Test 3 - .NET, System.IO
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
$txt = [System.IO.File]::ReadAllText("$xml").Replace("'"," ") 
[System.IO.File]::WriteAllText("$xml", $txt)
}
$end   = get-date
NEW-TIMESPAN Start $Start End $End
<#
TotalMinutes: 5.83619516833333
#>
DBADon
fonte
A questão era sobre a substituição de strings em um determinado arquivo, não em vários arquivos.
PL
1

Crédito para @ rominator007

Eu o envolvi em uma função (porque você pode usá-lo novamente)

function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
    $content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
    [System.IO.File]::WriteAllText("$FullPathToFile", $content)
}

NOTA: Isto NÃO diferencia maiúsculas de minúsculas !!!!!

Consulte esta publicação: String.Replace ignoring case

Kolob Canyon
fonte
0

Isso funcionou para mim usando o diretório de trabalho atual no PowerShell. Você precisa usar a FullNamepropriedade ou ela não funcionará no PowerShell versão 5. Eu precisava alterar a versão do .NET framework de destino em TODOS os meus CSPROJarquivos.

gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
 Set-Content "$($_.FullName)"}
SliverNinja - MSFT
fonte
0

Um pouco velho e diferente, pois eu precisava alterar uma determinada linha em todas as instâncias de um nome de arquivo específico.

Além disso, Set-Content não estava retornando resultados consistentes, então tive que recorrer aOut-File .

Código abaixo:


$FileName =''
$OldLine = ''
$NewLine = ''
$Drives = Get-PSDrive -PSProvider FileSystem
foreach ($Drive in $Drives) {
    Push-Location $Drive.Root
        Get-ChildItem -Filter "$FileName" -Recurse | ForEach { 
            (Get-Content $_.FullName).Replace($OldLine, $NewLine) | Out-File $_.FullName
        }
    Pop-Location
}

Foi o que funcionou melhor para mim nesta versão do PowerShell:

Major.Minor.Build.Revision

5.1.16299.98

Kalin Tashev
fonte
-1

Correção pequena para o comando Set-Content. Se a string pesquisada não for encontrada, oSet-Content comando deixará em branco (vazio) o arquivo de destino.

Você pode primeiro verificar se a string que você está procurando existe ou não. Caso contrário, não substituirá nada.

If (select-string -path "c:\Windows\System32\drivers\etc\hosts" -pattern "String to look for") `
    {(Get-Content c:\Windows\System32\drivers\etc\hosts).replace('String to look for', 'String to replace with') | Set-Content c:\Windows\System32\drivers\etc\hosts}
    Else{"Nothing happened"}
dan D
fonte
3
Bem-vindo ao StackOverflow! Por favor, use a formatação. Você pode ler este artigo se precisar de ajuda.
usar o seguinte
1
Isso não é verdade, se alguém usar a resposta correta e a substituição não for encontrada, ele ainda gravará o arquivo, mas não haverá alterações. Por exemplo, set-content test.txt "hello hello world hello world hello"e então (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtnão esvaziará o arquivo como sugerido neste.
Ciantic