วิธีการแทนที่หลายรูปแบบพร้อมกันด้วย sed?


231

สมมติว่าฉันมีสตริง 'abbc' และฉันต้องการแทนที่:

  • ab -> bc
  • bc -> ab

หากฉันลองทั้งสองแทนที่ผลลัพธ์จะไม่ใช่สิ่งที่ฉันต้องการ:

echo 'abbc' | sed 's/ab/bc/g;s/bc/ab/g'
abab

ดังนั้นคำสั่ง sed ใดที่ฉันสามารถใช้เพื่อแทนที่ดังนี้

echo abbc | sed SED_COMMAND
bcab

แก้ไข : ที่จริงแล้วข้อความอาจมีรูปแบบมากกว่า 2 รูปแบบและฉันไม่ทราบว่าจะต้องแทนที่จำนวนเท่าไหร่ เนื่องจากมีคำตอบที่บอกว่าsedเป็นตัวแก้ไขสตรีมและการแทนที่มีความตะกละฉันคิดว่าฉันจะต้องใช้ภาษาสคริปต์สำหรับสิ่งนั้น


คุณต้องการเปลี่ยนหลายครั้งในบรรทัดเดียวกันหรือไม่? หากไม่เพียงแค่วางgธงจากทั้งสองs///คำสั่งเหล่านั้นและที่จะทำงาน
Etan Reisner

คุณพลาดจุดคำถามของฉัน ฉันหมายถึงคุณจำเป็นต้องทำการเปลี่ยนแต่ละครั้งมากกว่าหนึ่งครั้งในบรรทัดเดียวกัน มีมากกว่าหนึ่งรายการที่ตรงกันสำหรับab หรือ bcในอินพุตต้นฉบับ
Etan Reisner

ขออภัย @EtanReisner ฉันเข้าใจผิดแล้ว Anwser คือใช่ ข้อความสามารถมีได้หลายรายการ
DaniloNC

คำตอบ:


342

อาจจะเป็นสิ่งนี้:

sed 's/ab/~~/g; s/bc/ab/g; s/~~/bc/g'

แทนที่~ด้วยอักขระที่คุณรู้ว่าจะไม่อยู่ในสตริง


9
GNU sed จับ NULs เพื่อให้คุณสามารถใช้สำหรับ\x0 ~~
jthill

3
มีgความจำเป็นและมันทำอะไร?
ลี

12
@Lee gนั้นใช้สำหรับทั่วโลก - มันจะแทนที่อินสแตนซ์ทั้งหมดของรูปแบบในแต่ละบรรทัดแทนที่จะเป็นแค่อันแรก (ซึ่งเป็นพฤติกรรมเริ่มต้น)
naught101

1
โปรดดูคำตอบของฉันstackoverflow.com/a/41273117/539149สำหรับรูปแบบของคำตอบของ ooga ที่สามารถแทนที่ชุดค่าผสมหลายชุดพร้อมกันได้
Zack Morris

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

33

ฉันมักจะใช้หลายงบกับ "-e"

$ sed -e 's:AND:\n&:g' -e 's:GROUP BY:\n&:g' -e 's:UNION:\n&:g' -e 's:FROM:\n&:g' file > readable.sql

สิ่งนี้จะผนวก '\ n' ก่อนหน้า AND ทั้งหมด, GROUP BY's, UNION และ FROM's ในขณะที่ '&' หมายถึงสตริงที่ตรงกันและ '\ n &' หมายความว่าคุณต้องการแทนที่สตริงที่ตรงกันด้วย '\ n' ก่อน 'จับคู่ '


14

นี่คือความแตกต่างของคำตอบของ oogaที่ใช้ได้กับการค้นหาหลายครั้งและแทนที่คู่โดยไม่ต้องตรวจสอบว่าค่าจะถูกนำมาใช้ซ้ำได้อย่างไร

sed -i '
s/\bAB\b/________BC________/g
s/\bBC\b/________CD________/g
s/________//g
' path_to_your_files/*.txt

นี่คือตัวอย่าง:

ก่อน:

some text AB some more text "BC" and more text.

หลังจาก:

some text BC some more text "CD" and more text.

โปรดทราบว่า\bหมายถึงขอบเขตของคำซึ่งเป็นสิ่งที่ป้องกันไม่ให้________รบกวนการค้นหา (ฉันใช้ GNU sed 4.2.2 บน Ubuntu) หากคุณไม่ได้ใช้การค้นหาขอบเขตคำศัพท์เทคนิคนี้อาจใช้ไม่ได้

นอกจากนี้โปรดทราบว่าสิ่งนี้จะให้ผลลัพธ์เช่นเดียวกับการลบs/________//gและต่อ&& sed -i 's/________//g' path_to_your_files/*.txtท้ายคำสั่ง แต่ไม่ต้องการระบุพา ธ สองครั้ง

