Bagaimana saya bisa mengganti setiap kemunculan String dalam file dengan PowerShell?

289

Menggunakan PowerShell, saya ingin mengganti semua kemunculan yang tepat [MYID]dalam file yang diberikan dengan MyValue. Apa cara termudah untuk melakukannya?

amatir
sumber
Untuk solusi yang lebih efektif dalam hal konsumsi memori daripada yang ditawarkan dalam jawaban untuk pertanyaan ini, lihat Menemukan dan Mengganti dalam File Besar .
Martin Prikryl

Jawaban:

444

Gunakan (versi V3):

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

Atau untuk V2:

(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
Loïc MICHEL
sumber
3
Terima kasih - Saya mendapat kesalahan "ganti: Doa metode gagal karena [System.Object []] tidak mengandung metode yang bernama 'ganti'." meskipun?
amatir
\ Sebagai jalan keluar bekerja di ps v4 saya baru saja menemukan Terima kasih.
ErikE
4
@rob pipkan hasilnya ke set-content atau out-file jika Anda ingin menyimpan modifikasi
Loïc MICHEL
2
Saya mendapat kesalahan "Doa metode gagal karena [System.Object []] tidak mengandung metode yang bernama 'replace'." karena saya sedang mencoba menjalankan versi V3 pada mesin yang hanya memiliki V2.
SFlagg
5
Peringatan: Menjalankan skrip ini terhadap file besar (beberapa ratus megabita atau lebih) dapat memakan cukup banyak memori. Pastikan Anda memiliki ruang kepala yang cukup jika Anda menjalankan pada server produksi: D
neoscribe
89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

Perhatikan bahwa tanda kurung (Get-Content file.txt)diperlukan:

Tanpa tanda kurung konten dibaca, satu baris pada satu waktu, dan mengalir turun pipa sampai mencapai file-out atau set-konten, yang mencoba menulis ke file yang sama, tetapi sudah terbuka oleh get-konten dan Anda mendapatkan kesalahan. Tanda kurung menyebabkan operasi pembacaan konten dilakukan sekali (buka, baca dan tutup). Hanya kemudian ketika semua baris telah dibaca, mereka disalurkan satu per satu dan ketika mereka mencapai perintah terakhir dalam pipa, mereka dapat ditulis ke file. Itu sama dengan $ content = content; $ content | dimana ...

Shay Levy
sumber
5
Saya akan mengubah upvote saya menjadi downvote jika saya bisa. Di PowerShell 3 ini secara diam-diam menghapus semua konten dari file! Menggunakan Set-Contentalih- Out-Filealih Anda mendapatkan peringatan seperti "Proses tidak dapat mengakses file '123.csv' karena sedang digunakan oleh proses lain." .
Iain Samuel McLean Penatua
9
Itu tidak boleh terjadi ketika konten-get dalam tanda kurung. Mereka menyebabkan operasi untuk membuka, membaca dan menutup file sehingga kesalahan yang Anda dapatkan tidak boleh terjadi. Bisakah Anda mengujinya lagi dengan file teks sampel?
Shay Levy
2
Dengan Get-Contentdalam tanda kurung berfungsi. Bisakah Anda menjelaskan dalam jawaban Anda mengapa tanda kurung diperlukan? Saya masih akan mengganti Out-Filedengan Set-Contentkarena lebih aman; itu melindungi Anda dari memusnahkan file target jika Anda lupa tanda kurung.
Iain Samuel McLean Penatua
6
Masalah dengan pengkodean file UTF-8 . Saat menyimpan file, ubah encoding. Tidak sama. stackoverflow.com/questions/5596982/… . Saya pikir set-content mempertimbangkan file Encoding (seperti UTF-8). tapi tidak di -File
Kiquenet
1
Solusi ini sangat menyesatkan dan menyebabkan masalah saat saya menggunakannya. Saya memperbarui file konfigurasi yang langsung digunakan oleh proses instalasi. File config masih ditahan oleh proses dan instalasi gagal. Menggunakan Set-Contentbukan Out-Filesolusi yang jauh lebih baik dan lebih aman. Maaf harus downvote.
Martin Basista
81

Saya lebih suka menggunakan File-class .NET dan metode statisnya seperti yang terlihat pada contoh berikut.

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

Ini memiliki keuntungan bekerja dengan String tunggal daripada String-array seperti dengan Get-Content . Metode ini juga menangani penyandian file (UTF-8 BOM , dll.) Tanpa Anda harus mengurus sebagian besar waktu.

Juga metode tidak mengacaukan akhir baris (akhir baris Unix yang mungkin digunakan) berbeda dengan algoritma menggunakan Get-Content dan pipa melalui Set-Content .

Jadi bagi saya: Lebih sedikit hal yang bisa pecah selama bertahun-tahun.

Suatu hal yang sedikit diketahui ketika menggunakan kelas .NET adalah bahwa ketika Anda telah mengetik "[System.IO.File] ::" di jendela PowerShell Anda dapat menekan Tabtombol untuk melangkah melalui metode di sana.

rominator007
sumber
Anda juga dapat melihat metode dengan perintah [System.IO.File] | gm
fbehrens
Mengapa metode ini mengambil jalur relatif C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian
Apakah begitu? Itu mungkin ada hubungannya dengan cara .NET AppDomain dimulai dalam PowerShell. Mungkin, bahwa jalur saat ini tidak diperbarui ketika menggunakan cd. Tapi ini tidak lebih dari tebakan yang berpendidikan. Saya tidak menguji ini atau mencarinya.
rominator007
2
Ini juga jauh lebih mudah daripada menulis kode yang berbeda untuk versi Powershell yang berbeda.
Willem van Ketwich
Metode ini juga tampaknya menjadi yang tercepat. Berpasangan dengan manfaat yang disebutkan dan pertanyaannya adalah, "mengapa Anda ingin menggunakan hal lain?"
DBAD
21

Yang di atas hanya berjalan untuk "Satu File" saja, tetapi Anda juga dapat menjalankan ini untuk beberapa file dalam folder Anda:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}
John V Hobbs Jr
sumber
perhatikan bahwa saya menggunakan .xml tetapi Anda dapat menggantinya dengan .txt
John V Hobbs Jr
Bagus. Atau untuk menggunakan bagian dalam foreachAnda dapat melakukan iniGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD
1
Sebenarnya, Anda memang membutuhkan bagian dalam itu foreach, karena Get-Content melakukan sesuatu yang mungkin tidak Anda harapkan ... Ini mengembalikan array string, di mana setiap string adalah sebuah baris dalam file. Jika Anda mengulang-ulang direktori (dan sub-direktori) yang berada di lokasi yang berbeda dari skrip Anda yang sedang berjalan, Anda akan menginginkan sesuatu seperti ini: di Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }mana $Directorydirektori berisi file yang ingin Anda modifikasi.
birdamongmen
1
Apa jawaban "yang di atas"?
Peter Mortensen
10

