この正規表現の一致で、行全体ではなく、キャプチャ変数の最初の単語のみが表示されるのはなぜですか?

Aug 20 2020

私は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」(すべて大文字と小文字を区別しない)を入力した場合と同じように、Brianの行のみが表示されます。

テキストファイルをより敏感に操作できるように、キャプチャ変数がどのように機能するかを理解しようとしています。21行目をprint $1ではなくに変更するとprint $line、結果は同じになると予想されます。これは、指定した正規表現が「BRIAN」の任意のインスタンス、コロン、その後の任意の数の文字に一致する必要があるためです。この線。

ただし、これを行うと、次のように返されます。

BRIANBRIANBRIANBRIAN

...ブライアンに属する4行の代わりに。そこで、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。それは「BRIAN」またはあなたが探しているキャラクターになります。

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

次に、編集$lineして、行の先頭から文字名とコロン(およびスペース)を削除します。だからあなたはただ話されている線を得るだけです。そして、あなたはそれを印刷します。

ここまでは順調ですね。あなたのコードは場所によっては少し無駄ですが、あなたが思うことをします。

次に、行を変更します。

print $line;

に:

print $1;

そして、あなたは混乱します:-)

ただし、すでに見てきたように、キャプチャ括弧は「BRIAN」をに格納し$1ます。したがって、印刷する$1と「BRIAN」と表示されます。

あなたが尋ねる、

キャプチャ変数が最初の単語「BRIAN」のみを表示し、行全体を表示しないのはなぜですか?

そして答えは、それがあなたがそれをするように頼んだことだからです。$1キャプチャ括弧内にあるものが含まれます。これは$charnameです。それが「BRIAN」です。正規表現の残りの一致は括弧の外にあるため、で終わることはありません$1

それは理にかなっていますか?

4 mivk Aug 19 2020 at 23:34

$1は最初のキャプチャグループです。正規表現の最初の括弧のペアに一致する部分です。

2セットの括弧付きの正規表現がある場合$2、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!