จับคู่ข้อความหลายบรรทัดโดยใช้นิพจน์ทั่วไป


174

ฉันพยายามจับคู่ข้อความหลายบรรทัดโดยใช้ java เมื่อฉันใช้PatternคลาสกับโมดิPattern.MULTILINEฟายเออร์ฉันสามารถจับคู่ได้ แต่ฉันไม่สามารถทำได้(?m).

รูปแบบเดียวกันกับ(?m)และใช้String.matchesดูเหมือนจะไม่ทำงาน

ฉันแน่ใจว่าฉันพลาดอะไรบางอย่าง แต่ไม่รู้จะทำอะไร ฉันไม่ค่อยเก่งในการแสดงออกปกติ

นี่คือสิ่งที่ฉันพยายาม

String test = "User Comments: This is \t a\ta \n test \n\n message \n";

String pattern1 = "User Comments: (\\W)*(\\S)*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: (\\W)*(\\S)*";
System.out.println(test.matches(pattern2));  //false - why?

คำตอบ:


298

ก่อนอื่นคุณใช้ตัวดัดแปลงภายใต้สมมติฐานที่ไม่ถูกต้อง

Pattern.MULTILINEหรือ(?m)บอกให้ Java ยอมรับจุดยึด^และ$จับคู่ที่จุดเริ่มต้นและจุดสิ้นสุดของแต่ละบรรทัด (ไม่เช่นนั้นจะจับคู่ที่จุดเริ่มต้น / สิ้นสุดของสตริงทั้งหมดเท่านั้น)

Pattern.DOTALLหรือ(?s)บอกให้ Java อนุญาตให้จุดจับคู่อักขระขึ้นบรรทัดใหม่ด้วย

ประการที่สองในกรณีของคุณ regex ล้มเหลวเนื่องจากคุณใช้matches()วิธีที่คาดว่า regex จะจับคู่สตริงทั้งหมดซึ่งแน่นอนว่าใช้งานไม่ได้เนื่องจากมีอักขระบางตัวเหลืออยู่หลังจาก(\\W)*(\\S)*จับคู่แล้ว

ดังนั้นหากคุณแค่มองหาสตริงที่ขึ้นต้นด้วยUser Comments:ใช้ regex

^\s*User Comments:\s*(.*)

ด้วยPattern.DOTALLตัวเลือก:

Pattern regex = Pattern.compile("^\\s*User Comments:\\s+(.*)", Pattern.DOTALL);
Matcher regexMatcher = regex.matcher(subjectString);
if (regexMatcher.find()) {
    ResultString = regexMatcher.group(1);
} 

ResultString จะมีข้อความหลังจากนั้น User Comments:


ฉันพยายามค้นหารูปแบบที่จะจับคู่สตริงที่ขึ้นต้นด้วย "ความคิดเห็นของผู้ใช้:" หลังจาก "ความคิดเห็นของผู้ใช้:" นี้เป็นสิ่งที่ผู้ใช้ป้อนใน textarea และดังนั้นจึงสามารถมีอะไรก็ได้ - แม้แต่บรรทัดใหม่ ดูเหมือนว่าฉันจะต้องเรียนรู้มากใน regex แล้ว ...
Nivas

2
งานนี้ (ขอบคุณ!) (?s)User Comments:\s*(.*)ฉันพยายามแบบแผน จากคำตอบของ @Amarghosh User Comments: [\\s\\S]*ฉันมีแบบแผน ในหมู่คนเหล่านี้มีวิธีที่ดีกว่าหรือแนะนำหรือเป็นเพียงสองวิธีที่แตกต่างกันในการทำเช่นเดียวกัน?
Nivas

