이 정규식 일치가 전체 줄이 아닌 캡처 변수의 첫 번째 단어 만 표시하는 이유는 무엇입니까?

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"(모두 대소 문자를 구분하지 않음)를 입력하면 마찬가지입니다.

캡처 변수의 작동 방식을 이해하여 텍스트 파일을보다 민감하게 조작 할 수 있도록 노력하고 있습니다. print $1대신 21 행을로 변경 print $line하면 결과가 동일 할 것으로 예상했을 것입니다. 입력 한 정규식이 "BRIAN"의 모든 인스턴스와 일치해야하고 콜론이 뒤 따르고 끝까지의 문자 수와 일치해야하기 때문입니다. 라인.

그러나 이렇게하면 다음과 같이 반환됩니다.

BRIANBRIANBRIANBRIAN

... 브라이언의 네 줄 대신. 그래서 22 번과 21 번 줄을 print $1바꿔서 정규식 대체 앞에 문을 두었지만 동일한 결과를 반환합니다.

캡처 변수가 전체 줄이 아닌 첫 번째 단어 "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 ) {

문자열의 시작 부분에서 문자 이름을 ^찾은 다음 (그렇게 합니다) 콜론과 다른 문자를 차례로 찾습니다 . 즉 .*그것이 무엇 정규식 일치 전혀 변경하지 않는 한 무의미하다.

그러나 당신이 둘러싼 괄호는 $charname흥미로운 일을합니다. 정규식의 해당 부분과 일치하는 문자열 비트를 캡처하여 $1. 솔직히 말하면 약간 낭비입니다. $charname고정 된 문자열과 마찬가지로 $1. "브라이언"또는 원하는 캐릭터입니다.

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

그런 다음 $line줄 시작 부분에서 문자 이름과 콜론 (및 공백)을 제거하도록 편집 합니다. 그래서 당신은 말한 대사를 얻습니다. 그리고 그것을 인쇄합니다.

여태까지는 그런대로 잘됐다. 당신의 코드는 장소에서 약간 낭비이지만 당신이 생각하는 바를 수행합니다.

그런 다음 줄을 변경합니다.

print $line;

에:

print $1;

그리고 당신은 혼란스러워합니다 :-)

그러나 이미 살펴본 것처럼 캡처하는 괄호는 "BRIAN"을 $1. $1따라서을 인쇄 하면 "BRIAN"이 표시됩니다.

물어,

캡처 변수가 전체 줄이 아닌 첫 번째 단어 "BRIAN"만 표시하는 이유는 무엇입니까?

대답은 당신이 요청한 것이기 때문입니다. $1캡처하는 괄호 안에있는 내용이 포함됩니다. 어느입니다 $charname. "브라이언"입니다. 나머지 정규식 일치는 괄호 밖에 있으므로 $1.

말이 돼?

4 mivk Aug 19 2020 at 23:34

$1첫 번째 캡처 그룹 은 정규식에서 첫 번째 괄호 쌍과 일치하는 부분입니다.

두 세트의 괄호가있는 정규식이 있다면 $2두 번째 부분과 일치하는 것입니다.

다음은 스크립트의 해당 부분에 대한 대안입니다.

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

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

그리고 재미를 위해 정규식 부분에 대한 주석과 함께 전체 스크립트의 축약 버전이 있습니다.

#!/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!