Menggunakan PowerShell, saya ingin mengganti semua kemunculan yang tepat [MYID]
dalam file yang diberikan dengan MyValue
. Apa cara termudah untuk melakukannya?
Menggunakan PowerShell, saya ingin mengganti semua kemunculan yang tepat [MYID]
dalam file yang diberikan dengan MyValue
. Apa cara termudah untuk melakukannya?
Jawaban:
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
(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 ...
Set-Content
alih- Out-File
alih Anda mendapatkan peringatan seperti "Proses tidak dapat mengakses file '123.csv' karena sedang digunakan oleh proses lain." .
Get-Content
dalam tanda kurung berfungsi. Bisakah Anda menjelaskan dalam jawaban Anda mengapa tanda kurung diperlukan? Saya masih akan mengganti Out-File
dengan Set-Content
karena lebih aman; itu melindungi Anda dari memusnahkan file target jika Anda lupa tanda kurung.
Set-Content
bukan Out-File
solusi yang jauh lebih baik dan lebih aman. Maaf harus downvote.
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.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
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 $_
}
foreach
Anda dapat melakukan iniGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
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 $Directory
direktori berisi file yang ingin Anda modifikasi.
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.
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'
$myFile
?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
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
#>
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
Ini berhasil bagi saya menggunakan direktori kerja saat ini di PowerShell. Anda perlu menggunakan FullName
properti, atau tidak akan berfungsi di PowerShell versi 5. Saya perlu mengubah versi .NET framework framework di SEMUA CSPROJ
file saya .
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
Agak lama dan berbeda, karena saya perlu mengubah baris tertentu dalam semua contoh nama file tertentu.
Juga, Set-Content
tidak 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
Koreksi kecil untuk perintah Set-Content. Jika string yang dicari tidak ditemukan, Set-Content
perintah 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"}
set-content test.txt "hello hello world hello world hello"
dan kemudian (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
tidak akan mengosongkan file seperti yang disarankan dalam ini.