Perché questa corrispondenza Regex mostra solo la prima parola nella variabile di acquisizione, non l'intera riga?

Aug 20 2020

Sono abbastanza nuovo in Perl e Regexes, quindi per favore sii paziente con me se uso impropriamente la terminologia.

Sto tentando di leggere un file di testo contenente una sceneggiatura di un film e utilizzare un Regex per visualizzare tutte le linee pronunciate da un particolare personaggio. Ecco l'estratto che sto 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!

Ed ecco il codice:

  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;

Il codice funziona bene e quando eseguo il programma, passando "Brian" come argomento della riga di comando, mi mostra solo le righe di Brian, idem se inserisco "Mandy" o "Followers" (tutte senza distinzione tra maiuscole e minuscole).

Sto cercando di capire come funzionano le variabili di cattura in modo da poter manipolare in modo più sensibile il file di testo. Quando cambio la riga 21 in print $1invece di print $line, mi sarei aspettato che il risultato fosse lo stesso, perché la regex che ho fornito dovrebbe corrispondere a qualsiasi istanza di "BRIAN", seguita da due punti, quindi qualsiasi numero di caratteri fino alla fine di la linea.

Quando lo faccio, tuttavia, restituisce semplicemente:

BRIANBRIANBRIANBRIAN

... Invece delle quattro linee che appartengono a Brian. Quindi ho provato a scambiare le righe 22 e 21, mettendo l' print $1istruzione prima della sostituzione dell'espressione regolare, ma ciò restituisce lo stesso risultato.

Perché la variabile di cattura mostra solo la prima parola "BRIAN" e non l'intera riga? Sono sicuro che sia un errore molto semplice, ma faccio fatica a capire cosa sto sbagliando.

Risposte

4 DaveCross Aug 19 2020 at 23:52

Diamo un'occhiata al tuo codice:

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

Nella tua prima riga:

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

Hai letto una riga da $fhin $line. Va bene. Quindi cerchiamo il nome del tuo personaggio:

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

Cerchi il nome del carattere all'inizio della stringa (è quello che ^fa), seguito da due punti e poi da qualsiasi altro carattere. Questo .*è inutile in quanto non cambia affatto ciò che la regex corrisponde.

Ma le parentesi che metti in giro $charnamefanno qualcosa di interessante. Catturano il bit della stringa che corrisponde a quella parte della regex e lo memorizzano $1. Ora, è un po 'uno spreco, ad essere onesti. Poiché $charnameè una stringa fissa, sai già in cosa finirà $1. Sarà "BRIAN" o qualunque personaggio tu stia cercando.

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

Quindi modificare $lineper rimuovere il nome del carattere e i due punti (e uno spazio) dall'inizio della riga. Quindi ottieni solo la linea che viene pronunciata. E lo stampi tu.

Fin qui tutto bene. Il tuo codice è un po 'dispendioso in alcuni punti, ma fa quello che pensi.

Quindi cambi la linea:

print $line;

Per:

print $1;

E ti confondi :-)

Ma come abbiamo già visto, le parentesi catturate memorizzeranno "BRIAN" in $1. Quindi se stampi $1, vedrai "BRIAN".

Tu chiedi,

Perché la variabile di cattura mostra solo la prima parola "BRIAN" e non l'intera riga?

E la risposta è, perché è quello che gli hai chiesto di fare. $1conterrà ciò che è all'interno delle parentesi di cattura. Che è $charname. Che è "BRIAN". Il resto della corrispondenza regex è al di fuori delle parentesi, quindi non finisce in $1.

Ha senso?

4 mivk Aug 19 2020 at 23:34

$1è il tuo primo gruppo di cattura : la parte che corrisponde alla prima coppia di parentesi nella tua regex.

Se avessi una regex con 2 serie di parentesi, $2sarebbe ciò che corrisponde alla seconda parte.

Ecco un'alternativa a quella parte del tuo script:

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

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

E solo per divertimento, ecco una versione abbreviata del tuo script completo, con commenti sulle parti 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

Si prega di esaminare il seguente script perl su come ottenere l'output desiderato.

NOTA:

  • Immettere i dati di test memorizzati nel __DATA__blocco
  • Per leggere da un file sostituire <DATA>con <>ed eseguire come 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!

Esempio di output 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.

Esempio di output 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!