ฉันจะแทนที่ String ทุกครั้งในไฟล์ด้วย PowerShell ได้อย่างไร


289

ใช้ PowerShell ผมต้องการที่จะแทนที่เกิดขึ้นแน่นอนทั้งหมดในแฟ้มที่กำหนดด้วย[MYID] MyValueวิธีที่ง่ายที่สุดในการทำเช่นนั้นคืออะไร?


สำหรับการแก้ปัญหามีประสิทธิภาพมากขึ้นในแง่ของการใช้หน่วยความจำมากกว่านำเสนอในการตอบคำถามนี้ให้ดูที่การค้นหาและแทนที่ในไฟล์ขนาดใหญ่
Martin Prikryl

คำตอบ:


444

ใช้ (รุ่น V3):

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

หรือสำหรับ V2:

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

3
ขอบคุณ - ฉันได้รับข้อผิดพลาด "replace: การเรียกเมธอดล้มเหลวเนื่องจาก [System.Object []] ไม่มีวิธีการที่ชื่อว่า 'replace'" แต่?
มือสมัครเล่น

\ as escape ใช้งานได้ใน ps v4 ฉันเพิ่งค้นพบ ขอบคุณ
ErikE

4
@rob ไปป์ผลลัพธ์เป็น set-content หรือ out-file ถ้าคุณต้องการบันทึกการแก้ไข
Loïc MICHEL

2
ฉันได้รับข้อผิดพลาด"การเรียกใช้เมธอดล้มเหลวเนื่องจาก [System.Object []] ไม่มีวิธีชื่อ 'แทนที่' เพราะฉันพยายามรันเวอร์ชัน V3 บนเครื่องที่มีเฉพาะ V2
SFlagg

5
คำเตือน: การเรียกใช้สคริปต์เหล่านี้กับไฟล์ขนาดใหญ่ (สองร้อยเมกะไบต์หรือมากกว่านั้น) อาจทำให้หน่วยความจำในปริมาณที่พอสมควร เพียงให้แน่ใจว่าคุณมีที่ว่างเพียงพอหากคุณใช้งานเซิร์ฟเวอร์ที่ใช้งานจริง: D
neoscribe

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

โปรดทราบว่า(Get-Content file.txt)ต้องใช้วงเล็บใน:

โดยไม่ต้องใส่วงเล็บเนื้อหาจะอ่านครั้งละหนึ่งบรรทัดและไหลไปป์ไลน์จนกว่าจะถึงไฟล์ที่ออกหรือชุดเนื้อหาซึ่งพยายามที่จะเขียนไปยังไฟล์เดียวกัน แต่มันเปิดแล้วโดยรับเนื้อหาและคุณจะได้รับ ข้อผิดพลาด วงเล็บทำให้การดำเนินการอ่านเนื้อหาทำได้ครั้งเดียว (เปิดอ่านและปิด) จากนั้นเมื่อทุกบรรทัดถูกอ่านบรรทัดเหล่านั้นจะถูกไพพ์ทีละครั้งและเมื่อถึงคำสั่งสุดท้ายในไพพ์ไลน์พวกเขาสามารถเขียนลงไฟล์ได้ มันเหมือนกับ $ content = content; $ เนื้อหา | ที่ ...


5
ฉันจะเปลี่ยน upvote เป็น downvote ถ้าทำได้ ใน PowerShell 3 สิ่งนี้จะลบเนื้อหาทั้งหมดออกจากไฟล์อย่างเงียบ ๆ ! การใช้Set-Contentแทนที่จะเป็นOut-Fileคุณได้รับคำเตือนเช่น"กระบวนการไม่สามารถเข้าถึงไฟล์ '123.csv' เพราะกระบวนการอื่นกำลังถูกใช้งาน" .
เลนซามูเอลแมคลีนผู้อาวุโส

9
มันไม่ควรเกิดขึ้นเมื่อเนื้อหารับอยู่ในวงเล็บ พวกเขาทำให้การดำเนินการเพื่อเปิดอ่านและปิดไฟล์ดังนั้นข้อผิดพลาดที่คุณได้รับไม่ควรเกิดขึ้น คุณสามารถทดสอบอีกครั้งด้วยไฟล์ข้อความตัวอย่างได้หรือไม่?
Shay Levy

2
ด้วยGet-Contentในวงเล็บมันทำงาน คุณสามารถอธิบายในคำตอบของคุณได้ว่าเหตุใดจึงจำเป็นต้องใช้วงเล็บ ฉันจะแทนที่Out-Fileด้วยSet-Contentเพราะปลอดภัยกว่า มันปกป้องคุณจากการเช็ดไฟล์เป้าหมายถ้าคุณลืมวงเล็บ
เลนซามูเอลแมคลีนผู้อาวุโส

