สคริปต์ PowerShell เพื่อค้นหาและแทนที่สำหรับไฟล์ทั้งหมดที่มีนามสกุลเฉพาะ


124

ฉันมีไฟล์คอนฟิกูเรชันหลายไฟล์ใน Windows Server 2008 ซ้อนกันดังนี้:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

ในการกำหนดค่าของฉันฉันต้องทำการแทนที่สตริงเช่นนี้:

<add key="Environment" value="Dev"/>

จะกลายเป็น:

<add key="Environment" value="Demo"/>

ฉันคิดเกี่ยวกับการใช้สคริปต์แบทช์ แต่ไม่มีวิธีที่ดีในการทำเช่นนี้และฉันได้ยินมาว่าด้วยสคริปต์ PowerShell คุณสามารถดำเนินการนี้ได้อย่างง่ายดาย ฉันพบตัวอย่างของการค้นหา / แทนที่ แต่ฉันหวังว่าจะมีวิธีที่จะสำรวจโฟลเดอร์ทั้งหมดภายในไดเร็กทอรี C: \ Projects ของฉันและค้นหาไฟล์ใด ๆ ที่ลงท้ายด้วยนามสกุล ".config" เมื่อพบหนึ่งฉันต้องการให้มันแทนที่ค่าสตริงของฉัน

แหล่งข้อมูลที่ดีในการค้นหาวิธีการทำสิ่งนี้หรือผู้เชี่ยวชาญด้าน PowerShell ที่สามารถให้ข้อมูลเชิงลึกได้บ้าง?


1
แจ้งให้เราทราบว่าคุณดำเนินการอย่างไรหรือมีปัญหาการจัดรูปแบบแปลก ๆ กับไฟล์ที่จำเป็นต้องแก้ไข สิ่งหนึ่งที่ดีเกี่ยวกับปัญหาคือการทดสอบโดยไม่ส่งผลกระทบต่อรหัสการผลิต
Robben_Ford_Fan_boy

คำตอบ:


178

นี่เป็นความพยายามครั้งแรกที่ด้านบนของหัวของฉัน

$configFiles = Get-ChildItem . *.config -rec
foreach ($file in $configFiles)
{
    (Get-Content $file.PSPath) |
    Foreach-Object { $_ -replace "Dev", "Demo" } |
    Set-Content $file.PSPath
}

3
ฉันต้องการเพิ่มว่าการทดสอบโซลูชันที่ให้มาทั้งหมดได้ผล แต่วิธีนี้ง่ายที่สุดสำหรับการอ่าน ฉันสามารถมอบสิ่งนี้ให้กับเพื่อนร่วมงานได้และเขาก็เข้าใจสิ่งที่เกิดขึ้นได้อย่างง่ายดาย ขอบคุณสำหรับความช่วยเหลือ
Brandon

11
เพื่อให้ใช้งานได้กับไฟล์ในไดเรกทอรีย่อยคุณต้องมี ".PSPath" ที่น่าสนใจคือเมื่อฉันพยายามทำให้งานนี้โดยไม่มี () around get-content มันล้มเหลวในการเขียนเนื้อหาเนื่องจากไฟล์ถูกล็อค
Frank Schwieterman

25
เวอร์ชันสั้น (ใช้นามแฝงทั่วไป):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
Artyom

5
@Artyom อย่าลืม.หลังls. โดนต่อยเอง
Pureferret

5
UnauthorizedAccessException อาจเกิดจากโฟลเดอร์หากคุณจะลบ * .config เพื่อให้ทำงานกับไฟล์ทั้งหมด คุณสามารถเพิ่มตัวกรอง -File ใน Get-ChildItem ... ต้องใช้เวลาสักพักกว่าจะคิดออก
Amir Katz

32

PowerShell เป็นตัวเลือกที่ดี;) มันง่ายมากที่จะแจกแจงไฟล์ในไดเร็กทอรีที่กำหนดอ่านและประมวลผล

สคริปต์อาจมีลักษณะดังนี้:

Get-ChildItem C:\Projects *.config -recurse |
    Foreach-Object {
        $c = ($_ | Get-Content) 
        $c = $c -replace '<add key="Environment" value="Dev"/>','<add key="Environment" value="Demo"/>'
        [IO.File]::WriteAllText($_.FullName, ($c -join "`r`n"))
    }

ฉันแบ่งรหัสเป็นบรรทัดอื่น ๆ เพื่อให้คุณอ่านได้ โปรดทราบว่าคุณสามารถใช้ Set-Content แทนได้[IO.File]::WriteAllTextแต่จะเพิ่มบรรทัดใหม่ในตอนท้าย ด้วยWriteAllTextคุณสามารถหลีกเลี่ยงได้

$c | Set-Content $_.FullNameมิฉะนั้นรหัสจะมีลักษณะเช่นนี้


