เหตุใดการจับคู่ Regex นี้จึงแสดงเฉพาะคำแรกในตัวแปรการจับภาพไม่ใช่ทั้งบรรทัด

Aug 20 2020

ฉันค่อนข้างใหม่สำหรับ Perl และ Regexes ดังนั้นโปรดอดทนรอหากฉันใช้คำศัพท์ผิด ๆ

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

BRIAN: Hello, mother.
MANDY: Don't you 'hello mother' me. What are all those people doing out ther    e?!
BRIAN: Oh. Well-- well, I, uh--
MANDY: Come on! What have you been up to, my lad?!
BRIAN: Well, uh, I think they must have popped by for something.
MANDY: 'Popped by'?! 'Swarmed by', more like! There's a multitude out there!
BRIAN: Mm, they-- they started following me yesterday.
MANDY: Well, they can stop following you right now. Now, stop following my son! You ought to be ashamed of yourselves.
FOLLOWERS: The Messiah! The Messiah! Show us the Messiah!
MANDY: The who?
FOLLOWERS: The Messiah!
MANDY: Huh, there's no Messiah in here. There's a mess, all right, but no Me    ssiah. Now, go away!
FOLLOWERS: The Messiah! The Messiah!
MANDY: Ooooh.
FOLLOWERS: Show us the Messiah! The Messiah! The Messiah! Show us the Messiah!
MANDY: Now, you listen here! He's not the Messiah. He's a very naughty boy! Now, go away!

และนี่คือรหัส:

  1 use strict;
  2 use warnings;
  3 
  4 my $filename = "movie_script.txt"; 5 my $charname = $ARGV[0]; 6 7 if (-e $filename) {
  8     print "File exists.\n";
  9 } else {
 10     print "Alas, file does not exist.\n";
 11     exit 1;
 12 }
 13 
 14 open(my $fh, '<', $filename);
 15 
 16 my $match = "^($charname):.*/i";
 17 
 18 while (my $line = <$fh>) {
 19     if ( $line =~ m/^($charname):.*/i ) {
 20         $line =~ s/($charname): //i;
 21         print $line; 22 } 23 } 24 print "\n"; 25 close $fh;

รหัสใช้งานได้ดีและเมื่อฉันเรียกใช้โปรแกรมโดยส่ง "Brian" เป็นอาร์กิวเมนต์บรรทัดคำสั่งมันจะแสดงเฉพาะบรรทัดของ Brian เท่านั้นหากฉันป้อน "Mandy" หรือ "Followers" (ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่)

ฉันพยายามทำความเข้าใจว่าตัวแปรการจับภาพทำงานอย่างไรเพื่อให้สามารถจัดการกับไฟล์ข้อความได้อย่างละเอียดอ่อนมากขึ้น เมื่อฉันเปลี่ยนบรรทัดที่ 21 เป็นprint $1แทนprint $lineฉันคาดหวังว่าผลลัพธ์จะเหมือนกันเพราะ regex ที่ฉันให้มาควรตรงกับอินสแตนซ์ของ "BRIAN" ใด ๆ ตามด้วยเครื่องหมายจุดคู่จากนั้นจึงจะมีอักขระจำนวนเท่าใดก็ได้จนกว่าจะสิ้นสุด เส้น.

อย่างไรก็ตามเมื่อฉันทำสิ่งนี้มันจะกลับมา:

BRIANBRIANBRIANBRIAN

... แทนที่จะเป็นสี่บรรทัดเป็นของ Brian ดังนั้นฉันจึงลองสลับบรรทัดที่ 22 และ 21 โดยใส่print $1คำสั่งก่อนการแทนที่ regex แต่ก็ให้ผลลัพธ์เดียวกัน

เหตุใดตัวแปรการจับจึงแสดงเฉพาะคำแรก "BRIAN" ไม่ใช่ทั้งบรรทัด ฉันแน่ใจว่ามันเป็นข้อผิดพลาดที่ง่ายมาก แต่ฉันกำลังพยายามเข้าใจว่าฉันทำอะไรผิด

คำตอบ

4 DaveCross Aug 19 2020 at 23:52

ลองดูรหัสของคุณ:

while (my $line = <$fh>) {
    if ( $line =~ m/^($charname):.*/i ) {
        $line =~ s/($charname): //i;
        print $line;                                                
    }
} 

ในบรรทัดแรกของคุณ:

