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?
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?
Respostas:
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
(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 ...
Set-Content
vez de você, Out-File
você recebe um aviso como "O processo não pode acessar o arquivo '123.csv' porque está sendo usado por outro processo." .
Get-Content
parênteses, funciona. Você pode explicar em sua resposta por que o parêntese é necessário? Eu ainda substituiria Out-File
por Set-Content
porque é mais seguro; ele protege você de eliminar o arquivo de destino se você esquecer os parênteses.
Set-Content
vez de Out-File
é uma solução muito melhor e mais segura. Desculpe ter que votar.
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.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
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 $_
}
foreach
você pode fazer isso #Get-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
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 $Directory
está o diretório que contém os arquivos que você deseja modificar.
É 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.
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'
$myFile
?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
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
#>
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
Isso funcionou para mim usando o diretório de trabalho atual no PowerShell. Você precisa usar a FullName
propriedade 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 CSPROJ
arquivos.
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
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
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"}
set-content test.txt "hello hello world hello world hello"
e então (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
não esvaziará o arquivo como sugerido neste.