Jak mogę zamienić każde wystąpienie ciągu w pliku za pomocą programu PowerShell?

289

Korzystając z programu PowerShell, chcę zamienić wszystkie dokładne wystąpienia [MYID]w danym pliku na MyValue. Jaki jest najłatwiejszy sposób?

amator
źródło
Aby uzyskać bardziej skuteczne rozwiązanie w zakresie zużycia pamięci niż oferowane w odpowiedziach na to pytanie, zobacz Znajdź i zamień w dużym pliku .
Martin Prikryl

Odpowiedzi:

444

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
Loïc MICHEL
źródło
3
Dzięki - pojawia się błąd „zamień: wywołanie metody nie powiodło się, ponieważ [System.Object []] nie zawiera metody o nazwie„ replace ”.” chociaż?
amator
\ jak ucieczka działa w ps v4 właśnie odkryłem. Dzięki.
ErikE
4
@rob potokuj wynik do zbioru lub zawartości pliku, jeśli chcesz zapisać modyfikację
Loïc MICHEL
2
Wystąpił błąd „Wywołanie metody nie powiodło się, ponieważ [System.Object []] nie zawiera metody o nazwie„ replace ”. ponieważ próbowałem uruchomić wersję V3 na maszynie, która miała tylko V2.
SFlagg,
5
Ostrzeżenie: Uruchomienie tych skryptów na dużych plikach (około kilkuset megabajtów) może pochłonąć sporo pamięci. Tylko upewnij się, że masz wystarczająco dużo miejsca, jeśli pracujesz na serwerze produkcyjnym: D
neoscribe
89
(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 ...

Shay Levy
źródło
5
Gdybym mógł, zmieniłbym moje głosowanie na głosowanie negatywne. W PowerShell 3 to po cichu usuwa całą zawartość z pliku! Użycie Set-Contentzamiast Out-Fileyu powoduje wyświetlenie ostrzeżenia „Proces nie może uzyskać dostępu do pliku„ 123.csv ”, ponieważ jest używany przez inny proces.” .
Iain Samuel McLean Starszy
9
Nie powinno się to zdarzyć, gdy get-content jest w nawiasie. Powodują, że operacja otwiera, odczytuje i zamyka plik, więc występujący błąd nie powinien się zdarzyć. Czy możesz to przetestować ponownie z przykładowym plikiem tekstowym?
Shay Levy,
2
Z Get-Contentw nawiasie to działa. Czy potrafisz wyjaśnić w odpowiedzi, dlaczego nawias jest konieczny? Chciałbym jeszcze wymienić Out-Filez Set-Contentponieważ jest bezpieczniejsza; chroni cię przed wymazaniem pliku docelowego, jeśli zapomnisz nawias.
Iain Samuel McLean Starszy
6
Problem z kodowaniem pliku UTF-8 . Podczas zapisywania pliku zmienia kodowanie. Nie ten sam. stackoverflow.com/questions/5596982/... . Myślę, że set-content rozważa kodowanie pliku (jak UTF-8). ale nie Out-File
Kiquenet
1
To rozwiązanie jest niepotrzebnie mylące i powoduje problemy, gdy go używam. Aktualizowałem plik konfiguracyjny, który został natychmiast wykorzystany przez proces instalacji. Plik konfiguracyjny był nadal przechowywany przez proces i instalacja nie powiodła się. Używanie Set-Contentzamiast Out-Filejest znacznie lepszym i bezpieczniejszym rozwiązaniem. Przepraszam, muszę przegłosować.
Martin Basista,
81

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.

rominator007
źródło
Możesz również zobaczyć metody z poleceniem [System.IO.File] | gm
fbehrens
Dlaczego ta metoda zakłada ścieżkę względną C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian
Czy tak jest Ma to prawdopodobnie związek ze sposobem uruchamiania aplikacji .NET AppDomain w programie PowerShell. Możliwe, że bieżąca ścieżka nie zostanie zaktualizowana podczas korzystania z cd. Ale to tylko wykształcone przypuszczenie. Nie przetestowałem tego ani nie sprawdziłem.
rominator007
2
Jest to również o wiele łatwiejsze niż pisanie różnych kodów dla różnych wersji programu PowerShell.
Willem van Ketwich,
Ta metoda wydaje się również najszybsza. Połączmy to z wymienionymi korzyściami, a pytanie powinno brzmieć: „dlaczego miałbyś chcieć użyć czegoś innego?”
DBADon
21

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 $_
}
John V Hobbs Jr
źródło
zauważ, że użyłem .xml, ale możesz zastąpić .txt
John V Hobbs Jr
Miły. foreachMożesz to również zrobić za pomocą wewnętrznegoGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD
1
W rzeczywistości potrzebujesz tego wewnętrznego 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 $Directoryznajduje się katalog zawierający pliki, które chcesz zmodyfikować.
birdamongmen
1
Jaka jest odpowiedź „ta powyżej”?
Peter Mortensen
10

Możesz spróbować czegoś takiego:

$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path 
$newText = $text -replace $word,$replacement
$newText > $path
Richard
źródło
7

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.

Darrell Lloyd Harvey
źródło
Myślę, że set-content rozważa kodowanie pliku (jak UTF-8). ale nie plik
wyjściowy
2

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'
js2010
źródło
Co jeśli nazwa pliku znajduje się w zmiennej takiej jak $myFile?
ΩmegaMan
@ ΩmegaMan hmm tylko do tej pory$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010
2

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
#>
DBADon
źródło
Pytanie dotyczyło zamiany ciągów w danym pliku, a nie w wielu plikach.
PL
1

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

Kanion Kolob
źródło
0

To działało dla mnie przy użyciu bieżącego katalogu roboczego w PowerShell. Musisz użyć FullNamewłaściwości, inaczej nie będzie działać w PowerShell w wersji 5. Musiałem zmienić docelową wersję środowiska .NET we WSZYSTKICH moich CSPROJplikach.

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

Trochę stary i inny, ponieważ musiałem zmienić określoną linię we wszystkich instancjach określonej nazwy pliku.

Ponadto Set-Contentnie 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

Kalin Tashev
źródło
-1

Mała korekta polecenia Set-Content. Jeśli szukany ciąg nie zostanie znaleziony, Set-Contentpolecenie 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"}
dan D.
źródło
3
Witamy w StackOverflow! Użyj formatowania, możesz przeczytać ten artykuł, jeśli potrzebujesz pomocy.
CodenameLambda,
1
Nie jest to prawdą, jeśli ktoś użyje poprawnej odpowiedzi, a zamiennik nie zostanie znaleziony, nadal zapisuje plik, ale nie ma żadnych zmian. Np. set-content test.txt "hello hello world hello world hello"I wtedy (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtnie opróżni pliku zgodnie z sugestią w tym.
Ciantic