Mit PowerShell möchte ich alle genauen Vorkommen [MYID]
in einer bestimmten Datei durch ersetzen MyValue
. Was ist der einfachste Weg, dies zu tun?
Mit PowerShell möchte ich alle genauen Vorkommen [MYID]
in einer bestimmten Datei durch ersetzen MyValue
. Was ist der einfachste Weg, dies zu tun?
Antworten:
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
(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 ...
Set-Content
anstelle von " Out-File
Sie erhalten eine Warnung wie " Der Prozess kann nicht auf die Datei '123.csv' zugreifen, da sie von einem anderen Prozess verwendet wird. " .
Get-Content
in Klammern funktioniert es. Können Sie in Ihrer Antwort erklären, warum die Klammer notwendig ist? Ich würde immer noch ersetzen Out-File
mit , Set-Content
weil es sicherer ist ; Es schützt Sie vor dem Löschen der Zieldatei, wenn Sie die Klammer vergessen.
Set-Content
statt Out-File
ist eine viel bessere und sicherere Lösung. Entschuldigung, ich muss abstimmen.
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.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
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 $_
}
foreach
können Sie dies tunGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
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 $Directory
befindet sich das Verzeichnis mit den Dateien, die Sie ändern möchten?
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.
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'
$myFile
.
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
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
#>
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
Dies funktionierte bei mir mit dem aktuellen Arbeitsverzeichnis in PowerShell. Sie müssen die FullName
Eigenschaft verwenden, sonst funktioniert sie in PowerShell Version 5 nicht. Ich musste die .NET Framework-Zielversion in ALLEN meinen CSPROJ
Dateien ändern .
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
Ein bisschen alt und anders, da ich in allen Fällen eines bestimmten Dateinamens eine bestimmte Zeile ändern musste.
Außerdem wurden Set-Content
keine 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
Kleine Korrektur für den Befehl Set-Content. Wenn die gesuchte Zeichenfolge nicht gefunden Set-Content
wird, 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"}
set-content test.txt "hello hello world hello world hello"
und leert dann (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
die Datei nicht wie hier vorgeschlagen.