¿Cómo puedo reemplazar cada aparición de una cadena en un archivo con PowerShell?

289

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?

aficionado
fuente
Para obtener una solución más efectiva en términos de consumo de memoria que la que se ofrece en las respuestas a esta pregunta, consulte Buscar y reemplazar en un archivo grande .
Martin Prikryl

Respuestas:

444

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
Loïc MICHEL
fuente
3
Gracias: aparece el error "reemplazar: la invocación del método falló porque [System.Object []] no contiene un método llamado 'reemplazar'". ¿aunque?
aficionado
\ como escape funciona en ps v4 que acabo de descubrir. Gracias.
ErikE
44
@rob canalice el resultado para establecer el contenido o el archivo externo si desea guardar la modificación
Loïc MICHEL
2
Recibí el error "La invocación del método falló porque [System.Object []] no contiene un método llamado 'reemplazar'". porque estaba tratando de ejecutar la versión V3 en una máquina que solo tenía V2.
SFlagg
55
Advertencia: ejecutar estos scripts en archivos grandes (unos cientos de megabytes más o menos) puede consumir una buena cantidad de memoria. Solo asegúrese de tener suficiente espacio para la cabeza si está ejecutando en un servidor de producción: D
neoscribe
89
(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 ...

Shay Levy
fuente
55
Cambiaría mi voto a favor si pudiera. ¡En PowerShell 3, esto elimina silenciosamente todo el contenido del archivo! Al usar en Set-Contentlugar de a Out-Fileusted, aparece una advertencia como "El proceso no puede acceder al archivo '123.csv' porque está siendo utilizado por otro proceso". .
Iain Samuel McLean Elder
99
No debería suceder cuando get-content está entre paréntesis. Causan que la operación abra, lea y cierre el archivo, por lo que el error que obtenga no debería ocurrir. ¿Puedes probarlo de nuevo con un archivo de texto de muestra?
Shay Levy
2
Con Get-Contentparéntesis funciona. ¿Puedes explicar en tu respuesta por qué es necesario el paréntesis? Me gustaría volver a reemplazar Out-Filecon Set-Contentporque es más seguro; te protege de borrar el archivo de destino si olvidas el paréntesis.
Iain Samuel McLean Elder
66
Problema con la codificación de archivos UTF-8 . Cuando guarda el archivo, cambia la codificación. No es el mísmo. stackoverflow.com/questions/5596982/… . Creo que set-content considera el archivo de codificación (como UTF-8). pero no fuera de archivo
Kiquenet
1
Esta solución es innecesariamente engañosa y causó problemas cuando la usé. Estaba actualizando un archivo de configuración que fue utilizado inmediatamente por el proceso de instalación. El archivo de configuración aún estaba retenido por el proceso y la instalación falló. Usar en Set-Contentlugar de Out-Filees una solución mucho mejor y más segura. Lo siento, tengo que votar a favor.
Martin Basista
81

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í.

rominator007
fuente
También puede ver los métodos con el comando [System.IO.File] | gm
fbehrens
¿Por qué este método asume una ruta relativa desde C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian
¿Es eso así? Eso probablemente tenga algo que ver con la forma en que se inicia un dominio de aplicación .NET dentro de PowerShell. Puede ser que la ruta actual no se actualice cuando se usa cd. Pero esto no es más que una suposición educada. No probé esto ni lo busqué.
rominator007
2
Esto también es mucho más fácil que escribir código diferente para diferentes versiones de Powershell.
Willem van Ketwich
Este método también parece ser el más rápido. Combine eso con los beneficios señalados y la pregunta debería ser, "¿por qué querría usar algo más?"
DBADon el
21

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 $_
}
John V Hobbs Jr
fuente
observe que usé .xml pero puede reemplazarlo por .txt
John V Hobbs Jr el
Agradable. Como alternativa al uso del interior foreach, puede hacerloGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD
1
En realidad, necesitas ese interior 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 $Directoryestá el directorio que contiene los archivos que desea modificar.
birdamongmen
1
¿Qué respuesta es "la de arriba"?
Peter Mortensen
10

Podrías probar algo como esto:

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

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.

Darrell Lloyd Harvey
fuente
Creo que set-content considera el archivo de codificación (como UTF-8). pero no Out-File stackoverflow.com/questions/5596982/…
Kiquenet
2

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'
js2010
fuente
¿Qué pasa si el nombre del archivo está en una variable como $myFile?
ΩmegaMan
@ ΩmegaMan hmm solo esto hasta ahora$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010
2

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
#>
DBADon
fuente
La pregunta era sobre el reemplazo de cadenas en un archivo dado, no en varios archivos.
PL
1

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

Cañón Kolob
fuente
0

Esto funcionó para mí usando el directorio de trabajo actual en PowerShell. Necesita usar la FullNamepropiedad, o no funcionará en PowerShell versión 5. Necesitaba cambiar la versión de destino de .NET framework en TODOS mis CSPROJarchivos.

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

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-Contentno 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

Kalin Tashev
fuente
-1

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"}
dan D
fuente
3
¡Bienvenido a StackOverflow! Utilice el formato, puede leer este artículo si necesita ayuda.
CodenameLambda
1
Esto no es cierto, si uno usa la respuesta correcta y no se encuentra el reemplazo, aún escribe el archivo, pero no hay cambios. Por ejemplo, set-content test.txt "hello hello world hello world hello"y luego (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtno vaciará el archivo como se sugiere en esto.
Ciantic