Mengapa pencocokan Regex ini hanya menampilkan kata pertama dalam variabel tangkap, bukan seluruh baris?

Aug 20 2020

Saya cukup baru mengenal Perl dan Regexes, jadi harap bersabar jika saya menyalahgunakan terminologi.

Saya mencoba membaca file teks yang berisi skrip film, dan menggunakan Regex untuk menampilkan semua baris yang diucapkan oleh karakter tertentu. Berikut kutipan yang saya gunakan:

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!

Dan inilah kodenya:

  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;

Kode berfungsi dengan baik, dan ketika saya menjalankan program, meneruskan "Brian" sebagai argumen baris perintah, itu hanya menampilkan baris Brian, ditto jika saya memasukkan "Mandy" atau "Pengikut" (semua case-insensitive.)

Saya mencoba memahami cara kerja variabel tangkap sehingga saya dapat memanipulasi file teks dengan lebih sensitif. Ketika saya mengubah baris 21 menjadi print $1bukannya print $line, saya akan mengharapkan hasilnya sama, karena regex yang saya berikan harus cocok dengan sembarang contoh "BRIAN", diikuti oleh titik dua, lalu sejumlah karakter sampai akhir garis.

Ketika saya melakukan ini, bagaimanapun, itu hanya mengembalikan:

BRIANBRIANBRIANBRIAN

... Dari pada empat baris milik Brian. Jadi saya mencoba menukar baris 22 dan 21, meletakkan print $1pernyataan sebelum substitusi regex, tetapi itu mengembalikan hasil yang sama.

Mengapa variabel tangkap hanya menampilkan kata pertama "BRIAN" dan bukan keseluruhan baris? Saya yakin ini kesalahan yang sangat sederhana, tetapi saya kesulitan memahami kesalahan yang saya lakukan.

Jawaban

4 DaveCross Aug 19 2020 at 23:52

Mari kita lihat kode Anda:

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

Di baris pertama Anda:

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

Anda membaca satu baris dari $fhke $line. Tidak apa-apa. Kemudian kami mencari nama karakter Anda:

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

Anda mencari nama karakter di awal string (begitulah ^), diikuti oleh titik dua dan kemudian karakter lainnya. Itu .*tidak ada gunanya karena tidak mengubah apa yang cocok dengan regex sama sekali.

Tetapi tanda kurung yang Anda letakkan di sekitar $charnamemelakukan sesuatu yang menarik. Mereka menangkap bit string yang cocok dengan bagian regex itu dan menyimpannya $1. Nah, itu agak boros, jujur ​​saja. Seperti $charnamestring tetap, Anda sudah tahu akan berakhir di apa $1. Ini akan menjadi "BRIAN" atau karakter apa pun yang Anda cari.

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

Anda kemudian mengedit $lineuntuk menghapus nama karakter dan titik dua (dan spasi) dari awal baris. Jadi Anda hanya mendapatkan baris yang diucapkan. Dan Anda mencetaknya.

Sejauh ini bagus. Kode Anda sedikit boros di beberapa tempat, tetapi sesuai dengan yang Anda pikirkan.

Kemudian Anda mengubah baris:

print $line;

Untuk:

print $1;

Dan Anda bingung :-)

Tapi seperti yang telah kita lihat, tanda kurung pengambilan akan menyimpan "BRIAN" $1. Jadi jika Anda mencetak $1, Anda akan melihat "BRIAN".

Anda bertanya,

Mengapa variabel tangkap hanya menampilkan kata pertama "BRIAN" dan bukan keseluruhan baris?

Dan jawabannya adalah, karena itulah yang Anda minta. $1akan berisi apa yang ada di dalam tanda kurung pengambilan. Yaitu $charname. Yaitu "BRIAN". Pencocokan regex lainnya berada di luar tanda kurung, jadi tidak berakhir di $1.

Apakah itu masuk akal?

4 mivk Aug 19 2020 at 23:34

$1adalah grup tangkapan pertama Anda : bagian yang cocok dengan pasangan tanda kurung pertama di ekspresi reguler Anda.

Jika Anda memiliki regex dengan 2 set tanda kurung, $2akan cocok dengan bagian kedua.

Berikut adalah alternatif untuk bagian itu dari skrip Anda:

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

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

Dan hanya untuk bersenang-senang, berikut ini adalah versi singkat dari skrip lengkap Anda, dengan komentar di bagian 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

Harap selidiki skrip perl berikut bagaimana keluaran yang diinginkan dapat dicapai.

CATATAN:

  • Masukkan data uji yang disimpan dalam __DATA__blok
  • Untuk membaca dari file, ganti <DATA>dengan <>dan jalankan sebagai 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!

Sampel keluaran 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.

Sampel keluaran 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!