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

#regex #perl

#регулярное выражение #perl

Вопрос:

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

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

 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» в качестве аргумента командной строки, она показывает мне только строки Брайана, то же самое, если я ввожу «Mandy» или «Followers» (все без учета регистра.)

Я пытаюсь понять, как работают переменные capture, чтобы я мог более деликатно манипулировать текстовым файлом. Когда я меняю строку 21 на print $1 вместо print $line , я ожидал бы, что результат будет таким же, потому что предоставленное мной регулярное выражение должно соответствовать любому экземпляру «BRIAN», за которым следует двоеточие, затем любое количество символов до конца строки.

Однако, когда я делаю это, оно просто возвращает:

 BRIANBRIANBRIANBRIAN
  

… Вместо четырех строк, принадлежащих Брайану. Итак, я попытался поменять местами строки 22 и 21, поместив print $1 оператор перед подстановкой регулярных выражений, но это возвращает тот же результат.

Почему переменная capture показывает только первое слово «BRIAN», а не всю строку? Я уверен, что это очень простая ошибка, но я изо всех сил пытаюсь понять, что я делаю не так.

Комментарии:

1. Лу для поиска книг по изучению perl в Google perl bookshelf .

Ответ №1:

Давайте посмотрим на ваш код:

 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;
  

И вы запутались 🙂

Но, как мы уже видели, в $1 скобках capture будет сохранено «BRIAN». Итак, если вы напечатаете $1 , вы увидите «BRIAN».

Вы спрашиваете,

Почему переменная capture показывает только первое слово «BRIAN», а не всю строку?

И ответ таков: потому что это то, что вы попросили это сделать. $1 будет содержать то, что находится внутри фиксирующих круглых скобок. Что $charname . Которое является «BRIAN». Остальная часть совпадения регулярных выражений находится за скобками, поэтому оно не заканчивается в $1 .

Имеет ли это смысл?

Комментарии:

1. Хотел бы я принять все три ответа — вам потребовалось время, чтобы терпеливо объяснить переменные capture, которые я, очевидно, неправильно понял, в то время как mivk потребовалось время, чтобы объяснить регулярные выражения. Спасибо за ваше отличное и проницательное объяснение!

Ответ №2:

$1 ваша первая группа захвата: часть, которая соответствует первой паре круглых скобок в вашем регулярном выражении.

Если бы у вас было регулярное выражение с двумя наборами круглых скобок, $2 было бы то, что соответствует второй части.

Вот альтернатива этой части вашего скрипта:

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

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

И просто для развлечения, здесь была бы сокращенная версия вашего полного скрипта с комментариями к частям регулярных выражений:

 #!/usr/bin/env perl

use strict;
use warnings;

my $filename = "/tmp/y";
my $charname = $ARGV[0];

open(my $fh, '<', $filename)
  or die "Cannot find $filenamen";

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 "$2n" if ( /$match/ );
}

print "n";
close $fh;
  

Комментарии:

1. Большое спасибо за это — я бы тоже хотел принять этот ответ. Ваше объяснение регулярного выражения очень полезно.

2. Но что qr/ делает то m/ , чего нет?

3. @Lou: qr/ является ли » Оператором, подобным регулярному выражению в кавычках «, используемым для определения регулярного выражения в переменной, которое может быть использовано позже внутри m/.../ (или /.../ . m Необязательно при использовании стандартного / синтаксиса регулярных выражений). Я использовал это здесь, потому что вы определили $match переменную в вашем исходном примере.

4. А, кажется, я понимаю. Если бы я поместил регулярное выражение в скаляр без qr/ , например $match = /^($charname):(. )$/i , а затем попытался выполнить print "x" if ( $match ) , я полагаю, это не сработало бы?

5. @Lou Да. Условие if не пыталось бы ничего сопоставлять и просто всегда возвращало бы true (поскольку «$match» не является ни 0, ни пустым).

Ответ №3:

Пожалуйста, изучите следующий сценарий 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/ amp;amp; 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!