Wie kann ich jedes Vorkommen eines Strings in einer Datei durch PowerShell ersetzen?

289

Mit PowerShell möchte ich alle genauen Vorkommen [MYID]in einer bestimmten Datei durch ersetzen MyValue. Was ist der einfachste Weg, dies zu tun?

Amateur
quelle
Eine effektivere Lösung hinsichtlich des Speicherverbrauchs als in den Antworten auf diese Frage angegeben, finden Sie unter Suchen und Ersetzen in einer großen Datei .
Martin Prikryl

Antworten:

444

Verwendung (V3-Version):

(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt

Oder für V2:

(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
Loïc MICHEL
quelle
3
Danke - Ich erhalte die Fehlermeldung "Ersetzen: Methodenaufruf fehlgeschlagen, da [System.Object []] keine Methode mit dem Namen 'Ersetzen' enthält." obwohl?
Amateur
\ Als Escape funktioniert in PS v4 habe ich gerade entdeckt. Vielen Dank.
ErikE
4
@rob leitet das Ergebnis an set-content oder out-file weiter, wenn Sie die Änderung speichern möchten
Loïc MICHEL
2
Ich habe die Fehlermeldung "Methodenaufruf fehlgeschlagen, weil [System.Object []] keine Methode mit dem Namen 'replace' enthält." weil ich versucht habe, die V3-Version auf einem Computer auszuführen, der nur V2 hatte.
SFlagg
5
Warnung: Das Ausführen dieser Skripte für große Dateien (etwa ein paar hundert Megabyte) kann viel Speicherplatz beanspruchen.
Stellen
89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

Beachten Sie, dass die Klammern (Get-Content file.txt)erforderlich sind:

Ohne die Klammer wird der Inhalt zeilenweise gelesen und fließt durch die Pipeline, bis er die Out-Datei oder den Set-Inhalt erreicht, der versucht, in dieselbe Datei zu schreiben, aber er wird bereits von get-content geöffnet und Sie erhalten ein Fehler. Die Klammer bewirkt, dass der Vorgang des Inhaltslesens einmal ausgeführt wird (Öffnen, Lesen und Schließen). Erst dann, wenn alle Zeilen gelesen wurden, werden sie einzeln weitergeleitet, und wenn sie den letzten Befehl in der Pipeline erreichen, können sie in die Datei geschrieben werden. Es ist dasselbe wie $ content = content; $ content | wo ...

Shay Levy
quelle
5
Ich würde mein Upvote in ein Downvote ändern, wenn ich könnte. In PowerShell 3 werden dadurch alle Inhalte stillschweigend aus der Datei gelöscht! Verwenden Sie Set-Contentanstelle von " Out-FileSie erhalten eine Warnung wie " Der Prozess kann nicht auf die Datei '123.csv' zugreifen, da sie von einem anderen Prozess verwendet wird. " .
Iain Samuel McLean Elder
9
Es sollte nicht passieren, wenn der get-Inhalt in Klammern steht. Sie bewirken, dass der Vorgang die Datei öffnet, liest und schließt, sodass der Fehler, den Sie erhalten, nicht auftreten sollte. Können Sie es erneut mit einer Beispieltextdatei testen?
Shay Levy
2
Mit Get-Contentin Klammern funktioniert es. Können Sie in Ihrer Antwort erklären, warum die Klammer notwendig ist? Ich würde immer noch ersetzen Out-Filemit , Set-Contentweil es sicherer ist ; Es schützt Sie vor dem Löschen der Zieldatei, wenn Sie die Klammer vergessen.
Iain Samuel McLean Elder
6
Problem mit der Dateicodierung UTF-8 . Ändert beim Speichern der Datei die Codierung. Nicht das gleiche. stackoverflow.com/questions/5596982/… . Ich denke, Set-Content berücksichtigt Codierungsdatei (wie UTF-8). aber nicht Out-File
Kiquenet
1
Diese Lösung ist unnötig irreführend und hat Probleme verursacht, als ich sie verwendet habe. Ich habe eine Konfigurationsdatei aktualisiert, die sofort vom Installationsprozess verwendet wurde. Die Konfigurationsdatei wurde noch vom Prozess gehalten und die Installation schlug fehl. Verwenden Set-Contentstatt Out-Fileist eine viel bessere und sicherere Lösung. Entschuldigung, ich muss abstimmen.
Martin Basista
81

Ich bevorzuge die Verwendung der File-Klasse von .NET und ihrer statischen Methoden, wie im folgenden Beispiel gezeigt.

$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)

Dies hat den Vorteil, dass mit einem einzelnen String anstelle eines String-Arrays wie bei Get-Content gearbeitet wird . Die Methoden kümmern sich auch um die Codierung der Datei (UTF-8- Stückliste usw.), ohne dass Sie sich die meiste Zeit darum kümmern müssen.

Außerdem bringen die Methoden die Zeilenenden (möglicherweise verwendete Unix-Zeilenenden) nicht durcheinander, im Gegensatz zu einem Algorithmus, der Get-Content verwendet und zu Set-Content weiterleitet .

Also für mich: Weniger Dinge, die im Laufe der Jahre kaputt gehen könnten.

Bei der Verwendung von .NET-Klassen ist wenig bekannt, dass Sie nach Eingabe von "[System.IO.File] ::" im PowerShell-Fenster die TabTaste drücken können, um die dortigen Methoden zu durchlaufen.

rominator007
quelle
Sie können die Methoden auch mit dem Befehl sehen [System.IO.File] | gm
fbehrens
Warum nimmt diese Methode einen relativen Pfad von an C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian
Ist das so? Dies hat wahrscheinlich etwas mit der Art und Weise zu tun, wie eine .NET AppDomain in PowerShell gestartet wird. Möglicherweise wird der aktuelle Pfad bei Verwendung von cd nicht aktualisiert. Dies ist jedoch nur eine fundierte Vermutung. Ich habe das nicht getestet oder nachgeschlagen.
rominator007
2
Dies ist auch viel einfacher als das Schreiben von unterschiedlichem Code für verschiedene Versionen von Powershell.
Willem van Ketwich
Diese Methode scheint auch die schnellste zu sein. Verbinden Sie das mit den genannten Vorteilen und der Frage sollte lauten: "Warum sollten Sie etwas anderes verwenden wollen?"
DBADon
21

Die obige Version wird nur für "Eine Datei" ausgeführt. Sie können sie jedoch auch für mehrere Dateien in Ihrem Ordner ausführen:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}
John V Hobbs Jr.
quelle
Beachten Sie, dass ich .xml verwendet habe, aber Sie können durch .txt ersetzen
John V Hobbs Jr
Nett. Alternativ zur Verwendung des Inneren foreachkönnen Sie dies tunGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD
1
Eigentlich brauchen Sie das innere foreach, weil Get-Content etwas tut, was Sie vielleicht nicht erwarten ... Es gibt ein Array von Zeichenfolgen zurück, wobei jede Zeichenfolge eine Zeile in der Datei ist. Wenn Sie ein Verzeichnis (und Unterverzeichnisse) durchlaufen, die sich an einem anderen Speicherort als Ihr ausgeführtes Skript befinden, möchten Sie Folgendes: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }Wo $Directorybefindet sich das Verzeichnis mit den Dateien, die Sie ändern möchten?
Birdamongmen
1
Welche Antwort ist "die oben"?
Peter Mortensen
10