3
พวกเขาทั้งสองมีความหมายเหมือนกัน; [\s\S]ค่อนข้างชัดเจนมากขึ้น ("จับคู่อักขระใด ๆ ที่เป็นช่องว่างหรือไม่ใช่ช่องว่าง") .ง่ายต่อการอ่าน แต่คุณต้องค้นหา(?s)หรือDOTALLแก้ไขเพื่อหาว่ามีการขึ้นบรรทัดใหม่หรือไม่ ฉันชอบ.กับPattern.DOTALLชุดธง (นี่คือง่ายต่อการอ่านและจำกว่า(?s)ในความคิดของคุณควรใช้สิ่งที่คุณรู้สึกสบายที่สุดด้วย..
ทิม Pietzcker

.*ด้วยDOTALLสามารถอ่านได้มากขึ้น ฉันใช้อีกอันหนึ่งเพื่อแสดงว่าปัญหาอยู่ในความแตกต่างระหว่าง str.matches และ matcher.find และไม่ใช่แฟล็ก +1
Amarghosh

ฉันชอบ.*ที่มีPattern.DOTALLแต่จะต้องไปกับ (s) String.matchesเพราะผมต้องใช้
Nivas

42

สิ่งนี้ไม่มีส่วนเกี่ยวข้องกับการตั้งค่าสถานะ MULTILINE สิ่งที่คุณเห็นคือความแตกต่างระหว่างfind()และmatches()วิธีการ find()ประสบความสำเร็จถ้าการแข่งขันสามารถพบได้ทุกที่ในสตริงเป้าหมายในขณะที่matches()คาดว่า regex จับคู่สตริงทั้งหมด

Pattern p = Pattern.compile("xyz");

Matcher m = p.matcher("123xyzabc");
System.out.println(m.find());    // true
System.out.println(m.matches()); // false

Matcher m = p.matcher("xyz");
System.out.println(m.matches()); // true

นอกจากนี้MULTILINEไม่ได้หมายความว่าคุณคิดอย่างไร หลายคนดูเหมือนจะข้ามไปสู่ข้อสรุปที่คุณต้องใช้การตั้งค่าสถานะนั้นถ้าสตริงเป้าหมายของคุณมีการขึ้นบรรทัดใหม่ - นั่นคือถ้ามันมีหลายสายตรรกะ ผมเคยเห็นหลายคำตอบที่นี่ใน SO ที่มีผล แต่ในความเป็นจริงทั้งหมดที่ธงไม่สามารถเปลี่ยนพฤติกรรมของจุดยึดที่และ ^$

โดยปกติจะ^ตรงกับจุดเริ่มต้นของสตริงเป้าหมายและ$ตรงกับส่วนท้ายสุด (หรือก่อนขึ้นบรรทัดใหม่ในตอนท้าย แต่เราจะละจากจุดนี้ไปก่อน) แต่ถ้าสตริงมีการขึ้นบรรทัดใหม่คุณสามารถเลือก^และ$จับคู่ที่จุดเริ่มต้นและจุดสิ้นสุดของโลจิคัลบรรทัดใด ๆ ไม่เพียงแค่การเริ่มต้นและสิ้นสุดของสตริงทั้งหมดโดยการตั้งค่าแฟล็ก MULTILINE

ดังนั้นลืมเกี่ยวกับสิ่งที่MULTILINE หมายถึงและเพียงแค่จำไว้ว่าสิ่งที่มันไม่ : การเปลี่ยนแปลงพฤติกรรมของ^และ$เบรก DOTALLโหมดเดิมเรียกว่า "บรรทัดเดียว" (และยังคงมีอยู่ในบางรสชาติรวมถึง Perl และ. NET) และทำให้เกิดความสับสนที่คล้ายกัน เราโชคดีที่ผู้พัฒนา Java ใช้ชื่อที่มีความหมายมากกว่านี้ในกรณีนั้น แต่ไม่มีทางเลือกที่สมเหตุสมผลสำหรับโหมด "multiline"

ใน Perl ที่ความคลั่งไคล้นี้เริ่มต้นพวกเขายอมรับความผิดพลาดและกำจัดโหมด "multiline" และ "single-line" ใน Perl 6 regexes ในอีกยี่สิบปีข้างหน้าโลกที่เหลืออาจจะตามหลังชุดสูท


5
ยากที่จะเชื่อว่าพวกเขาใช้ชื่อวิธีการ "# ตรงกัน" เพื่อหมายถึง "จับคู่ทั้งหมด" yikes
rogerdpack

@ alan-moore ขออภัยฉันลงสิ่งนี้แม้ว่าจะถูกต้อง [ต้องการนอนมากกว่า :)]
Raymond Naseef

