วิธีการแทนที่หลายสตริงในไฟล์โดยใช้ PowerShell


108

ฉันกำลังเขียนสคริปต์สำหรับปรับแต่งไฟล์คอนฟิกูเรชัน ฉันต้องการแทนที่สตริงหลายอินสแตนซ์ภายในไฟล์นี้และฉันพยายามใช้ PowerShell เพื่อทำงาน

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

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
    $_ -replace 'something1', 'something1new'
    } | Set-Content $destination_file

ฉันอยากได้อะไรแบบนี้ แต่ไม่รู้จะเขียนยังไง:

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
    $_ -replace 'something1', 'something1aa'
    $_ -replace 'something2', 'something2bb'
    $_ -replace 'something3', 'something3cc'
    $_ -replace 'something4', 'something4dd'
    $_ -replace 'something5', 'something5dsf'
    $_ -replace 'something6', 'something6dfsfds'
    } | Set-Content $destination_file

คำตอบ:


168

ทางเลือกหนึ่งคือเชื่อม-replaceโยงการดำเนินการเข้าด้วยกัน `ในตอนท้ายของแต่ละบรรทัดหนีขึ้นบรรทัดใหม่ที่ก่อให้เกิด PowerShell เพื่อดำเนินการต่อการแยกวิเคราะห์การแสดงออกในบรรทัดถัดไป:

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
    $_ -replace 'something1', 'something1aa' `
       -replace 'something2', 'something2bb' `
       -replace 'something3', 'something3cc' `
       -replace 'something4', 'something4dd' `
       -replace 'something5', 'something5dsf' `
       -replace 'something6', 'something6dfsfds'
    } | Set-Content $destination_file

อีกทางเลือกหนึ่งคือการกำหนดตัวแปรกลาง:

$x = $_ -replace 'something1', 'something1aa'
$x = $x -replace 'something2', 'something2bb'
...
$x

$ original_file == $ destination_file ได้หรือไม่ ในขณะที่ฉันกำลังแก้ไขไฟล์เดียวกันกับแหล่งที่มาของฉัน?
cquadrini

เนื่องจากวิธีที่ PowerShell cmdlets สตรีมอินพุต / ouput ของพวกเขาฉันไม่เชื่อว่ามันจะใช้งานได้ในการเขียนลงไฟล์เดียวกันในไปป์ไลน์เดียวกัน $c = Get-Content $original_file; $c | ... | Set-Content $original_fileแต่คุณสามารถทำสิ่งที่ชอบ
dahlbyk

คุณมีปัญหาเกี่ยวกับการเข้ารหัสไฟล์โดยใช้ชุดเนื้อหาที่ไม่มีการเข้ารหัสดั้งเดิมหรือไม่? ตัวอย่างเช่นการเข้ารหัส UTF-8 หรือ ANSI
Kiquenet

1
ใช่ PowerShell คือ ... ไม่มีประโยชน์เช่นนั้น คุณต้องตรวจจับการเข้ารหัสด้วยตัวเองเช่นgithub.com/dahlbyk/posh-git/blob/…
dahlbyk

25

เพื่อให้โพสต์โดย George Howarth ทำงานได้อย่างถูกต้องโดยมีการแทนที่มากกว่าหนึ่งรายการคุณต้องลบตัวแบ่งออกให้กำหนดผลลัพธ์ให้กับตัวแปร ($ line) จากนั้นจึงส่งออกตัวแปร:

$lookupTable = @{
    'something1' = 'something1aa'
    'something2' = 'something2bb'
    'something3' = 'something3cc'
    'something4' = 'something4dd'
    'something5' = 'something5dsf'
    'something6' = 'something6dfsfds'
}

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'

Get-Content -Path $original_file | ForEach-Object {
    $line = $_

    $lookupTable.GetEnumerator() | ForEach-Object {
        if ($line -match $_.Key)
        {
            $line = $line -replace $_.Key, $_.Value
        }
    }
   $line
} | Set-Content -Path $destination_file

นี่เป็นแนวทางที่ดีที่สุดที่ฉันเคยเห็นมา ปัญหาเดียวคือฉันต้องอ่านเนื้อหาไฟล์ทั้งหมดเป็นตัวแปรก่อนเพื่อใช้เส้นทางไฟล์ต้นทาง / ปลายทางเดียวกัน
angularsen

ดูเหมือนคำตอบที่ดีที่สุดแม้ว่าฉันจะเห็นพฤติกรรมแปลก ๆ บางอย่างที่จับคู่ไม่ถูกต้อง เช่นในกรณีที่คุณมีตารางแฮชที่มีค่าฐานสิบหกเป็นสตริง (0x0, 0x1, 0x100, 0x10000) และ 0x10000 จะตรงกับ 0x1
Lorek

14

ด้วย PowerShell เวอร์ชัน 3 คุณสามารถเชื่อมต่อการโทรแทนที่เข้าด้วยกัน:

 (Get-Content $sourceFile) | ForEach-Object {
    $_.replace('something1', 'something1').replace('somethingElse1', 'somethingElse2')
 } | Set-Content $destinationFile

ทำงานได้ดี + รสชาติคล่อง
hdoghmen

10

สมมติว่าคุณมีได้เพียงบรรทัดเดียว'something1'หรือ'something2'ฯลฯ ต่อบรรทัดคุณสามารถใช้ตารางการค้นหา:

$lookupTable = @{
    'something1' = 'something1aa'
    'something2' = 'something2bb'
    'something3' = 'something3cc'
    'something4' = 'something4dd'
    'something5' = 'something5dsf'
    'something6' = 'something6dfsfds'
}

$original_file = 'path\filename.abc'
$destination_file =  'path\filename.abc.new'

Get-Content -Path $original_file | ForEach-Object {
    $line = $_

    $lookupTable.GetEnumerator() | ForEach-Object {
        if ($line -match $_.Key)
        {
            $line -replace $_.Key, $_.Value
            break
        }
    }
} | Set-Content -Path $destination_file

ถ้าคุณสามารถมีได้มากกว่าหนึ่งในบรรดาเพียงลบbreakในifคำสั่ง


ฉันเห็น TroyBramley เพิ่ม $ line ก่อนบรรทัดสุดท้ายเพื่อเขียนบรรทัดใด ๆ ที่ไม่มีการเปลี่ยนแปลง ตกลง. ในกรณีของฉันฉันเปลี่ยนทุกบรรทัดที่ต้องการการเปลี่ยนเท่านั้น
Cliffclof

8

ตัวเลือกที่สามสำหรับหนึ่งซับแบบไพพ์ไลน์คือการซ้อน -replaces:

PS> ("ABC" -replace "B","C") -replace "C","D"
ADD

และ:

PS> ("ABC" -replace "C","D") -replace "B","C"
ACD

สิ่งนี้จะรักษาคำสั่งดำเนินการอ่านง่ายและเข้ากับไปป์ไลน์ได้อย่างเรียบร้อย ฉันชอบใช้วงเล็บในการควบคุมอย่างชัดเจนเอกสารประกอบตัวเอง ฯลฯ มันใช้งานได้โดยไม่มีพวกเขา แต่คุณจะเชื่อถือได้มากแค่ไหน?

-Replace เป็นตัวดำเนินการเปรียบเทียบซึ่งยอมรับวัตถุและส่งคืนวัตถุที่แก้ไขโดยสันนิษฐาน นี่คือเหตุผลที่คุณสามารถซ้อนหรือซ้อนกันได้ดังที่แสดงด้านบน

โปรดมอง:

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