PowerShell-Skript zum Suchen und Ersetzen für alle Dateien mit einer bestimmten Erweiterung

122

Ich habe mehrere Konfigurationsdateien unter Windows Server 2008 wie folgt verschachtelt:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

In meiner Konfiguration muss ich einen String wie folgt ersetzen:

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

wird werden:

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

Ich habe über die Verwendung von Batch-Skripten nachgedacht, aber es gab keine gute Möglichkeit, dies zu tun, und ich habe gehört, dass Sie dies mit PowerShell-Skripten problemlos durchführen können. Ich habe Beispiele für Suchen / Ersetzen gefunden, aber ich hatte gehofft, einen Weg zu finden, der alle Ordner in meinem Verzeichnis C: \ Projects durchläuft und alle Dateien findet, die mit der Erweiterung '.config' enden. Wenn es einen findet, möchte ich, dass es meine Zeichenfolgenwerte ersetzt.

Gibt es gute Ressourcen, um herauszufinden, wie dies funktioniert, oder PowerShell-Gurus, die Einblicke bieten können?

Brandon
quelle
1
Lassen Sie uns wissen, wie Sie sich verstanden haben oder ob es merkwürdige Formatierungsprobleme mit den Dateien gab, die behoben werden mussten. Eine gute Sache über das Problem ist, dass es Test ist, ohne den Produktionscode zu beeinflussen
Robben_Ford_Fan_boy

Antworten:

177

Hier ein erster Versuch auf meinem Kopf.

$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
quelle
3
Ich möchte hinzufügen, dass das Testen der bereitgestellten Lösungen funktioniert hat, aber dieses war für die Lesbarkeit am einfachsten. Ich konnte dies einem Kollegen geben und er konnte leicht verstehen, was los war. Danke für die Hilfe.
Brandon
11
Damit dies in Dateien in Unterverzeichnissen funktioniert, benötigen Sie ".PSPath". Interessanterweise ist es beim Schreiben von Inhalten fehlgeschlagen, als ich versuchte, diese Funktion ohne ein () um get-content zu erstellen, da die Datei gesperrt war.
Frank Schwieterman
25
Kurzversion (gebräuchliche Aliase):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
Artyom
5
@Artyom vergiss das .nach dem ls. Ich bin selbst davon gestochen worden.
Pureferret
5
UnauthorizedAccessException kann auch aufgrund von Ordnern dazu führen, dass Sie die * .config entfernen, die für alle Dateien ausgeführt werden soll. Sie können -File-Filter zum Get-ChildItem hinzufügen ... Es hat eine Weile gedauert, um es herauszufinden
Amir Katz
32

PowerShell ist eine gute Wahl;) Es ist sehr einfach, Dateien in einem bestimmten Verzeichnis aufzulisten, zu lesen und zu verarbeiten.

Das Skript könnte folgendermaßen aussehen:

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"))
    }

Ich habe den Code in mehrere Zeilen aufgeteilt, damit Sie ihn lesen können. Beachten Sie, dass Sie Set-Content anstelle von verwenden können [IO.File]::WriteAllText, aber am Ende eine neue Zeile hinzugefügt wird. Mit können WriteAllTextSie es vermeiden.

Andernfalls könnte der Code folgendermaßen aussehen : $c | Set-Content $_.FullName.

stej
quelle
14

Dieser Ansatz funktioniert gut:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • Ändern Sie "alt" und "neu" in die entsprechenden Werte (oder verwenden Sie Variablen).
  • Vergessen Sie nicht die Klammer - ohne die Sie einen Zugriffsfehler erhalten.
Tolga
quelle
Also ging ich mit diesem für prägnanten Ausdruck - aber ich hatte zu ersetzen Get-Content $_mit Get-Content $_.FullName, und das äquivalent für Set-Contentdafür Dateien zu behandeln , die nicht an der Wurzel waren.
Matt Whitfield
11

Ich würde mit xml und xpath gehen:

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
quelle
11

Dieses Powershell-Beispiel sucht nach allen Instanzen der Zeichenfolge "\ foo \" in einem Ordner und seinen Unterordnern, ersetzt "\ foo \" durch "\ bar \" und schreibt keine Dateien neu, die die Zeichenfolge "\ foo \" nicht neu schreiben "Auf diese Weise zerstören Sie nicht die Datums- / Uhrzeitstempel der letzten Aktualisierung der Datei, bei denen die Zeichenfolge nicht gefunden wurde:

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

Ich fand den Kommentar von @Artyom nützlich, aber leider hat er keine Antwort gepostet.

Dies ist meiner Meinung nach die Kurzversion der akzeptierten Antwort.

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}
M--
quelle
1
Falls jemand anderes darauf stößt, wie ich es getan habe - um dies direkt aus einer Batch-Datei auszuführen -, kann es hilfreich sein, bei der Ausführung eines solchen Befehls foreach-objectanstelle des %Alias zu verwenden . Andernfalls kann es zu folgendem Fehler kommen:Expressions are only allowed as the first element of a pipeline
Dustin Halstead
Kurz ist nicht immer besser, es ist klarer, was in der langen Version passiert.
Nick N.
@ NickN. Na richtig. Es hängt davon ab, was Ihr Ziel ist. Ich würde es als POB
kennzeichnen
6

Ich habe eine kleine Hilfsfunktion geschrieben, um Text in einer Datei zu ersetzen:

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

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

Beispiel:

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

Beim rekursiven Ersetzen müssen Pfad und Dateiname angegeben werden:

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

Dadurch werden alle "alten" durch "neue" ersetzt, wobei in allen Dateien Ihrer Ordner Ihres aktuellen Verzeichnisses zwischen Groß- und Kleinschreibung unterschieden wird.

Geir Ivar Jerstad
quelle
Der Teil ".PSPath" Ihrer Antwort hat mir wirklich geholfen. Aber ich musste das innere "{$" in "$ _" ändern. Ich würde Ihre Antwort bearbeiten, aber ich verwende Ihren Ersatzteil nicht - ich verwende die akzeptierte Antwort mit .PSPath
aaaa bbbb