Pourquoi cette correspondance Regex n'affiche-t-elle que le premier mot de la variable de capture, pas la ligne entière?

Aug 20 2020

Je suis assez nouveau dans Perl et Regexes, alors soyez patient avec moi si j'utilise mal la terminologie.

J'essaie de lire un fichier texte contenant un script de film et d'utiliser un Regex pour afficher toutes les lignes prononcées par un caractère particulier. Voici l'extrait que j'utilise:

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!

Et voici le code:

  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;

Le code fonctionne bien, et lorsque j'exécute le programme, en passant "Brian" comme argument de ligne de commande, il ne me montre que les lignes de Brian, idem si j'entre "Mandy" ou "Followers" (tous insensibles à la casse).

J'essaie de comprendre le fonctionnement des variables de capture afin de pouvoir manipuler le fichier texte de manière plus sensible. Quand je change la ligne 21 à la print $1place de print $line, je m'attendais à ce que le résultat soit le même, car l'expression régulière que j'ai fournie devrait correspondre à n'importe quelle instance de "BRIAN", suivie d'un deux-points, puis de n'importe quel nombre de caractères jusqu'à la fin de la ligne.

Lorsque je fais cela, cependant, il renvoie simplement:

BRIANBRIANBRIANBRIAN

... Au lieu des quatre lignes appartenant à Brian. J'ai donc essayé de permuter les lignes 22 et 21, en mettant l' print $1instruction avant la substitution regex, mais cela renvoie le même résultat.

Pourquoi la variable de capture n'affiche-t-elle que le premier mot "BRIAN" et non la ligne entière? Je suis sûr que c'est une erreur très simple, mais j'ai du mal à comprendre ce que je fais de mal.

Réponses

4 DaveCross Aug 19 2020 at 23:52

Regardons votre code:

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

Dans votre première ligne:

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

Vous avez lu une ligne de $fhdedans $line. C'est très bien. Ensuite, nous recherchons le nom de votre personnage:

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

Vous recherchez le nom du caractère au début de la chaîne (c'est ce que ^fait), suivi d'un deux-points puis de tous les autres caractères. C'est .*inutile car cela ne change pas du tout ce que correspond à l'expression régulière.

Mais les parenthèses que vous mettez autour $charnamefont quelque chose d'intéressant. Ils capturent le bit de la chaîne qui correspond à cette partie de l'expression régulière et le stockent $1. Maintenant, c'est un peu du gaspillage, pour être honnête. Comme $charnamec'est une chaîne fixe, vous savez déjà ce qui va se terminer $1. Ce sera "BRIAN" ou n'importe quel personnage que vous recherchez.

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

Vous modifiez ensuite $linepour supprimer le nom du caractère et les deux points (et un espace) du début de la ligne. Donc, vous obtenez juste la ligne qui est prononcée. Et vous imprimez cela.

Jusqu'ici tout va bien. Votre code est un peu inutile par endroits, mais il fait ce que vous pensez.

Ensuite, vous modifiez la ligne:

print $line;

À:

print $1;

Et vous êtes confus :-)

Mais comme nous l'avons déjà vu, les parenthèses de capture stockeront "BRIAN" dedans $1. Donc, si vous imprimez $1, vous verrez "BRIAN".

Tu demandes,

Pourquoi la variable de capture n'affiche-t-elle que le premier mot "BRIAN" et non la ligne entière?

Et la réponse est que c'est ce que vous lui avez demandé de faire. $1contiendra ce qui est à l'intérieur des parenthèses de capture. Ce qui est $charname. Qui est "BRIAN". Le reste de la correspondance regex est en dehors des parenthèses, donc il ne se termine pas $1.

Cela a-t-il du sens?

4 mivk Aug 19 2020 at 23:34

$1est votre premier groupe de capture : la partie qui correspond à la première paire de parenthèses dans votre regex.

Si vous aviez une regex avec 2 jeux de parenthèses, ce $2serait ce qui correspondrait à la deuxième partie.

Voici une alternative à cette partie de votre script:

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

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

Et juste pour le plaisir, voici une version abrégée de votre script complet, avec des commentaires sur les parties 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

Veuillez examiner le script perl suivant comment obtenir la sortie souhaitée.

REMARQUE:

  • Données de test d'entrée stockées dans le __DATA__bloc
  • Pour lire à partir d'un fichier, remplacez <DATA>par <>et exécutez en tant que 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!

Exemple de sortie 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.

Exemple de sortie 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!