6
ปัญหาเกี่ยวกับการเข้ารหัสไฟล์ UTF-8 เมื่อบันทึกไฟล์ให้เปลี่ยนการเข้ารหัส ไม่เหมือนกัน. stackoverflow.com/questions/5596982/… . ฉันคิดว่า ชุดเนื้อหาพิจารณาไฟล์การเข้ารหัส (เช่น UTF-8) แต่ไม่ใช่ไฟล์
Kiquenet

1
วิธีแก้ปัญหานี้ทำให้เข้าใจผิดและก่อให้เกิดปัญหาเมื่อฉันใช้งาน ฉันกำลังอัปเดตไฟล์กำหนดค่าซึ่งใช้งานได้ทันทีโดยกระบวนการติดตั้ง ไฟล์กำหนดค่ายังคงถูกจัดการตามกระบวนการและการติดตั้งล้มเหลว การใช้Set-Contentแทนที่จะOut-Fileเป็นวิธีที่ดีกว่าและปลอดภัยกว่ามาก ขออภัยต้องลงคะแนน
Martin Basista

81

ฉันชอบใช้ File-class ของ. NET และวิธีการคงที่ตามที่เห็นในตัวอย่างต่อไปนี้

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

นี้มีประโยชน์ในการทำงานกับสายอักขระตัวเดียวแทนที่จะเป็น String อาร์เรย์เช่นเดียวกับได้รับเนื้อหา วิธีการต่างๆยังดูแลการเข้ารหัสไฟล์ (UTF-8 BOMฯลฯ ) โดยที่คุณไม่ต้องเสียเวลาดูแลมากนัก

นอกจากนี้ยังมีวิธีการที่จะไม่ยุ่งขึ้นตอนจบบรรทัด (ตอนจบบรรทัด Unix ที่อาจจะใช้) ในทางตรงกันข้ามกับอัลกอริทึมที่ใช้รับเนื้อหาและท่อผ่านไปยังชุดเนื้อหา

ดังนั้นสำหรับฉัน: สิ่งที่น้อยลงที่สามารถทำลายในช่วงหลายปี

สิ่งที่ไม่ค่อยมีคนรู้จักเมื่อใช้คลาส. NET คือเมื่อคุณพิมพ์ใน "[System.IO.File] ::" ในหน้าต่าง PowerShell คุณสามารถกดTabปุ่มเพื่อทำตามวิธีการต่างๆ


คุณยังสามารถดูวิธีการด้วยคำสั่ง [System.IO.File] | gm
fbehrens

ทำไมวิธีนี้ถือว่าทางญาติจากC:\Windows\System32\WindowsPowerShell\v1.0?
Adrian

เป็นอย่างนั้นเหรอ? อาจเป็นสิ่งที่เกี่ยวข้องกับวิธีที่. NET AppDomain เริ่มต้นภายใน PowerShell อาจเป็นไปได้ว่าเส้นทางปัจจุบันไม่ได้รับการปรับปรุงเมื่อใช้ซีดี แต่นี่ไม่ใช่แค่การเดาที่มีการศึกษา ฉันไม่ได้ทดสอบหรือค้นหามัน
rominator007

2
นอกจากนี้ยังง่ายกว่าการเขียนรหัสที่ต่างกันสำหรับ Powershell รุ่นต่างๆ
Willem van Ketwich

วิธีนี้ดูเหมือนจะเร็วที่สุด คู่กับผลประโยชน์ที่ระบุไว้และคำถามที่ควรจะเป็น "ทำไมคุณต้องการที่จะใช้อะไรอีกหรือไม่"
DBADon

21

ไฟล์ด้านบนใช้สำหรับ "ไฟล์เดียว" เท่านั้น แต่คุณสามารถเรียกใช้ไฟล์นี้ได้หลายไฟล์ภายในโฟลเดอร์ของคุณ:

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

สังเกตเห็นว่าฉันใช้. xml แต่คุณสามารถแทนที่ด้วย. txt
John V Hobbs Jr

ดี อีกทางเลือกหนึ่งคือการใช้ด้านในforeachคุณสามารถทำได้Get-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD

1
ที่จริงแล้วคุณต้องการภายในนั่นforeachเพราะ Get-Content ทำสิ่งที่คุณอาจไม่คาดคิด ... มันส่งคืนอาร์เรย์ของสตริงโดยที่แต่ละสตริงเป็นบรรทัดในไฟล์ หากคุณวนลูปผ่านไดเรกทอรี (และไดเรกทอรีย่อย) ที่อยู่ในตำแหน่งที่แตกต่างจากสคริปต์ที่ใช้งานของคุณคุณจะต้องการสิ่งนี้: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }ที่ไหน$Directoryเป็นไดเรกทอรีที่มีไฟล์ที่คุณต้องการแก้ไข
birdamongmen

1
คำตอบคืออะไร "ข้างต้น"?
Peter Mortensen

10

คุณสามารถลองสิ่งนี้:

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

7

