Script de PowerShell para buscar y reemplazar todos los archivos con una extensión específica


123

Tengo varios archivos de configuración en Windows Server 2008 anidados como tales:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

En mi configuración, necesito hacer un reemplazo de cadena como tal:

<add key="Environment" value="Dev"/>

se convertirá:

<add key="Environment" value="Demo"/>

Pensé en usar secuencias de comandos por lotes, pero no había una buena manera de hacerlo, y escuché que con las secuencias de comandos de PowerShell puede realizar esto fácilmente. He encontrado ejemplos de buscar / reemplazar, pero esperaba una forma de atravesar todas las carpetas dentro de mi directorio C: \ Proyectos y encontrar cualquier archivo que termine con la extensión '.config'. Cuando encuentre uno, quiero que reemplace mis valores de cadena.

¿Algún buen recurso para descubrir cómo hacer esto o cualquier gurú de PowerShell que pueda ofrecer alguna información?


1
Háganos saber cómo le fue o si hubo algunos problemas de formato extraños con los archivos que debían abordarse. Una cosa buena del problema es que es una prueba sin afectar el código de producción
— Robben_Ford_Fan_boy

Respuestas:


178

Aquí un primer intento en la parte superior de mi cabeza.

$configFiles = Get-ChildItem . *.config -rec
foreach ($file in $configFiles)
{
    (Get-Content $file.PSPath) |
    Foreach-Object { $_ -replace "Dev", "Demo" } |
    Set-Content $file.PSPath
}

3
Me gustaría agregar que probar las soluciones proporcionadas funcionó, pero esta fue la más fácil de leer. Pude entregar esto a un compañero de trabajo y él pudo entender fácilmente lo que estaba sucediendo. Gracias por la ayuda
— Brandon

11
Para que esto funcione en archivos en subdirectorios, necesita ".PSPath". Curiosamente, cuando intenté hacer que esto funcionara sin un () alrededor de get-content, falló en el contenido de escritura porque el archivo estaba bloqueado.
— Frank Schwieterman

25
Versión corta (alias comunes utilizados):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
— Artyom

55
@Artyom no olvides el .después del ls. Fui picado por eso yo mismo.
— Pureferret

55
UnaAccessException no autorizada también puede causar carpetas debido a que eliminará * .config para que se ejecute en todos los archivos. Puede agregar el filtro a la -File Get-ChildItem ... Tomó un tiempo para averiguarlo
— Amir Katz

32

PowerShell es una buena opción;) Es muy fácil enumerar archivos en un directorio dado, leerlos y procesarlos.

El guión podría verse así:

Get-ChildItem C:\Projects *.config -recurse |
    Foreach-Object {
        $c = ($_ | Get-Content) 
        $c = $c -replace '<add key="Environment" value="Dev"/>','<add key="Environment" value="Demo"/>'
        [IO.File]::WriteAllText($_.FullName, ($c -join "`r`n"))
    }

Divido el código en más líneas para que pueda leerlo. Tenga en cuenta que puede usar Set-Content en lugar de[IO.File]::WriteAllText , pero agrega una nueva línea al final. Con WriteAllTextusted puede evitarlo.

De lo contrario, el código podría tener este aspecto: $c | Set-Content $_.FullName.


14

Este enfoque funciona bien:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • Cambie "viejo" y "nuevo" a sus valores correspondientes (o use variables).
  • No olvide el paréntesis, sin el cual recibirá un error de acceso.

Así que elegí este para una expresión sucinta, pero tuve que reemplazarlo Get-Content $_con Get-Content $_.FullNameel equivalente para Set-Contentmanejar archivos que no estaban en la raíz.
— Matt Whitfield

11

Yo iría con xml y xpath:

dir C:\Projects\project_*\project*.config -recurse | foreach-object{  
   $wc = [xml](Get-Content $_.fullname)
   $wc.SelectNodes("//add[@key='Environment'][@value='Dev']") | Foreach-Object {$_.value = 'Demo'}  
   $wc.Save($_.fullname)  
}

11

Este ejemplo de PowerShell busca todas las instancias de la cadena "\ foo \" en una carpeta y sus subcarpetas, reemplaza "\ foo \" con "\ bar \" Y NO ESCRIBE los archivos que no contienen la cadena "\ foo \ "De esta manera no destruye los sellos de fecha y hora de la última actualización del archivo donde no se encontró la cadena:

Get-ChildItem  -Path C:\YOUR_ROOT_PATH\*.* -recurse 
 | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\foo\\') 
           {(Get-Content $_ | ForEach {$_ -replace '\\foo\\', '\bar\'}) | Set-Content $_ }
           }

7

Encontré útil el comentario de @Artyom pero desafortunadamente no ha publicado una respuesta.

Esta es la versión corta, en mi opinión la mejor versión, de la respuesta aceptada;

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}

1
En caso de que alguien más se encuentre con esto, como lo hice yo, buscando ejecutar esto directamente desde un archivo por lotes, puede ser útil usarlo en foreach-objectlugar del %alias al ejecutar un comando como este. De lo contrario, puede provocar el error:Expressions are only allowed as the first element of a pipeline
— Dustin Halstead

Corto no siempre es mejor, es más claro lo que está sucediendo en la versión larga.
— Nick N.

@NickN. Pues bien. Depende de cuál sea tu objetivo. Lo marcaría como POB;)
— M--

6

He escrito una pequeña función auxiliar para reemplazar el texto en un archivo:

function Replace-TextInFile
{
    Param(
        [string]$FilePath,
        [string]$Pattern,
        [string]$Replacement
    )

    [System.IO.File]::WriteAllText(
        $FilePath,
        ([System.IO.File]::ReadAllText($FilePath) -replace $Pattern, $Replacement)
    )
}

Ejemplo:

Get-ChildItem . *.config -rec | ForEach-Object { 
    Replace-TextInFile -FilePath $_ -Pattern 'old' -Replacement 'new' 
}

4

Al realizar un reemplazo recursivo, se debe incluir la ruta y el nombre de archivo:

Get-ChildItem -Recurse | ForEach {  (Get-Content $_.PSPath | 
ForEach {$ -creplace "old", "new"}) | Set-Content $_.PSPath }

Esto reemplazará todo "viejo" con "nuevo" distingue entre mayúsculas y minúsculas en todos los archivos de sus carpetas de su directorio actual.


La parte ".PSPath" de tu respuesta realmente me ayudó. Pero tuve que cambiar el "{$" interno a "$ _". Editaría su respuesta, pero no estoy usando su parte -creplace - estoy usando la respuesta aceptada con .PSPath
— aaaa bbbb
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.