รูปแบบทั่วไปเกี่ยวกับเรื่องนี้จะมีการใช้งาน\x0หรือ_\x0_ในสถานที่ของ________ถ้าคุณรู้ว่าไม่มี nulls ปรากฏในไฟล์ของคุณตามที่แนะนำ jthill


ฉันเห็นด้วยกับความคิดเห็นของ hagello ด้านบนเกี่ยวกับการไม่ตั้งสมมติฐานว่าข้อมูลอาจมีอะไร ดังนั้นฉันเองรู้สึกว่านี่เป็นทางออกที่เชื่อถือได้มากที่สุดนอกเหนือจากท่อ seds ที่ด้านบนของแต่ละอื่น ๆ ( sed 's/ab/xy/' | sed 's/cd/ab/' .....)
4154

12

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

echo 'abcd' | sed -e 's/ab/xy/;s/cd/ab/;s/xy/cd/'


4

สิ่งนี้อาจใช้ได้กับคุณ (GNU sed):

sed -r '1{x;s/^/:abbc:bcab/;x};G;s/^/\n/;:a;/\n\n/{P;d};s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/;ta;s/\n(.)/\1\n/;ta' file

สิ่งนี้ใช้ตารางการค้นหาที่จัดทำและเก็บไว้ในพื้นที่พัก (HS) แล้วต่อท้ายแต่ละบรรทัด เครื่องหมายที่ไม่ซ้ำกัน (ในกรณีนี้\n) จะถูกนำไปรวมไว้ที่จุดเริ่มต้นของบรรทัดและใช้เป็นวิธีในการค้นหาตลอดความยาวของบรรทัด เมื่อเครื่องหมายมาถึงจุดสิ้นสุดของบรรทัดกระบวนการจะเสร็จสิ้นและจะพิมพ์ตารางการค้นหาและเครื่องหมายที่ถูกทิ้ง

NB ตารางการค้นหาถูกเตรียมไว้ล่วงหน้าตั้งแต่จุดเริ่มต้นและเครื่องหมายที่สองที่ไม่ซ้ำกัน (ในกรณีนี้:) ถูกเลือกเพื่อไม่ให้ขัดแย้งกับสตริงการแทนที่

ด้วยความคิดเห็นบางส่วน:

sed -r '
  # initialize hold with :abbc:bcab
  1 {
    x
    s/^/:abbc:bcab/
    x
  }

  G        # append hold to patt (after a \n)

  s/^/\n/  # prepend a \n

  :a

  /\n\n/ {
    P      # print patt up to first \n
    d      # delete patt & start next cycle
  }

  s/\n(ab|bc)(.*\n.*:(\1)([^:]*))/\4\n\2/
  ta       # goto a if sub occurred

  s/\n(.)/\1\n/  # move one char past the first \n
  ta       # goto a if sub occurred
'

ตารางทำงานดังนี้:

   **   **   replacement
:abbc:bcab
 **   **     pattern

3

อาจเป็นวิธีที่ง่ายกว่าสำหรับการเกิดรูปแบบเดียวที่คุณสามารถลองได้ดังนี้: echo 'abbc' | sed 's / ab / bc /; s / bc / ab / 2'

ผลลัพธ์ของฉัน:

 ~# echo 'abbc' | sed 's/ab/bc/;s/bc/ab/2'
 bcab

สำหรับรูปแบบที่เกิดขึ้นหลายรายการ:

sed 's/\(ab\)\(bc\)/\2\1/g'

ตัวอย่าง

~# cat try.txt
abbc abbc abbc
bcab abbc bcab
abbc abbc bcab

~# sed 's/\(ab\)\(bc\)/\2\1/g' try.txt
bcab bcab bcab
bcab bcab bcab
bcab bcab bcab

หวังว่านี่จะช่วยได้ !!


2

Tcl มีbuiltinสำหรับสิ่งนี้

$ tclsh
% string map {ab bc bc ab} abbc
bcab

สิ่งนี้ทำงานโดยการเดินสตริงที่ตัวละครในเวลาทำการเปรียบเทียบสตริงเริ่มต้นที่ตำแหน่งปัจจุบัน

ใน Perl:

perl -E '
    sub string_map {
        my ($str, %map) = @_;
        my $i = 0;
        while ($i < length $str) {
          KEYS:
            for my $key (keys %map) {
                if (substr($str, $i, length $key) eq $key) {
                    substr($str, $i, length $key) = $map{$key};
                    $i += length($map{$key}) - 1;
                    last KEYS;
                }
            }
            $i++;
        }
        return $str;
    }
    say string_map("abbc", "ab"=>"bc", "bc"=>"ab");
'
bcab

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