22

str.matches(regex) ทำงานแบบ Pattern.matches(regex, str)ที่พยายามจับคู่ลำดับอินพุตทั้งหมดกับรูปแบบและผลตอบแทน

trueถ้าและเฉพาะในกรณีที่ลำดับการป้อนข้อมูลทั้งหมดตรงกับรูปแบบของผู้จับคู่นี้

ในขณะที่matcher.find() พยายามค้นหาลำดับถัดไปของลำดับอินพุตที่ตรงกับรูปแบบและผลตอบแทน

trueถ้าหากว่าเป็นsubsequenceของลำดับการป้อนข้อมูลที่ตรงกับรูปแบบของการจับคู่นี้

ดังนั้นปัญหาอยู่กับ regex ลองทำสิ่งต่อไปนี้

String test = "User Comments: This is \t a\ta \ntest\n\n message \n";

String pattern1 = "User Comments: [\\s\\S]*^test$[\\s\\S]*";
Pattern p = Pattern.compile(pattern1, Pattern.MULTILINE);
System.out.println(p.matcher(test).find());  //true

String pattern2 = "(?m)User Comments: [\\s\\S]*^test$[\\s\\S]*";
System.out.println(test.matches(pattern2));  //true

ดังนั้นในระยะสั้น(\\W)*(\\S)*ส่วนใน regex แรกของคุณจะจับคู่สตริงว่างเปล่าซึ่ง*หมายถึงเหตุการณ์ที่เกิดขึ้นเป็นศูนย์หรือมากกว่าและสตริงที่ตรงกันจริงUser Comments:ไม่ใช่สตริงทั้งหมดตามที่คุณคาดหวัง คนที่สองล้มเหลวขณะที่มันพยายามที่จะตรงกับสตริงทั้ง แต่มันไม่สามารถเป็น\\Wตรงกับตัวละครที่ไม่ใช่คำเช่น[^a-zA-Z0-9_]และตัวอักษรตัวแรกเป็นTตัวอักษรคำว่า


ฉันต้องการจับคู่สตริงใด ๆ ที่ขึ้นต้นด้วย "ความคิดเห็นของผู้ใช้" และสตริงสามารถมีบรรทัดใหม่ได้เช่นกัน ดังนั้นฉันจึงใช้รูปแบบUser Comments: [\\s\\S]*และสิ่งนี้ได้ผล (ขอบคุณ!) จากคำตอบของ @Tim ฉันได้รูปแบบUser Comments:(.*)นี่ก็โอเคทีนี้มีวิธีที่แนะนำหรือดีกว่าในหมู่พวกเขาหรือว่าทั้งสองวิธีทำแบบเดียวกันหรือไม่?
Nivas

@Nivas ฉันไม่คิดว่าจะมีประสิทธิภาพที่แตกต่างกันอย่างชาญฉลาด แต่ฉันคิดว่า(.*)พร้อมกับDOTALLธงชัดเจน / อ่านง่ายกว่า([\\s\\S]*)
Amarghosh

นี่คือคำตอบที่ดีที่สุด .... ให้ทั้งการเข้าถึงรหัส Java และตัวเลือกรูปแบบสตริงสำหรับความสามารถ MultiLine
GoldBishop

0

ธงหลายบรรทัดบอก regex ให้จับคู่รูปแบบกับแต่ละบรรทัดตรงข้ามกับสตริงทั้งหมดเพื่อจุดประสงค์ของคุณบัตรเสริมจะพอเพียง

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