Regex & Sed / Perl: จับคู่คำที่ไม่นำหน้าด้วยคำอื่น


11

ฉันต้องการใช้sedหรือperlแทนที่คำทั้งหมดที่ไม่มีคำที่อยู่ข้างหน้า

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

ข้อความตัวอย่างอาจมีลักษณะเช่นนี้:

John Smith and Jane Johnson talk about Smith's car.

ฉันอยากให้มันเป็นแบบนี้:

John Smith and Jane Johnson talk about John's car.

ถ้าฉันแค่ทำsed 's/Smith/John/' fileแล้วฉันจะมี:

John John and Jane Johnson talk about John's car.

ชื่อแรกที่มาก่อนนามสกุลจะเหมือนกันเสมอ ฉันไม่ได้มีการจัดการกับและJohn Smith Frank Smithฉันต้องการวิธีการจับคู่Smithที่ไม่ได้นำJohnหน้า


คุณกำลังพูดถึงเรื่องอะไร
Ignacio Vazquez-Abrams

GNU sed 4.2.1 บน Linux
jonescb

คำตอบ:


8

คงเป็นเรื่องง่ายด้วยภาษาใด ๆ ที่นิพจน์ทั่วไปสามารถมองเห็นได้ แน่นอน Perl เป็นรายการแรก:

perl -pe 's/(?<!John\W)Smith/John/g' <<< "John Smith and Jane Johnson talk about Smith's car."

จุดอ่อนกำลังมีตัวละครที่ไม่ใช่คำมากกว่าหนึ่งคำระหว่าง“ จอห์น” และ“ สมิ ธ ” แต่น่าเสียดายปริมาณเช่น+สำหรับ\Wจะยก“ความยาวตัวแปร lookbehind ไม่ได้ดำเนินการผิดพลาด”


6

แก้ไข .. ความคิดเห็นของคุณอีกครั้ง .. นี่เป็นสคริปต์ใหม่ที่ไม่เกี่ยวข้องกับตัวเอง (เช่น) William Smith มันทำให้รูปแบบสับสนชั่วคราวซึ่งทำให้เหมือนSmith (ไม่เปลี่ยนแปลง)

sed -r 's/\<(John) (Smith)\>/\1\x01x\2/g; 
        s/\<Smith\>/John/g;  s/\x01x/ /g'

หากคุณมีความกังวลเกี่ยวกับนาย Mr นาง ...แล้วงานนี้

sed -r 's/\<(John|((M(r|rs|s))\.?)) (Smith)\>/\1\x01x\5/g
        s/\<Smith\>/John/g; s/\x01x/ /g'

คุณสามารถรองรับWilliam ได้โดยการเพิ่มชื่อของเขาลงในรายการหรือเช่น
sed -r 's/\<(William|John|...


นี่คือสคริปต์ต้นฉบับ

sed -r 's/(^|[[:punct:]] |\<[a-z]+ )(Smith\>)/\1John/'

วิธีนี้ใช้ได้ผล แต่ปัญหาหนึ่งที่ฉันพบคือถ้าคำก่อนหน้าสมิ ธ เป็นตัวพิมพ์ใหญ่ (เช่นมาจากคำแรกในประโยค) มันก็ไม่ตรงกัน วิธีการแก้ปัญหา perl โดย manatwork ไม่มีปัญหานั้นแม้ว่ามันจะล้มเหลวในสถานการณ์อื่น ๆ โชคดีที่ไฟล์ข้อความของฉันไม่มีชื่อเช่นนายหรือคนที่มีนามสกุลเหมือนกัน
jonescb

ใช่ขอบคุณ ... ฉันโพสต์สคริปต์ที่ใช้
กระสุน

1
 sed -r 's/([^John] )Smith/\1John/g;s/([^Jane] )Johnson/\1Jane/g'

() จะจับภาพไม่ใช่ชื่อแรกก่อนนามสกุลดังนั้นพวกเขาจะกลับมาแทนที่ใน

แก้ไข

@ manatwork กิลส์

คุณถูก. เกี่ยวกับ

sed -r 's/(John Smith)/temp1/g;s/Smith/John/g;s/temp1/John Smith/g'

ดูเหมือนว่าจะทำเคล็ดลับ


สิ่งนี้จะล้มเหลวหากไม่มีคำอื่น ๆ อยู่ข้างหน้าชื่อเช่น“ Smith และ Jane Johnson พูดถึงรถของ Smith”
จัดการ

2
[^John]ตรงกับตัวละครตัวหนึ่งซึ่งจะต้องเป็นหนึ่งJ, o, หรือh nฉันสงสัยว่านี่คือสิ่งที่คุณต้องการ ไม่มีโครงสร้างการปฏิเสธในนิพจน์ทั่วไป (Perl มี(?!…)และ(?<!…)แต่ถ้าคุณคิดว่ามันเป็นการปฏิเสธมันอาจจะไม่ทำในสิ่งที่คุณคาดหวัง)
Gilles 'หยุดความชั่วร้าย'

@Juaco: ผลงาน take-2 ของคุณ แต่มีความอ่อนไหวต่อข้อมูลที่ไม่คาดคิด ฉันใช้วิธีการที่คล้ายกัน (แม้ว่าจะลังเลเล็กน้อย) เพราะการใช้sedโดยไม่ใช้มันจะทำให้ตรรกะของคนที่มีอาการบวม ... temp1เกือบจะเป็นเรื่องปกติ แต่! ระวังรถบัสนั้น เพื่อลดความเป็นไปได้นี้ฉันเชื่อว่ามันเป็นการดีที่จะใช้ตัวอักษรที่ (เกือบ) ไม่เคยเกิดขึ้นในไฟล์ข้อความละติน - สคริปต์เช่นค่า Hex \ x01 \ x02 หรือการรวมกันของพวกเขาหรืออาจ \ xe188b4 UTF-8 locale (ሴ - ชาติพันธุ์ที่ไร้เหตุผลดู) .. เช่น echo -e 'Z' |sed 's/./\xe1\x88\xb4/'=> เมื่อโลแคลคือ UTF-8 ..
Peter.O
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.