นี่คือสิ่งที่ฉันใช้ แต่มันช้าในไฟล์ข้อความขนาดใหญ่

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

หากคุณกำลังจะถูกแทนที่สตริงในไฟล์ข้อความที่มีขนาดใหญ่และความเร็วเป็นกังวลมองในการใช้System.IO.StreamReaderและ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()
   }
}

(รหัสข้างต้นยังไม่ได้ทดสอบ)

อาจเป็นวิธีที่ยอดเยี่ยมกว่าในการใช้ StreamReader และ StreamWriter เพื่อแทนที่ข้อความในเอกสาร แต่ควรให้จุดเริ่มต้นที่ดีแก่คุณ


ฉันคิดว่าชุดเนื้อหาพิจารณาไฟล์การเข้ารหัส (เช่น UTF-8) แต่ไม่ใช่ Out-File stackoverflow.com/questions/5596982/…
Kiquenet

2

ผมพบว่าเล็ก ๆ น้อย ๆ ที่รู้จักกัน แต่วิธีที่เย็นที่น่าอัศจรรย์ใจที่จะทำมันได้จากเย็ตของWindows PowerShell ในการดำเนินการ คุณสามารถอ้างอิงไฟล์เช่นตัวแปรคล้ายกับ $ env: path แต่คุณต้องเพิ่มเครื่องหมายปีกกา

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

เกิดอะไรขึ้นถ้าชื่อไฟล์อยู่ในตัวแปรเช่น$myFile?
ΩmegaMan

@ ΩmegaMan hmm จนถึงตอนนี้เท่านั้น$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010

2

หากคุณต้องการแทนที่สตริงในหลายไฟล์:

ควรสังเกตว่าวิธีการต่างๆที่โพสต์ที่นี่อาจแตกต่างกันอย่างมากโดยคำนึงถึงเวลาที่ใช้ในการทำให้เสร็จ สำหรับฉันฉันมีไฟล์ขนาดเล็กเป็นประจำ เพื่อทดสอบสิ่งที่มีประสิทธิภาพมากที่สุดฉันได้แยก XML ขนาด 5.52 GB (5,933,604,999 ไบต์) เป็นไฟล์แยก 40,693 ไฟล์และวิ่งผ่านคำตอบสามข้อที่ฉันพบที่นี่:

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

คำถามเกี่ยวกับการแทนที่สตริงในไฟล์ที่กำหนดไม่ใช่หลายไฟล์
PL

1

เครดิตถึง @ rominator007

ฉันห่อมันเป็นฟังก์ชั่น (เพราะคุณอาจต้องการใช้อีกครั้ง)

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

หมายเหตุ: นี่ไม่ตรงตามตัวพิมพ์ใหญ่ - เล็ก !!!!!

ดูโพสต์นี้: String แทนที่กรณีที่ไม่สนใจ


0

สิ่งนี้ใช้ได้กับฉันโดยใช้ไดเรกทอรีการทำงานปัจจุบันใน PowerShell คุณต้องใช้FullNameคุณสมบัติไม่เช่นนั้นจะไม่ทำงานใน PowerShell เวอร์ชัน 5 ฉันต้องเปลี่ยนเวอร์ชั่นเฟรมเวิร์ก. NET เป้าหมายในCSPROJไฟล์ทั้งหมดของฉัน

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

0

ค่อนข้างเก่าและแตกต่างไปตามที่ฉันต้องการเพื่อเปลี่ยนบรรทัดในทุกกรณีของชื่อไฟล์

ยังSet-Contentไม่ได้ผลลัพธ์ที่สอดคล้องกันดังนั้นฉันจึงต้องหันไปOut-Fileใช้

รหัสด้านล่าง:


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

นี่คือสิ่งที่ดีที่สุดสำหรับฉันใน PowerShell เวอร์ชันนี้:

Major.Minor.Build.Revision

5.1.16299.98


-1

การแก้ไขเล็กน้อยสำหรับคำสั่งชุดเนื้อหา หากไม่พบสตริงการค้นหาSet-Contentคำสั่งจะว่างเปล่า (ว่าง) ไฟล์เป้าหมาย

คุณสามารถตรวจสอบก่อนว่าสตริงที่คุณต้องการมีอยู่หรือไม่ ถ้าไม่มันจะไม่เปลี่ยนอะไร

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! โปรดใช้การจัดรูปแบบคุณสามารถอ่านบทความนี้หากคุณต้องการความช่วยเหลือ
CodenameLambda

1
นี่ไม่ใช่ความจริงหากมีใครใช้คำตอบที่ถูกต้องและไม่พบการแทนที่ก็ยังคงเขียนไฟล์ แต่ไม่มีการเปลี่ยนแปลง เช่นset-content test.txt "hello hello world hello world hello"และจากนั้น(get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtจะไม่ลบไฟล์ตามที่แนะนำในนี้
Ciantic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.