Почему это соответствие Regex показывает только первое слово в переменной захвата, а не всю строку?

Aug 20 2020

Я новичок в Perl и Regexes, поэтому проявите ко мне терпение, если я неправильно использую терминологию.

Я пытаюсь прочитать текстовый файл, содержащий сценарий фильма, и использовать регулярное выражение для отображения всех строк, произнесенных определенным персонажем. Вот отрывок, который я использую:

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;

Код работает нормально, и когда я запускаю программу, передавая «Брайан» в качестве аргумента командной строки, она показывает мне только строки Брайана, то же самое, если я ввожу «Мэнди» или «Последователи» (все без учета регистра).

Я пытаюсь понять, как работают переменные захвата, чтобы более чувствительно управлять текстовым файлом. Когда я меняю строку 21 на print $1вместо 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», а не всю строку?

И ответ таков: потому что вы просили это сделать. $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!