Bir dizede her dizeyi PowerShell ile nasıl değiştirebilirim?


289

PowerShell kullanarak, [MYID]belirli bir dosyadaki tüm kesin oluşumları ile değiştirmek istiyorum MyValue. Bunu yapmanın en kolay yolu nedir?


Bellek tüketimi açısından bu sorunun yanıtlarında sunulandan daha etkili bir çözüm için, bkz . Büyük Dosyada Bulma ve Değiştirme .
Martin Prikryl

Yanıtlar:


444

Kullanım (V3 sürümü):

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

Veya V2 için:

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

3
Teşekkürler - bir hata alıyorum "replace: [System.Object []] 'replace' adında bir yöntem içermediğinden yöntem çağırma başarısız oldu." rağmen?
amatör

\ ps kaçış olarak çalışıyor v4 yeni keşfettim. Teşekkürler.
ErikE

4
@rob, değişikliği kaydetmek istiyorsanız sonucu set-content veya out-file olarak yönlendirin
Loïc MICHEL

2
"[System.Object []] 'replace' adında bir yöntem içermediğinden yöntem çağırma başarısız oldu." Hatasını aldım . çünkü V3 sürümünü sadece V2 olan bir makinede çalıştırmaya çalışıyordum.
SFlagg

5
Uyarı: Bu komut dosyalarını büyük dosyalara (birkaç yüz megabayt veya daha fazla) karşı çalıştırmak yeterli miktarda bellek tüketebilir. Bir üretim sunucusunda koşuyorsanız yeterli kafa odanız olduğundan emin olun: D
neoscribe

89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

Parantezlerin (Get-Content file.txt)gerekli olduğunu unutmayın:

Parantez olmadan içerik her seferinde bir satır okunur ve aynı dosyaya yazmaya çalışan dosya dışına veya set içeriğine ulaşana kadar ardışık düzenden aşağı akar, ancak get-content tarafından zaten açıktır ve bir hata. Parantez, içerik okuma işleminin bir kez (açma, okuma ve kapatma) yapılmasına neden olur. Ancak o zaman tüm satırlar okunduklarında, her seferinde bir tane borulanırlar ve boru hattındaki son komuta ulaştıklarında dosyaya yazılabilirler. $ Content = content ile aynı; $ içerik | nerede ...


5
Yapabilseydim, benim oyumu bir aşağı oyla değiştirirdim. PowerShell 3'te bu, tüm içeriği sessizce dosyadan siler! Kullanma Set-Contentyerine Out-Filegibi yuou bir uyarı olsun "başka bir işlem tarafından kullanıldığı için işlem dosyası '123.csv' erişemez." .
Iain Samuel McLean Elder

9
Get-content parantez içinde olduğunda olmamalıdır. İşlemin dosyayı açmasına, okumasına ve kapatmasına neden olurlar, böylece aldığınız hata olmamalıdır. Örnek bir metin dosyasıyla tekrar test edebilir misiniz?
Shay Levy

2
İle Get-Contentparantez içinde çalışıyor. Cevabınızda parantezin neden gerekli olduğunu açıklayabilir misiniz? Hala yerini alacak Out-Fileolan Set-Contentdaha güvenli çünkü; parantezi unutursanız sizi hedef dosyayı silmekten korur.
Iain Samuel McLean Elder

6
UTF-8 dosya kodlaması ile ilgili sorun . Dosyayı kaydettiğinde kodlamayı değiştirir. Aynı değil. stackoverflow.com/questions/5596982/… . Bence set-içeriği Kodlama dosyasını (UTF-8 gibi) dikkate alır. ama Dosya Dışı Değil
Kiquenet

1
Bu çözüm gereksiz yere yanıltıcıdır ve kullandığımda sorunlara neden olmuştur. Yükleme işlemi tarafından hemen kullanılan bir yapılandırma dosyasını güncelleştiriyordum. Yapılandırma dosyası hala işlem tarafından tutuldu ve yükleme başarısız oldu. Set-ContentBunun yerine kullanmak Out-Fileçok daha iyi ve daha güvenli bir çözümdür. Üzgünüm intikam almak zorunda.
Martin Basista

81

Aşağıdaki örnekte görüldüğü gibi .NET File sınıfını ve statik yöntemlerini kullanmayı tercih ederim.

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

Bunun avantajı Get-Content gibi String dizesi yerine tek bir String ile çalışabilmesidir . Yöntemler , çoğu zaman ilgilenmek zorunda kalmadan dosyanın kodlamasını da (UTF-8 BOM , vb.) Halleder .

Ayrıca yöntemler Get-Content kullanan bir algoritmanın aksine satır sonlarını (kullanılabilecek Unix satır sonları) bozmaz ve Set-Content'e aktarır .

Yani benim için: Yıllar boyunca kırılabilecek daha az şey.

.NET sınıflarını kullanırken az bilinen bir şey, PowerShell penceresinde "[System.IO.File] ::" yazdığınızda, Taboradaki yöntemler arasında gezinmek için tuşa basabilmenizdir.


Ayrıca şu yöntemleri de kullanabilirsiniz [System.IO.File] | gm
fbehrens

Bu yöntem neden göreli bir yol alıyor C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian

