Korzystając z programu PowerShell, chcę zamienić wszystkie dokładne wystąpienia [MYID]
w danym pliku na MyValue
. Jaki jest najłatwiejszy sposób?
Korzystając z programu PowerShell, chcę zamienić wszystkie dokładne wystąpienia [MYID]
w danym pliku na MyValue
. Jaki jest najłatwiejszy sposób?
Odpowiedzi:
Użyj (wersja V3):
(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt
Lub dla 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
Uwaga (Get-Content file.txt)
: wymagane są nawiasy wokół :
Bez nawiasów treść jest odczytywana, po jednym wierszu na raz, i płynie w dół potoku, aż dotrze do pliku wyjściowego lub zestawu treści, który próbuje zapisać do tego samego pliku, ale jest już otwarty przez get-content i dostajesz błąd. Nawias powoduje, że operacja odczytu treści jest wykonywana jeden raz (otwieranie, czytanie i zamykanie). Dopiero wtedy, gdy wszystkie linie zostaną odczytane, są one przesyłane potokowo pojedynczo, a po osiągnięciu ostatniego polecenia w potoku można je zapisać do pliku. Jest to to samo, co $ content = content; $ content | gdzie ...
Set-Content
zamiast Out-File
yu powoduje wyświetlenie ostrzeżenia „Proces nie może uzyskać dostępu do pliku„ 123.csv ”, ponieważ jest używany przez inny proces.” .
Get-Content
w nawiasie to działa. Czy potrafisz wyjaśnić w odpowiedzi, dlaczego nawias jest konieczny? Chciałbym jeszcze wymienić Out-File
z Set-Content
ponieważ jest bezpieczniejsza; chroni cię przed wymazaniem pliku docelowego, jeśli zapomnisz nawias.
Set-Content
zamiast Out-File
jest znacznie lepszym i bezpieczniejszym rozwiązaniem. Przepraszam, muszę przegłosować.
Wolę używać klasy File .NET i jej metod statycznych, jak pokazano w poniższym przykładzie.
$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)
Ma to tę zaletę, że pracuje z jednym ciągiem zamiast tablicy ciągów, jak w przypadku Get-Content . Metody te zajmują się również kodowaniem pliku (UTF-8 BOM itp.) Bez konieczności zajmowania się przez większość czasu.
Również metody nie psują zakończeń linii (uniksowe zakończenia linii, które mogą być użyte) w przeciwieństwie do algorytmu używającego Get-Content i przesyłającego potokiem do Set-Content .
Więc dla mnie: Mniej rzeczy, które mogłyby się zepsuć na przestrzeni lat.
Mało znaną rzeczą podczas korzystania z klas .NET jest to, że po wpisaniu „[System.IO.File] ::” w oknie programu PowerShell można nacisnąć Tabklawisz, aby przejść przez dostępne tam metody.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
Powyższy działa tylko dla „jednego pliku”, ale możesz go również uruchomić dla wielu plików w swoim folderze:
Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '[MYID]', 'MyValue' }) |
Set-Content $_
}
foreach
Możesz to również zrobić za pomocą wewnętrznegoGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
foreach
, ponieważ Get-Content robi coś, czego nie możesz oczekiwać ... Zwraca tablicę ciągów, gdzie każdy ciąg jest linią w pliku. Jeśli przeglądasz katalog (i podkatalogi) znajdujące się w innym miejscu niż uruchomiony skrypt, potrzebujesz czegoś takiego: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }
gdzie $Directory
znajduje się katalog zawierający pliki, które chcesz zmodyfikować.
Tego używam, ale jest on powolny w przypadku dużych plików tekstowych.
get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile
Jeśli zamierzasz zamieniać ciągi w dużych plikach tekstowych, a problemem jest szybkość, skorzystaj z System.IO.StreamReader i System.IO.StreamWriter .
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()
}
}
(Powyższy kod nie został przetestowany).
Prawdopodobnie istnieje bardziej elegancki sposób korzystania z StreamReader i StreamWriter do zamiany tekstu w dokumencie, ale powinno to dać dobry punkt wyjścia.
Znalazłem mało znany, ale niesamowicie fajny sposób, aby to zrobić w Windows PowerShell Payette w akcji . Możesz odwoływać się do plików takich jak zmienne, podobne do $ env: path, ale musisz dodać nawiasy klamrowe.
${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
$myFile
?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
Jeśli chcesz zamienić ciągi w wielu plikach:
Należy zauważyć, że różne zamieszczone tutaj metody mogą być bardzo różne w odniesieniu do czasu potrzebnego do ukończenia. Dla mnie regularnie mam dużą liczbę małych plików. Aby przetestować to, co jest najbardziej wydajne, wyodrębniłem 5,52 GB (5 933 604 999 bajtów) XML w 40 693 oddzielnych plikach i przejrzałem trzy odpowiedzi, które tu znalazłem:
## 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
#>
Kredyt na @ rominator007
Zawinąłem go w funkcję (ponieważ możesz chcieć użyć go ponownie)
function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
$content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
[System.IO.File]::WriteAllText("$FullPathToFile", $content)
}
UWAGA: W NIE jest rozróżniana wielkość liter !!!!!
Zobacz ten post: String.Replace ignorując wielkość liter
To działało dla mnie przy użyciu bieżącego katalogu roboczego w PowerShell. Musisz użyć FullName
właściwości, inaczej nie będzie działać w PowerShell w wersji 5. Musiałem zmienić docelową wersję środowiska .NET we WSZYSTKICH moich CSPROJ
plikach.
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
Trochę stary i inny, ponieważ musiałem zmienić określoną linię we wszystkich instancjach określonej nazwy pliku.
Ponadto Set-Content
nie zwracał spójnych wyników, więc musiałem się uciekać Out-File
.
Kod poniżej:
$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
}
Oto, co działało dla mnie najlepiej w tej wersji programu PowerShell:
Major.Minor.Build.Revision
5.1.16299.98
Mała korekta polecenia Set-Content. Jeśli szukany ciąg nie zostanie znaleziony, Set-Content
polecenie wyczyści (opróżni) plik docelowy.
Możesz najpierw sprawdzić, czy szukany ciąg istnieje, czy nie. Jeśli nie, nic nie zastąpi.
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"
I wtedy (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
nie opróżni pliku zgodnie z sugestią w tym.