Sie könnten so etwas versuchen:

$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path 
$newText = $text -replace $word,$replacement
$newText > $path
Richard
quelle
7

Dies ist, was ich benutze, aber es ist langsam bei großen Textdateien.

get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile

Wenn Sie Zeichenfolgen in großen Textdateien ersetzen möchten und die Geschwindigkeit ein Problem darstellt , sollten Sie System.IO.StreamReader und System.IO.StreamWriter verwenden .

try
{
   $reader = [System.IO.StreamReader] $pathToFile
   $data = $reader.ReadToEnd()
   $reader.close()
}
finally
{
   if ($reader -ne $null)
   {
       $reader.dispose()
   }
}

$data = $data -replace $stringToReplace, $replaceWith

try
{
   $writer = [System.IO.StreamWriter] $pathToFile
   $writer.write($data)
   $writer.close()
}
finally
{
   if ($writer -ne $null)
   {
       $writer.dispose()
   }
}

(Der obige Code wurde nicht getestet.)

Es gibt wahrscheinlich eine elegantere Möglichkeit, StreamReader und StreamWriter zum Ersetzen von Text in einem Dokument zu verwenden, aber das sollte Ihnen einen guten Ausgangspunkt bieten.

Darrell Lloyd Harvey
quelle
Ich denke, Set-Content berücksichtigt Codierungsdatei (wie UTF-8). aber nicht Out-File stackoverflow.com/questions/5596982/…
Kiquenet
2

Ich fand eine wenig bekannte, aber erstaunlich coole Möglichkeit, dies mit Payettes Windows Powershell in Action zu tun . Sie können auf Dateien wie Variablen verweisen, ähnlich wie bei $ env: path, aber Sie müssen die geschweiften Klammern hinzufügen.

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
js2010
quelle
Was ist, wenn sich der Dateiname in einer Variablen wie z $myFile.
ΩmegaMan
@ ΩmegaMan hmm nur das bisher$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010
2

