Con PowerShell, quiero reemplazar todas las ocurrencias exactas de [MYID]
un archivo determinado con MyValue
. ¿Cuál es la forma más fácil de hacerlo?
Con PowerShell, quiero reemplazar todas las ocurrencias exactas de [MYID]
un archivo determinado con MyValue
. ¿Cuál es la forma más fácil de hacerlo?
Respuestas:
Uso (versión V3):
(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt
O 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
Tenga en cuenta que los paréntesis (Get-Content file.txt)
son obligatorios:
Sin el paréntesis, el contenido se lee, una línea a la vez, y fluye por la tubería hasta que llega al archivo de salida o al contenido del conjunto, que intenta escribir en el mismo archivo, pero ya está abierto por get-content y obtienes un error. El paréntesis hace que la operación de lectura de contenido se realice una vez (abrir, leer y cerrar). Solo entonces, cuando se hayan leído todas las líneas, se canalizarán de una en una y cuando lleguen al último comando en la tubería, se pueden escribir en el archivo. Es lo mismo que $ content = content; $ contenido | dónde ...
Set-Content
lugar de a Out-File
usted, aparece una advertencia como "El proceso no puede acceder al archivo '123.csv' porque está siendo utilizado por otro proceso". .
Get-Content
paréntesis funciona. ¿Puedes explicar en tu respuesta por qué es necesario el paréntesis? Me gustaría volver a reemplazar Out-File
con Set-Content
porque es más seguro; te protege de borrar el archivo de destino si olvidas el paréntesis.
Set-Content
lugar de Out-File
es una solución mucho mejor y más segura. Lo siento, tengo que votar a favor.
Prefiero usar la clase de archivo de .NET y sus métodos estáticos como se ve en el siguiente ejemplo.
$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)
Esto tiene la ventaja de trabajar con una sola cadena en lugar de una serie de cadenas como con Get-Content . Los métodos también se encargan de la codificación del archivo (UTF-8 BOM , etc.) sin que tenga que preocuparse la mayor parte del tiempo.
Además, los métodos no estropean las terminaciones de línea (terminaciones de línea Unix que podrían usarse) en contraste con un algoritmo que usa Get-Content y pasa a Set-Content .
Entonces para mí: Menos cosas que podrían romperse con los años.
Algo poco conocido al usar clases .NET es que cuando ha escrito "[System.IO.File] ::" en la ventana de PowerShell, puede presionar la Tabtecla para recorrer los métodos allí.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
El anterior solo se ejecuta para "Un archivo", pero también puede ejecutarlo para varios archivos dentro de su carpeta:
Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '[MYID]', 'MyValue' }) |
Set-Content $_
}
foreach
, puede hacerloGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
foreach
, porque Get-Content hace algo que no podrías esperar ... Devuelve una matriz de cadenas, donde cada cadena es una línea en el archivo. Si está recorriendo un directorio (y subdirectorios) que se encuentran en una ubicación diferente a su script en ejecución, querrá algo como esto: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }
dónde $Directory
está el directorio que contiene los archivos que desea modificar.
Esto es lo que uso, pero es lento en archivos de texto grandes.
get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile
Si va a reemplazar cadenas en archivos de texto grandes y la velocidad es una preocupación, considere usar System.IO.StreamReader y 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()
}
}
(El código anterior no ha sido probado).
Probablemente haya una forma más elegante de usar StreamReader y StreamWriter para reemplazar texto en un documento, pero eso debería proporcionarle un buen punto de partida.
Encontré una forma poco conocida pero asombrosa de hacerlo desde Windows Powershell en acción de Payette . Puede hacer referencia a archivos como variables, similares a $ env: path, pero debe agregar las llaves.
${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
$myFile
?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
Si necesita reemplazar cadenas en varios archivos:
Cabe señalar que los diferentes métodos publicados aquí pueden ser muy diferentes con respecto al tiempo que lleva completar. Para mí, regularmente tengo grandes cantidades de archivos pequeños. Para probar cuál es el más eficiente, extraje 5.52 GB (5,933,604,999 bytes) de XML en 40,693 archivos separados y analicé tres de las respuestas que encontré aquí:
## 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 a @ rominator007
Lo envolví en una función (porque es posible que desee usarlo nuevamente)
function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
$content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
[System.IO.File]::WriteAllText("$FullPathToFile", $content)
}
NOTA: ¡Esto NO distingue entre mayúsculas y minúsculas!
Ver esta publicación: String . Reemplazar ignorando mayúsculas y minúsculas
Esto funcionó para mí usando el directorio de trabajo actual en PowerShell. Necesita usar la FullName
propiedad, o no funcionará en PowerShell versión 5. Necesitaba cambiar la versión de destino de .NET framework en TODOS mis CSPROJ
archivos.
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
Un poco viejo y diferente, ya que necesitaba cambiar una línea determinada en todas las instancias de un nombre de archivo en particular.
Además, Set-Content
no estaba devolviendo resultados consistentes, así que tuve que recurrir a Out-File
.
Código a continuación:
$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
}
Esto es lo que funcionó mejor para mí en esta versión de PowerShell:
Major.Minor.Build.Revision
5.1.16299.98
Pequeña corrección para el comando Establecer contenido. Si no se encuentra la cadena buscada, laSet-Content
comando dejará en blanco (vacío) el archivo de destino.
Primero puede verificar si la cadena que está buscando existe o no. Si no, no reemplazará 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"
y luego (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
no vaciará el archivo como se sugiere en esto.