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?

Brandon
fuente
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
}
Robben_Ford_Fan_boy
fuente
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.

stej
fuente
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.
Tolga
fuente
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)  
}
Shay Levy
fuente
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 $_ }
           }
Kaye
fuente
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}
METRO--
fuente
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' 
}
Martin Brandl
fuente
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.

Geir Ivar Jerstad
fuente
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