Wenn Sie Zeichenfolgen in mehreren Dateien ersetzen müssen:

Es sollte beachtet werden, dass die verschiedenen hier veröffentlichten Methoden in Bezug auf die Zeit, die für die Fertigstellung benötigt wird, sehr unterschiedlich sein können. Für mich habe ich regelmäßig eine große Anzahl kleiner Dateien. Um zu testen, was am leistungsfähigsten ist, habe ich 5,52 GB (5.933.604.999 Byte) XML in 40.693 separaten Dateien extrahiert und drei der hier gefundenen Antworten durchgearbeitet:

## 5.52 GB (5,933,604,999 bytes) of XML files (40,693 files) 

#### Test 1 - Plain Replace
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml).replace("'", " ") | Set-Content $xml
}
$end   = get-date
NEW-TIMESPAN Start $Start End $End
<#
TotalMinutes: 103.725113128333
#>

#### Test 2 - Replace with -Raw
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml -Raw).replace("'", " ") | Set-Content $xml
}
$end   = get-date
NEW-TIMESPAN Start $Start End $End
<#
TotalMinutes: 10.1600227983333
#>

#### Test 3 - .NET, System.IO
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
$txt = [System.IO.File]::ReadAllText("$xml").Replace("'"," ") 
[System.IO.File]::WriteAllText("$xml", $txt)
}
$end   = get-date
NEW-TIMESPAN Start $Start End $End
<#
TotalMinutes: 5.83619516833333
#>
DBADon
quelle
Die Frage betraf das Ersetzen von Zeichenfolgen in einer bestimmten Datei, nicht in mehreren Dateien.
PL
1

Gutschrift an @ rominator007

Ich habe es in eine Funktion eingewickelt (weil Sie es möglicherweise wieder verwenden möchten)

function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
    $content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
    [System.IO.File]::WriteAllText("$FullPathToFile", $content)
}

HINWEIS: Hierbei wird NICHT zwischen Groß- und Kleinschreibung unterschieden !!!!!

Siehe diesen Beitrag: String.Replace Ignorieren von Groß- und Kleinschreibung

Kolob Canyon
quelle
0

Dies funktionierte bei mir mit dem aktuellen Arbeitsverzeichnis in PowerShell. Sie müssen die FullNameEigenschaft verwenden, sonst funktioniert sie in PowerShell Version 5 nicht. Ich musste die .NET Framework-Zielversion in ALLEN meinen CSPROJDateien ändern .

gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
 Set-Content "$($_.FullName)"}
SliverNinja - MSFT
quelle
0

Ein bisschen alt und anders, da ich in allen Fällen eines bestimmten Dateinamens eine bestimmte Zeile ändern musste.

Außerdem wurden Set-Contentkeine konsistenten Ergebnisse zurückgegeben, sodass ich darauf zurückgreifen musste Out-File.

Code unten:


$FileName =''
$OldLine = ''
$NewLine = ''
$Drives = Get-PSDrive -PSProvider FileSystem
foreach ($Drive in $Drives) {
    Push-Location $Drive.Root
        Get-ChildItem -Filter "$FileName" -Recurse | ForEach { 
            (Get-Content $_.FullName).Replace($OldLine, $NewLine) | Out-File $_.FullName
        }
    Pop-Location
}

Folgendes hat bei dieser PowerShell-Version am besten funktioniert:

Major.Minor.Build.Revision

5.1.16299.98

Kalin Tashev
quelle
-1

Kleine Korrektur für den Befehl Set-Content. Wenn die gesuchte Zeichenfolge nicht gefunden Set-Contentwird, wird die Zieldatei durch den Befehl leer (leer).

Sie können zunächst überprüfen, ob die gesuchte Zeichenfolge vorhanden ist oder nicht. Wenn nicht, wird nichts ersetzt.

If (select-string -path "c:\Windows\System32\drivers\etc\hosts" -pattern "String to look for") `
    {(Get-Content c:\Windows\System32\drivers\etc\hosts).replace('String to look for', 'String to replace with') | Set-Content c:\Windows\System32\drivers\etc\hosts}
    Else{"Nothing happened"}
dan D.
quelle
3
Willkommen bei StackOverflow! Bitte verwenden Sie die Formatierung. Sie können diesen Artikel lesen, wenn Sie Hilfe benötigen.
CodenameLambda
1
Dies ist nicht wahr, wenn man die richtige Antwort verwendet und das Ersetzen nicht gefunden wird, schreibt es immer noch die Datei, aber es gibt keine Änderungen. ZB set-content test.txt "hello hello world hello world hello"und leert dann (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtdie Datei nicht wie hier vorgeschlagen.
Ciantic