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
Ivo Bosticky
fuente

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
Dalbyk
fuente
¿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
TroyBramley
fuente
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
Ian Robertson
fuente
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.

George Howarth
fuente
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

fuente