การสลับ Regex / หรือโอเปอเรเตอร์ (foo | bar) ใน GNU หรือ BSD Sed


28

ฉันไม่สามารถทำงานได้ เอกสารของ GNU sed บอกว่าจะหลบหนีไปป์ แต่นั่นไม่ได้ผลหรือใช้ไพพ์แบบตรงโดยไม่ต้องหลบหนี เพิ่ม parens ทำให้ไม่มีความแตกต่าง

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat|dog/Bear/g'
cat
dog
pear
banana
cat
dog

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat\|dog/Bear/g'
cat
dog
pear
banana
cat
dog

คำตอบ:


33

โดยค่าเริ่มต้นsedจะใช้นิพจน์ปกติพื้นฐาน POSIXซึ่งไม่มีตัว|ดำเนินการสำรอง หลายรุ่นsedรวมถึง GNU และ FreeBSD รองรับการสลับเป็นExtended Regular Expressionsซึ่งรวมถึงการ|สลับ วิธีที่คุณทำที่แตกต่างกัน: GNU sed ใช้-rในขณะที่FreeBSD , NetBSD , OpenBSDและOS X sed-Eใช้ รุ่นอื่น ๆ ส่วนใหญ่ไม่รองรับเลย คุณสามารถใช้ได้:

echo 'cat dog pear banana cat dog' | sed -E -e 's/cat|dog/Bear/g'

และมันจะทำงานบนระบบ BSD เหล่านั้นและsed -rกับ GNU


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

หมายเหตุแสดงความคิดเห็นว่ารุ่น BSD รองรับ-rเป็นนามแฝงที่ไม่มีเอกสารด้วย OS X ยังไม่ได้ทำในวันนี้และเครื่อง NetBSD และ OpenBSD รุ่นเก่าที่ฉันสามารถใช้งานไม่ได้ แต่ NetBSD 6.1 ก็ใช้ได้เช่นกัน Unices เชิงพาณิชย์ที่ฉันสามารถเข้าถึงได้ในระดับสากลไม่ได้ ดังนั้นทุกคำถามที่พกพาได้ค่อนข้างซับซ้อน ณ จุดนี้ แต่คำตอบง่ายๆคือเปลี่ยนเป็นawkถ้าคุณต้องการซึ่งใช้ EREs ทุกที่


BSD สามตัวที่คุณพูดถึงทั้งหมดสนับสนุน-rตัวเลือกนี้เพื่อเป็นคำพ้องความหมาย-Eสำหรับความเข้ากันได้กับ GNU sed OpenBSD และ OS X sed -Eจะตีความไปป์ที่หลบหนีเป็นไพพ์ตามตัวอักษรไม่ใช่ตัวดำเนินการสำรอง นี่คือลิงค์ที่ใช้งานได้ไปยังหน้าคน NetBSD และนี่คือลิงค์สำหรับ OpenBSD ที่มีอายุไม่สิบปี
เมียน


GNU sed สนับสนุนgnu.org/software/sed/manual/sed.html#index-_002dE-E
Isaac

9

สิ่งนี้เกิดขึ้นเนื่องจาก(a|b)เป็นการแสดงออกปกติที่ขยายไม่ใช่นิพจน์ปกติพื้นฐาน ใช้-Eตัวเลือกเพื่อจัดการกับสิ่งนี้

echo 'cat
dog
pear
banana
cat
dog'|sed -E 's/cat|dog/Bear/g'

จากsedหน้าคน:

 -E      Interpret regular expressions as extended (modern) regular
         expressions rather than basic regular expressions (BRE's).

โปรดทราบว่า-rเป็นแฟล็กอื่นสำหรับสิ่งเดียวกัน แต่-Eสามารถพกพาได้มากกว่าและจะอยู่ในข้อกำหนดรุ่น POSIX รุ่นถัดไป


6

วิธีพกพาในการทำเช่นนี้และวิธีที่มีประสิทธิภาพมากขึ้นคือการใช้ที่อยู่ คุณสามารถทำได้:

printf %s\\n cat dog pear banana cat dog |
sed -e '/cat/!{/dog/!b' -e '};cBear'

ด้วยวิธีนี้หากบรรทัดไม่มีcat string และไม่มี string dog sed b ranches ออกจากสคริปต์ให้พิมพ์บรรทัดปัจจุบันโดยอัตโนมัติและดึงในถัดไปเพื่อเริ่มรอบถัดไป ดังนั้นจึงไม่ได้ดำเนินการคำสั่งต่อไป - ซึ่งในตัวอย่างนี้cแขวนทั้งบรรทัดเพื่ออ่านแบร์แต่มันสามารถทำอะไรก็ได้

มันอาจจะเป็นที่น่าสังเกตว่าคำสั่งใด ๆ ต่อไปนี้!bในการที่sedคำสั่งสามารถเฉพาะตรงกับในบรรทัดที่มีทั้งสตริงdogหรือcat- เพื่อให้คุณสามารถดำเนินการทดสอบต่อไปโดยไม่เป็นอันตรายของการจับคู่สายที่ไม่ได้ใด ๆ - ซึ่งหมายความว่าตอนนี้คุณสามารถใช้กฎ เพื่อหนึ่งหรืออื่น ๆ เช่นกัน

แต่นั่นคือต่อไป นี่คือผลลัพธ์จากคำสั่งด้านบน:

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

คุณยังสามารถใช้ตารางการค้นหาที่มีการอ้างอิงย้อนหลังได้

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ cat dog /;x
};G;s/^\(.*\)\n.* \1 .*/Bear/;P;d'