Öyle mi? Bunun muhtemelen PowerShell içinde bir .NET AppDomain'in başlatılmasıyla bir ilgisi vardır. Olabilir, cd kullanılırken mevcut yol güncellenmez. Fakat bu eğitimli bir tahminden başka bir şey değildir. Bunu test etmedim ya da bakmadım.
rominator007

2
Bu aynı zamanda farklı Powershell sürümleri için farklı kod yazmaktan çok daha kolaydır.
Willem van Ketwich

Bu yöntem aynı zamanda en hızlı yöntemdir. Belirtilen faydalar ve soru ile, "neden başka bir şey kullanmak istesin?"
DBADon

21

Yukarıdaki dosya yalnızca "Bir Dosya" için çalışır, ancak bunu klasörünüzdeki birden çok dosya için de çalıştırabilirsiniz:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}

.xml kullandığımı fark ettiniz, ancak .txt ile değiştirebilirsiniz
John V Hobbs Jr

Güzel. İç kısmı kullanmaya alternatif olarak foreachbunu yapabilirsinizGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD

1
Aslında, o iç ihtiyacınız var foreach, çünkü Get-Content beklemediğiniz bir şey yapar ... Her dizenin dosyada bir çizgi olduğu bir dizeler dizisi döndürür. : Eğer çalışan komut daha farklı bir konumda olduğu bir dizin (ve alt dizinleri) arasında döngü ediyorsanız, böyle bir şey isteyeceksiniz değiştirmek istediğiniz dosyaları içeren dizindir. Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }$Directory
birdamongmen

1
"Yukarıdaki cevap" hangi cevaptır?
Peter Mortensen

10

Böyle bir şey deneyebilirsiniz:

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

7

Kullandığım şey bu, ancak büyük metin dosyalarında yavaş.

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

Büyük metin dosyalarındaki dizeleri değiştirecekseniz ve hız önemliyse, System.IO.StreamReader ve System.IO.StreamWriter kullanmaya bakın .

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()
   }
}

(Yukarıdaki kod test edilmemiştir.)

Bir belgedeki metni değiştirmek için StreamReader ve StreamWriter'ı kullanmanın muhtemelen daha zarif bir yolu vardır, ancak bu size iyi bir başlangıç ​​noktası vermelidir.


Bence set-içeriği Kodlama dosyasını (UTF-8 gibi) dikkate alır. ancak Dosya Dışı stackoverflow.com/questions/5596982/…
Kiquenet

2

Payette'in Windows Powershell in Action'ından bunu yapmak için biraz bilinen ama inanılmaz güzel bir yol buldum . $ Env: path benzeri değişkenler gibi dosyalara başvurabilirsiniz, ancak süslü parantez eklemeniz gerekir.

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'

Dosya adı gibi bir değişkendeyse ne olur $myFile?
MmegaMan

@ ΩmegaMan hmm sadece şu ana kadar$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010

2

Birden Çok Dosyadaki Dizeleri Değiştirmeniz Gerekiyorsa:

Burada yayınlanan farklı yöntemlerin tamamlanması için geçen süre bakımından çılgınca farklı olabileceğine dikkat edilmelidir. Benim için düzenli olarak çok sayıda küçük dosyam var. En yüksek performansı gösteren testi yapmak için 40.693 ayrı dosyada 5.52 GB (5.933.604.999 bayt) XML çıkardım ve burada bulduğum üç yanıttan geçtim:

## 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
#>

Soru, belirli bir dosyadaki dizeleri birden çok dosyayla değil değiştirmekti.
PL

1

@ Rominator007'ye kredi

Bir işleve sardım (çünkü tekrar kullanmak isteyebilirsiniz)

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

NOT: Bu büyük / küçük harfe duyarlı DEĞİLDİR !!!!!

Bu yazıyı görün: String.Roring ignoring case


0

Bu benim için PowerShell'deki geçerli çalışma dizinini kullanarak çalıştı. FullNameÖzelliği kullanmanız gerekir , yoksa PowerShell sürüm 5'de çalışmaz. TÜM CSPROJdosyalarımdaki hedef .NET framework sürümünü değiştirmem gerekiyordu .

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

0

Biraz eski ve farklı, çünkü belirli bir dosya adının tüm örneklerinde belirli bir satırı değiştirmem gerekiyordu.

Ayrıca, Set-Content tutarlı sonuçlar döndürmüyordu, bu yüzden başvurmak zorunda kaldımOut-File .

Aşağıdaki kod:


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

Bu PowerShell sürümünde benim için en uygun olan buydu:

Ana.Alt.Yapı.Düzeltme

5.1.16299.98


-1

Set-Content komutu için küçük düzeltme. Aranan dize bulunmazsa,Set-Content komut hedef dosyayı boşaltır (boşaltır).

İlk olarak aradığınız dizenin var olup olmadığını doğrulayabilirsiniz. Değilse hiçbir şeyin yerini almayacaktır.

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

3
StackOverflow'a hoş geldiniz! Lütfen biçimlendirmeyi kullanın, yardıma ihtiyacınız olursa bu makaleyi okuyabilirsiniz .
CodenameLambda

1
Bu doğru değildir, eğer biri doğru cevabı kullanırsa ve yerine koyma bulunmazsa, yine de dosyayı yazar, ancak değişiklik yoktur. Örneğin set-content test.txt "hello hello world hello world hello"ve daha sonra (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtbu dosyada önerildiği gibi dosyayı boşaltmaz.
Ciantic
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.