Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical

Pattern matching in perl

by noviceuser (Acolyte)
on Jun 30, 2022 at 15:12 UTC ( #11145202=perlquestion: print w/replies, xml ) Need Help??

noviceuser has asked for the wisdom of the Perl Monks concerning the following question:

i am writing a code where i have to find version associated with the name from a file. i am trying below code but the pattern match is not working. the file /home/test.txt contains multiple entries like below:

Anls/01.00/windows abc/02.00/windows core/03.00/windows route/04.00/windows . . .
my $file = "/home/test.txt"; if (-e $file) { my @list = ("Anls", "core", "route"); open(FH, '<', $file) or die $!; foreach my $x (@list) { while(<FH>){ my $pattern; if (defined($pattern) && ($pattern =~ /$x\/(.*)\/(.* +)/)) { my $version = $1; print "$x: $version\n"; } } } close(FH); }

Replies are listed 'Best First'.
Re: Pattern matching in perl
by hv (Parson) on Jun 30, 2022 at 15:31 UTC

    Hi, the first problem is that your two loops are the wrong way round: you want to read each line once (in the outer loop), than examine that line against each of the possible patterns (in the inner loop).

    The second problem is that you are reading the lines from the file into the default variable ($_), but then processing as if it had been read into $pattern. ("Pattern" is also an unexpected name for it - in usual terminology, the pattern would be the regular expression, and we'd talk about "matching a string against a pattern".)

    I'd also recommend using a different delimiter for the pattern such as {...} so that you don't need to escape the literal '/' characters in the pattern, and using a variable like $fh for the filehandle in preference to a bareword "FH".

    So I'd suggest something like this:

    use strict; # always use warnings; # always my $file = "/home/test.txt"; if (-e $file) { my @list = ("Anls", "core", "route"); open(my $fh, '<', $file) or die "$file: $!"; while (my $line = <$fh>) { foreach my $x (@list) { if ($line =~ m{$x/(.*)/(.*)}) { my $version = $1; print "$x: $version\n"; } } } close $fh; }

    Update: just after posting, it occurs to me that it is quite likely that you intend a line like "score/01.00/windows" not to match your "core" pattern. If I'm correct, then the pattern should additionally be anchored to the start of the string:

          if ($line =~ m{^$x/(.*)/(.*)}) {
      If speed is a concern, creating one regex to match all the possible names instead of looping over them is much faster:
      #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; my @list = ('Anls', 'core', 'route'); my $pattern = join '|', @list; $pattern = qr{^($pattern)/(.*)}; my $file = '/home/test.txt'; open my $fh, '<', $file or die "$file: $!"; while (my $line = <$fh>) { if (my ($name, $version) = $line =~ $pattern) { say "$name: $version"; } }
      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Pattern matching in perl [short-circuit and declared vs. defined]
by kcott (Archbishop) on Jun 30, 2022 at 18:30 UTC

    G'day noviceuser,

    In addition to issues already pointed out, you have a problem with short-circuited logic. Search perlop for "short-circuit": you'll find 9 matches which explain what it is and where it does (or does not) apply.

    Also, it may just be a typo; however, as a noviceuser, it occurs to me that you may not be fully across the difference between a variable being declared and defined.

    In your code you declare $pattern with 'my $pattern;'. At this point, $pattern has not been assigned a value; i.e. it is undefined.

    In the next statement you have an 'if' whose condition is '(defined($pattern) && ...)'. Because 'defined($pattern)' is FALSE, Perl does not bother evaluating the remainder of the condition: it already knows the entire condition is FALSE. This is an example of short-circuiting.

    "... but the pattern match is not working."

    It's not a case of it not working: it's never evaluated. Whether your regex is correct or not doesn't come into play.

    Consider these:

    $ perl -E 'my $pattern; say +(defined($pattern)) ? "YES" : "NO"' NO $ perl -E 'my $true = 1; say +(defined($true)) ? "YES" : "NO"' YES $ perl -E 'my $true = 1; my $pattern; say +(defined($pattern) && $true +) ? "YES" : "NO"' NO $ perl -E 'my $true = 1; my $pattern; say +(defined($pattern) || $true +) ? "YES" : "NO"' YES

    — Ken

Re: Pattern matching in perl
by Anonymous Monk on Jul 01, 2022 at 15:11 UTC

    Because this is Perl, There Is More Than One Way To Do It:

    use strict;
    use warnings;
    my $file = "/home/test.txt";
    # Store the list of desired names in a hash, with a true value for each.
    my %list = map { $_ => 1 } qw{ Anls core route };
    open my $fh, '<', $file or die "Failed to open $file: $!";
    while ( <$fh> ) {
      # Use split() to break $_ on slashes. Save only first two parts.
      my ( $x, $version ) = split /\//;
      # Print if hash entry {$x} contains a true value. Non-existing entries are false.
      print "$x: version $version\n" if $list{$x};
    close $fh;

      More portable to use File::Spec and splitpath rather than hardcoding a platform dependent separator . . .</nitpick>

      The cake is a lie.
      The cake is a lie.
      The cake is a lie.

        Good point. I assumed that in this case the '/' was the chosen field delimiter in the input file, but all the OP said was (paraphrased) "I have a file that looks like this ...". If the input file actually contains file names, yours is the way to go unless we know they are POSIX-format names.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11145202]
Approved by marto
Front-paged by Corion
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (5)
As of 2022-08-19 07:59 GMT
Find Nodes?
    Voting Booth?

    No recent polls found