Anda dapat mencoba sesuatu seperti ini:

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

Ini yang saya gunakan, tetapi lambat pada file teks besar.

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

Jika Anda akan mengganti string dalam file teks besar dan kecepatan adalah masalah, lihat menggunakan System.IO.StreamReader dan 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()
   }
}

(Kode di atas belum diuji.)

Mungkin ada cara yang lebih elegan untuk menggunakan StreamReader dan StreamWriter untuk mengganti teks dalam dokumen, tetapi itu akan memberi Anda titik awal yang baik.

Darrell Lloyd Harvey
sumber
Saya pikir set-content mempertimbangkan file Encoding (seperti UTF-8). tapi tidak Keluar-file stackoverflow.com/questions/5596982/…
Kiquenet
2

Saya menemukan cara yang dikenal tetapi sangat keren untuk melakukannya dari Payette Windows Powershell in Action . Anda dapat mereferensikan file seperti variabel, mirip dengan $ env: path, tetapi Anda perlu menambahkan kurung kurawal.

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
js2010
sumber
Bagaimana jika nama file dalam variabel seperti $myFile?
ΩmegaMan
@ ΩmegaMan hmm hanya sejauh ini$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010
2

Jika Anda Perlu Mengganti String di Banyak File:

Perlu dicatat bahwa berbagai metode yang diposting di sini dapat sangat berbeda berkaitan dengan waktu yang diperlukan untuk menyelesaikannya. Bagi saya, saya secara teratur memiliki banyak file kecil. Untuk menguji apa yang paling performan, saya mengekstraksi XML 5,52 GB (5,933,604,999 bytes) dalam 40.693 file terpisah dan menjalankan tiga jawaban yang saya temukan di sini:

## 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
sumber
Pertanyaannya adalah tentang mengganti string dalam file yang diberikan, bukan beberapa file.
PL
1

Kredit ke @ rominator007

Saya membungkusnya menjadi fungsi (karena Anda mungkin ingin menggunakannya lagi)

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

CATATAN: Ini BUKAN case sensitive !!!!!

Lihat posting ini: String.Replace case ignoring

Ngarai Kolob
sumber
0

Ini berhasil bagi saya menggunakan direktori kerja saat ini di PowerShell. Anda perlu menggunakan FullNameproperti, atau tidak akan berfungsi di PowerShell versi 5. Saya perlu mengubah versi .NET framework framework di SEMUA CSPROJfile saya .

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

Agak lama dan berbeda, karena saya perlu mengubah baris tertentu dalam semua contoh nama file tertentu.

Juga, Set-Contenttidak mengembalikan hasil yang konsisten, jadi saya harus menggunakan Out-File.

Kode di bawah ini:


$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
}

Inilah yang paling cocok untuk saya di versi PowerShell ini:

Major.Minor.Build.Revision

5.1.16299.98

Kalin Tashev
sumber
-1

Koreksi kecil untuk perintah Set-Content. Jika string yang dicari tidak ditemukan, Set-Contentperintah akan mengosongkan (mengosongkan) file target.

Pertama-tama Anda dapat memverifikasi apakah string yang Anda cari ada atau tidak. Jika tidak, itu tidak akan menggantikan apa pun.

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
sumber
3
Selamat datang di StackOverflow! Silakan gunakan pemformatan, Anda dapat membaca artikel ini jika Anda memerlukan bantuan.
CodenameLambda
1
Ini tidak benar, jika seseorang menggunakan jawaban yang benar dan penggantian tidak ditemukan, ia masih menulis file, tetapi tidak ada perubahan. Misalnya set-content test.txt "hello hello world hello world hello"dan kemudian (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txttidak akan mengosongkan file seperti yang disarankan dalam ini.
Ciantic