Mengapa pencocokan Regex ini hanya menampilkan kata pertama dalam variabel tangkap, bukan seluruh baris?
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 $1
bukannya 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 $1
pernyataan 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
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 $fh
ke $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 $charname
melakukan sesuatu yang menarik. Mereka menangkap bit string yang cocok dengan bagian regex itu dan menyimpannya $1
. Nah, itu agak boros, jujur saja. Seperti $charname
string 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 $line
untuk 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. $1
akan 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?
$1
adalah 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, $2
akan 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;
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 sebagaimovie_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!