มันทำงานได้มากขึ้นในการตั้งค่าสำหรับกรณีตัวอย่างง่ายๆนี้ แต่มันสามารถสร้างsedสคริปต์ที่ยืดหยุ่นมากขึ้นในระยะยาว

ในบรรทัดแรกฉันxเปลี่ยนพื้นที่พักและพื้นที่รูปแบบจากนั้นใส่สตริง<space>cat <space>dog<space>ลงในพื้นที่พักไว้ก่อนที่จะxเปลี่ยนกลับ

จากนั้นเป็นต้นไปทุกบรรทัดต่อไปนี้ฉันได้Gเว้นวรรคต่อท้ายพื้นที่รูปแบบจากนั้นตรวจสอบเพื่อดูว่าอักขระทั้งหมดจากจุดเริ่มต้นของบรรทัดจนถึงบรรทัดใหม่ที่เพิ่งเพิ่มตอนท้ายตรงกับสตริงที่ล้อมรอบด้วยช่องว่างหลังจากนั้น ถ้าเป็นเช่นนั้นฉันจะแทนที่ล็อตทั้งหมดด้วยBearและหากไม่มีสิ่งใดที่ทำอันตรายได้เพราะฉันต่อไปPจะทำการขึ้นบรรทัดใหม่ที่เกิดขึ้นครั้งแรกในช่องว่างรูปแบบdเท่านั้น

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

และเมื่อฉันพูดถึงความยืดหยุ่นฉันหมายถึงมัน นี่คือการแทนที่แมวด้วยBrownBearและสุนัขด้วยBlackBear :

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9]\1 \([^ ]*\) .*/\2Bear/;P;d'

###OUTPUT###
BrownBear
BlackBear
pear
banana
BrownBear
BlackBear

แน่นอนว่าคุณสามารถขยายเนื้อหาของตารางการค้นหาได้อย่างมาก - ฉันหยิบความคิดจากอีเมล Usenet ของ Greg Ubbenเกี่ยวกับเรื่องนี้เมื่อในยุค 90 เขาอธิบายว่าเขาสร้างเครื่องคิดเลขหยาบได้อย่างไรจากsed s///คำสั่งเดียว


1
วุ้ย +1 คุณมีใจชอบคิดนอกกรอบฉันต้องบอกว่า
iruvar

@ 1_CR - ดูการแก้ไขครั้งล่าสุดของฉัน - ไม่ใช่ความคิดของฉัน - ซึ่งไม่ได้บอกว่าฉันไม่ได้ชื่นชมและคิดว่าเป็นการชมเชย แต่ฉันชอบที่จะให้เครดิตที่มันครบกำหนด
mikeserv

1

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

คุณสามารถพูดได้ : sed -E '/^\/\*!(40103|40101|40111).*\/;$/d'

หรือใส่สิ่งนี้ในไฟล์ sed ของคุณ:

/^\/\*!40103.*\/;$/d
/^\/\*!40101.*\/;$/d
/^\/\*!40111.*\/;$/d

0

ต่อไปนี้เป็นเทคนิคที่ไม่ได้ใช้ประโยชน์จากตัวเลือกเฉพาะการนำไปใช้sed(เช่น-E, -r) แทนที่จะอธิบายรูปแบบเป็น regex เดียวcat|dogเราสามารถรันsedสองครั้ง:

echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat/Bear/g' | sed 's/dog/Bear/g'

มันเป็นวิธีแก้ปัญหาที่ชัดเจนจริงๆ แต่ก็คุ้มค่าที่จะแบ่งปัน มันเป็นเรื่องsedธรรมดามากกว่าสองสายรูปแบบแม้ว่าสายโซ่ที่ยาวมากๆ ก็ไม่ได้ดูดีเกินไป

ฉันมักจะใช้sed -i(ซึ่งทำงานเหมือนกันในทุกการใช้งาน) เพื่อทำการเปลี่ยนแปลงในไฟล์ รายการสตริงรูปแบบที่หลากหลายอาจรวมเข้าด้วยกันได้อย่างดีเนื่องจากผลลัพธ์ชั่วคราวจะถูกบันทึกลงในไฟล์:

for pattern in cat dog owl; do
    sed -i "s/${pattern}/Bear/g" myfile
done
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.