14

แนวทางนี้ใช้ได้ดี:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • เปลี่ยน "เก่า" และ "ใหม่" เป็นค่าที่ตรงกัน (หรือใช้ตัวแปร)
  • อย่าลืมวงเล็บ - โดยที่คุณจะได้รับข้อผิดพลาดในการเข้าถึง

ดังนั้นฉันจึงใช้อันนี้เพื่อการแสดงออกที่กระชับ - แต่ฉันต้องแทนที่Get-Content $_ด้วยGet-Content $_.FullNameและสิ่งที่เทียบเท่ากันSet-Contentเพื่อจัดการไฟล์ที่ไม่ได้อยู่ที่รูท
Matt Whitfield

11

ฉันจะไปกับ xml และ xpath:

dir C:\Projects\project_*\project*.config -recurse | foreach-object{  
   $wc = [xml](Get-Content $_.fullname)
   $wc.SelectNodes("//add[@key='Environment'][@value='Dev']") | Foreach-Object {$_.value = 'Demo'}  
   $wc.Save($_.fullname)  
}

11

ตัวอย่างพาวเวอร์เชลล์นี้ค้นหาอินสแตนซ์ทั้งหมดของสตริง "\ foo \" ในโฟลเดอร์และโฟลเดอร์ย่อยแทนที่ "\ foo \" ด้วย "\ bar \" และจะไม่รีไรท์ไฟล์ที่ไม่มีสตริง "\ foo \ "วิธีนี้จะช่วยให้คุณไม่ทำลายการประทับวันที่และเวลาอัปเดตล่าสุดของไฟล์ที่ไม่พบสตริง:

Get-ChildItem  -Path C:\YOUR_ROOT_PATH\*.* -recurse 
 | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\foo\\') 
           {(Get-Content $_ | ForEach {$_ -replace '\\foo\\', '\bar\'}) | Set-Content $_ }
           }

7

ฉันพบว่าความคิดเห็นของ@Artyomมีประโยชน์ แต่น่าเสียดายที่เขาไม่ได้โพสต์คำตอบ

นี่เป็นเวอร์ชันสั้นในความคิดของฉันเวอร์ชันที่ดีที่สุดของคำตอบที่ยอมรับ

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}

1
ในกรณีที่มีใครทำงานข้ามสิ่งนี้เหมือนที่ฉันทำ - ต้องการเรียกใช้สิ่งนี้โดยตรงจากไฟล์แบตช์ - อาจช่วยในการใช้foreach-objectแทน%นามแฝงเมื่อดำเนินการคำสั่งเช่นนี้ มิฉะนั้นอาจส่งผลให้เกิดข้อผิดพลาด:Expressions are only allowed as the first element of a pipeline
Dustin Halstead

สั้นไม่ได้ดีกว่าเสมอไปมีความชัดเจนมากขึ้นว่าเกิดอะไรขึ้นในเวอร์ชันยาว
Nick N.

@NickN ใช่แล้ว ขึ้นอยู่กับวัตถุประสงค์ของคุณ ฉันจะธงว่ามันเป็น POB;)
M--

6

ฉันได้เขียนฟังก์ชันตัวช่วยเล็ก ๆ น้อย ๆ เพื่อแทนที่ข้อความในไฟล์:

function Replace-TextInFile
{
    Param(
        [string]$FilePath,
        [string]$Pattern,
        [string]$Replacement
    )

    [System.IO.File]::WriteAllText(
        $FilePath,
        ([System.IO.File]::ReadAllText($FilePath) -replace $Pattern, $Replacement)
    )
}

ตัวอย่าง:

Get-ChildItem . *.config -rec | ForEach-Object { 
    Replace-TextInFile -FilePath $_ -Pattern 'old' -Replacement 'new' 
}

4

เมื่อทำการแทนที่แบบวนซ้ำจำเป็นต้องรวมพา ธ และชื่อไฟล์:

Get-ChildItem -Recurse | ForEach {  (Get-Content $_.PSPath | 
ForEach {$ -creplace "old", "new"}) | Set-Content $_.PSPath }

สิ่งนี้จะแทนที่ "เก่า" ทั้งหมดด้วย "ใหม่" ในไฟล์ทั้งหมดในโฟลเดอร์ของคุณในไดเรกทอรีปัจจุบันของคุณ


ส่วน ".PSPath" ในคำตอบของคุณช่วยฉันได้มาก แต่ฉันต้องเปลี่ยน "{$" ภายในเป็น "$ _" ฉันจะแก้ไขคำตอบของคุณ แต่ฉันไม่ได้ใช้ส่วน -creplace ของคุณ - ฉันใช้คำตอบที่ยอมรับกับ. PSPath
aaaa bbbb
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.