¿Por qué esta coincidencia de Regex solo muestra la primera palabra en la variable de captura, no la línea completa?
Soy bastante nuevo en Perl y Regexes, así que tenga paciencia conmigo si uso incorrectamente la terminología.
Estoy intentando leer un archivo de texto que contiene un guión de película y usar una expresión regular para mostrar todas las líneas pronunciadas por un personaje en particular. Aquí está el extracto que estoy usando:
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!
Y aquí está el código:
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;
El código funciona bien, y cuando ejecuto el programa, pasando "Brian" como un argumento de línea de comandos, solo me muestra las líneas de Brian, lo mismo si entro "Mandy" o "Seguidores" (todos sin distinción entre mayúsculas y minúsculas).
Estoy tratando de entender cómo funcionan las variables de captura para poder manipular el archivo de texto de manera más sensible. Cuando cambio la línea 21 a en print $1
lugar de print $line
, esperaba que el resultado fuera el mismo, porque la expresión regular que he proporcionado debería coincidir con cualquier instancia de "BRIAN", seguida de dos puntos, luego cualquier número de caracteres hasta el final de la línea.
Sin embargo, cuando hago esto, simplemente regresa:
BRIANBRIANBRIANBRIAN
... En lugar de las cuatro líneas que pertenecen a Brian. Así que intenté intercambiar las líneas 22 y 21, poniendo la print $1
declaración antes de la sustitución de expresiones regulares, pero eso arroja el mismo resultado.
¿Por qué la variable de captura solo muestra la primera palabra "BRIAN" y no la línea completa? Estoy seguro de que es un error muy simple, pero me cuesta entender qué estoy haciendo mal.
Respuestas
Veamos tu código:
while (my $line = <$fh>) {
if ( $line =~ m/^($charname):.*/i ) {
$line =~ s/($charname): //i;
print $line;
}
}
En tu primera línea:
while (my $line = <$fh>) {
Lees una línea de $fh
en $line
. Esta bien. Luego buscamos el nombre de tu personaje:
if ( $line =~ m/^($charname):.*/i ) {
Busca el nombre del personaje al comienzo de la cadena (eso es lo que ^
hace), seguido de dos puntos y luego cualquier otro carácter. Eso no .*
tiene sentido, ya que no cambia en absoluto lo que coincide con la expresión regular.
Pero los paréntesis que pones $charname
hacen algo interesante. Capturan la parte de la cadena que coincide con esa parte de la expresión regular y la almacenan $1
. Ahora, eso es un poco desperdicio, para ser honesto. Como $charname
es una cadena fija, ya sabes en qué terminará $1
. Será "BRIAN" o el personaje que estés buscando.
$line =~ s/($charname): //i; print $line;
Luego edita $line
para eliminar el nombre del personaje y los dos puntos (y un espacio) desde el principio de la línea. Así que obtienes la línea que se habla. Y lo imprime.
Hasta aquí todo bien. Su código es un poco derrochador en algunos lugares, pero hace lo que piensa.
Luego cambia la línea:
print $line;
A:
print $1;
Y te confundes :-)
Pero como ya hemos visto, los paréntesis de captura almacenarán "BRIAN" en formato $1
. Entonces, si imprime $1
, verá "BRIAN".
Usted pregunta,
¿Por qué la variable de captura solo muestra la primera palabra "BRIAN" y no la línea completa?
Y la respuesta es, porque eso es lo que le ha pedido que haga. $1
contendrá lo que está dentro de los paréntesis de captura. Que es $charname
. Que es "BRIAN". El resto de la coincidencia de expresiones regulares está fuera del paréntesis, por lo que no termina en $1
.
¿Tiene sentido?
$1
es su primer grupo de captura : la parte que coincide con el primer par de paréntesis en su expresión regular.
Si tuvieras una expresión regular con 2 pares de paréntesis, $2
sería lo que coincidiera con la segunda parte.
Aquí hay una alternativa a esa parte de su script:
my $match = qr/^($charname):\s*(.*)/i;
while (my $line = <$fh>) {
if ( $line =~ m/$match/ ) {
print "Character : $1\n", "text : $2\n";
}
}
Y solo por diversión, aquí hay una versión abreviada de su script completo, con comentarios sobre las partes de expresiones regulares:
#!/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;
Investigue el siguiente script de Perl cómo se puede lograr el resultado deseado.
NOTA:
- Ingrese los datos de prueba almacenados en el
__DATA__
bloque - Para leer desde un archivo, reemplace
<DATA>
con<>
y ejecute comomovie_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!
Muestra de salida 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.
Muestra de salida 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!