while (my $line = <$fh>) {

คุณอ่านบรรทัดจากเข้า$fh $lineไม่เป็นไร. จากนั้นเรามองหาชื่อตัวละครของคุณ:

if ( $line =~ m/^($charname):.*/i ) {

คุณมองหาชื่ออักขระที่จุดเริ่มต้นของสตริง (นั่นคือสิ่งที่^ทำ) ตามด้วยเครื่องหมายจุดคู่และอักขระอื่น ๆ นั่น.*ไม่มีจุดหมายเนื่องจากไม่ได้เปลี่ยนสิ่งที่ regex จับคู่เลย

แต่วงเล็บที่คุณใส่ไว้$charnameทำสิ่งที่น่าสนใจ พวกเขาจับบิตของสตริงที่ตรงกับที่เป็นส่วนหนึ่งของ regex $1และเก็บไว้ใน ตอนนี้สิ้นเปลืองไปหน่อยพูดตามตรง ในฐานะที่เป็นสตริงคงคุณรู้อยู่แล้วว่าสิ่งที่จะสิ้นสุดใน$charname $1มันจะเป็น "ไบรอัน" หรือตัวละครใดก็ตามที่คุณกำลังมองหา

$line =~ s/($charname): //i; print $line;

จากนั้นแก้ไข$lineเพื่อลบชื่ออักขระและเครื่องหมายทวิภาค (และช่องว่าง) ออกจากจุดเริ่มต้นของบรรทัด ดังนั้นคุณจะได้รับสายที่พูด และคุณพิมพ์สิ่งนั้น

จนถึงตอนนี้ดีมาก รหัสของคุณค่อนข้างสิ้นเปลืองในหลาย ๆ ที่ แต่มันก็ทำอย่างที่คุณคิด

จากนั้นคุณเปลี่ยนบรรทัด:

print $line;

ถึง:

print $1;

และคุณสับสน :-)

แต่ที่เราได้เห็นแล้ววงเล็บจับจะเก็บ "ไบรอัน" $1ใน ดังนั้นหากคุณพิมพ์$1คุณจะเห็น "ไบรอัน"

คุณถาม,

เหตุใดตัวแปรการจับจึงแสดงเฉพาะคำแรก "BRIAN" ไม่ใช่ทั้งบรรทัด

และคำตอบก็คือเพราะนั่นคือสิ่งที่คุณขอให้ทำ $1จะมีสิ่งที่อยู่ในวงเล็บยึด ซึ่งก็คือ$charname. ซึ่งก็คือ "ไบรอัน". ส่วนที่เหลือของการจับคู่ regex อยู่นอกวงเล็บดังนั้นจึงไม่ได้ลงเอย$1ด้วย

มันสมเหตุสมผลไหม

4 mivk Aug 19 2020 at 23:34

$1เป็นกลุ่มการจับภาพแรกของคุณ: ส่วนที่ตรงกับคู่แรกของวงเล็บใน regex ของคุณ

หากคุณมี regex ที่มีวงเล็บ 2 ชุด$2จะเป็นสิ่งที่ตรงกับส่วนที่สอง

นี่คือทางเลือกอื่นสำหรับส่วนนั้นของสคริปต์ของคุณ:

my $match = qr/^($charname):\s*(.*)/i;

while (my $line = <$fh>) {
    if ( $line =~ m/$match/ ) {
        print "Character : $1\n", "text : $2\n";                                                
    }
}   

และเพื่อความสนุกสนานนี่คือเวอร์ชันย่อของสคริปต์ฉบับเต็มของคุณพร้อมความคิดเห็นเกี่ยวกับส่วน regex:

#!/usr/bin/env perl

use strict;
use warnings;

my $filename = "/tmp/y"; my $charname = $ARGV[0]; open(my $fh, '<', $filename) or die "Cannot find $filename\n";

my $match = qr/^\s* ($charname) \s*:\s* (.*)/ix;
#               |   |              |     |   | \ extended regex which allows spaces for readability
#               |   |              |     |   \ case insensitive
#               |   |              |     \ capture the rest of the line into $2 # | | \ colon, optionally with spaces before and/or after # | \ capture the name into $1
#               \ also accept spaces before the name


while ( <$fh> ) { # use the default $_ variable instead of unneeded $line print "$2\n" if ( /$match/ ); } print "\n"; close $fh;
PolarBear Aug 20 2020 at 00:18

โปรดตรวจสอบสคริปต์ perl ต่อไปนี้ว่าสามารถบรรลุผลลัพธ์ที่ต้องการได้อย่างไร

บันทึก:

  • ป้อนข้อมูลทดสอบที่เก็บไว้ใน__DATA__บล็อก
  • สำหรับการอ่านจากไฟล์แทนที่<DATA>ด้วย<>และเรียกใช้เป็นmovie_script.pl BRIAN movie_script.txt.
use strict;
use warnings;
use feature 'say';

my $charname = shift or die 'Specify character'; say $charname;
/^$charname: (.*)\Z/ && say $1 for <DATA>;

__DATA__
BRIAN: Hello, mother.
MANDY: Don't you 'hello mother' me. What are all those people doing out ther    e?!
BRIAN: Oh. Well-- well, I, uh--
MANDY: Come on! What have you been up to, my lad?!
BRIAN: Well, uh, I think they must have popped by for something.
MANDY: 'Popped by'?! 'Swarmed by', more like! There's a multitude out there!
BRIAN: Mm, they-- they started following me yesterday.
MANDY: Well, they can stop following you right now. Now, stop following my son! You ought to be ashamed of yourselves.
FOLLOWERS: The Messiah! The Messiah! Show us the Messiah!
MANDY: The who?
FOLLOWERS: The Messiah!
MANDY: Huh, there's no Messiah in here. There's a mess, all right, but no Me    ssiah. Now, go away!
FOLLOWERS: The Messiah! The Messiah!
MANDY: Ooooh.
FOLLOWERS: Show us the Messiah! The Messiah! The Messiah! Show us the Messiah!
MANDY: Now, you listen here! He's not the Messiah. He's a very naughty boy! Now, go away!

ตัวอย่างผลลัพธ์ movie_script.pl BRIAN

BRIAN
Hello, mother.
Oh. Well-- well, I, uh--
Well, uh, I think they must have popped by for something.
Mm, they-- they started following me yesterday.

ตัวอย่างผลลัพธ์ movie_script.pl FOLLOWERS

FOLLOWERS
The Messiah! The Messiah! Show us the Messiah!
The Messiah!
The Messiah! The Messiah!
Show us the Messiah! The Messiah! The Messiah! Show us the Messiah!