Cómo reemplazar varias cadenas en un archivo usando PowerShell


106

Estoy escribiendo un script para personalizar un archivo de configuración. Quiero reemplazar varias instancias de cadenas dentro de este archivo e intenté usar PowerShell para hacer el trabajo.

Funciona bien para un solo reemplazo, pero hacer múltiples reemplazos es muy lento porque cada vez tiene que analizar todo el archivo nuevamente, y este archivo es muy grande. El guión se ve así:

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
    $_ -replace 'something1', 'something1new'
    } | Set-Content $destination_file

Quiero algo como esto, pero no sé cómo escribirlo:

$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

Respuestas:


167

Una opción es encadenar las -replaceoperaciones. El `al final de cada línea escapa de la nueva línea, lo que hace que PowerShell continúe analizando la expresión en la siguiente línea:

$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

Otra opción sería asignar una variable intermedia:

$x = $_ -replace 'something1', 'something1aa'
$x = $x -replace 'something2', 'something2bb'
...
$x

¿Puede $ original_file == $ destination_file? ¿Estoy modificando el mismo archivo que mi fuente?
cquadrini

Debido a la forma en que los cmdlets de PowerShell transmiten su entrada / salida, no creo que funcione escribir en el mismo archivo en la misma canalización. Sin embargo, podrías hacer algo como $c = Get-Content $original_file; $c | ... | Set-Content $original_file.
dahlbyk

¿Tiene problemas con la codificación de archivos usando Set-Content que no mantiene la codificación original? Codificaciones UTF-8 o ANSI, por ejemplo.
Kiquenet

1
Sí, PowerShell es ... tan inútil. Debe
dahlbyk

24

Para que la publicación de George Howarth funcione correctamente con más de un reemplazo, debe eliminar la ruptura, asignar la salida a una variable ($ line) y luego generar la variable:

$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

Este es, con mucho, el mejor enfoque que he visto hasta ahora. El único problema es que primero tuve que leer todo el contenido del archivo en una variable para usar las mismas rutas de archivo de origen / destino.
angularsen

esta parece la mejor respuesta, aunque he visto un comportamiento extraño con una coincidencia incorrecta. es decir, en el caso de que tenga una tabla hash con valores hexadecimales como cadenas (0x0, 0x1, 0x100, 0x10000) y 0x10000 coincidirá con 0x1.
Lorek

13

Con la versión 3 de PowerShell, puede encadenar las llamadas de reemplazo juntas:

 (Get-Content $sourceFile) | ForEach-Object {
    $_.replace('something1', 'something1').replace('somethingElse1', 'somethingElse2')
 } | Set-Content $destinationFile

Funciona bien + sabor fluido
hdoghmen

10

Suponiendo que solo puede tener uno 'something1'o 'something2', etc. por línea, puede usar una tabla de búsqueda:

$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

Si puede tener más de uno de esos, simplemente elimine el breaken la ifdeclaración.


Veo que TroyBramley agregó $ line justo antes de la última línea para escribir cualquier línea que no tuviera cambios. Bueno. En mi caso, solo cambié todas las líneas que necesitaban reemplazos.
Cliffclof

8

Una tercera opción, para un one-liner canalizado, es anidar los -replaces:

PS> ("ABC" -replace "B","C") -replace "C","D"
ADD

Y:

PS> ("ABC" -replace "C","D") -replace "B","C"
ACD

Esto preserva el orden de ejecución, es fácil de leer y encaja perfectamente en una tubería. Prefiero usar paréntesis para control explícito, auto-documentación, etc. Funciona sin ellos, pero ¿hasta qué punto confías en eso?

-Replace es un operador de comparación, que acepta un objeto y devuelve un objeto presuntamente modificado. Es por eso que puede apilarlos o anidarlos como se muestra arriba.

Por favor mira:

help about_operators
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.