Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I restrict grep (or map) to first match when I'm expecting only one match?

Tags:

grep

perl

I am literally brand new to Perl. I have the following...

#!/usr/bin/perl

#config file
$config = "$ENV{'HOME'}/.config/.wallpaperrc";

open my $handle, '<', $config;
chomp(my @lines = <$handle>);
close $handle;

@regular = map(/^regular\.roll (.*)$/, @lines);

print(@regular);

It works but it seems awkward and improper to be using an array when I am only expecting one match and only want one match. If I make @regular scalar then the function returns the number of matches instead.

I tried to search for the answer but the results are muddied with all the questions using Perl grep within Bash.

like image 490
deanresin Avatar asked Sep 04 '19 00:09

deanresin


3 Answers

You can capture a single match by assigning to a scalar in list context

($regular) = map(/^regular\.roll (.*)$/, @lines);

The parentheses on the left hand side are important, otherwise you are imposing scalar context on the right hand size and the result will be something else, like the number of elements.

If you're trying to capture the first match from grep (but not map) and you are more comfortable using Perl modules, the first function in the List::Util package returns the first match, and is more efficient than calling grep and discarding all the extra matches.

use List::Util 'first';
...
$regular = first { /pattern/ } @input;
like image 176
mob Avatar answered Oct 23 '22 00:10

mob


You could assign the results of the operation to a list that contains just one element:

my ($regular) = map(/^regular\.roll (.*)$/, @lines);
print $regular;
like image 33
GMB Avatar answered Oct 23 '22 01:10

GMB


Note  See the end for how to stop right after the first match (one statement, with a module)


In order for the regex match operator to return the capture(s) themselves it indeed need be invoked in list context. But then you can form that list as you wish -- with just one scalar for instance, to catch only one from the returned list of scalars

my ($regular) = map { /^regular\.roll (.*)/ } @lines;

Here the ($v1, $v2,...) on the LHS provides the list context for the assignment operator, and with only one variable the first of the returned list of (.*) captures is assigned and the rest discarded.

This above has mostly been stated already but I think that it is important to comment on a few other things in the question as well.

  • Always have use warnings; and use strict; at the beginning of a program

  • An open statement must be tested for failure, and if it failed you print the error. Commonly

    open my $fh, '<', $file  or die "Can't open $file: $!";
    
  • I suggest to chomp in a separate statement

  • There is no reason for $ anchor in that regex (except with multiline string and /m modifier)

  • When printing, if you put it under quotes it's interpolated with spaces (see $,) in between

    say "@regular";
    

    or, print each element on its own line

    say for @regular;
    

    In order to be able to use say feature you need use feature qw(say);


Since only the first match is needed we'd rather not go through the rest of the list once a match is found. This can be achieved using first_result from List::MoreUtils (riffing off of mob's idea)

my $regular = firstres { my ($m) = /^regular\.roll (.*)/; $m } @lines;

The syntax inside the block is a little wordy but returning $1 after a lone regex didn't work for me (?). If having two statements is a bother this can be shortened, at the expense of readability

my $regular = firstres { ( /^regular\.roll (.*)/ )[0] } @lines;

where () around the regex provide for list context, and [0] takes the first element of that list. I added spaces around regex to try to alleviate that syntax a little; they aren't needed.

like image 3
zdim Avatar answered Oct 23 '22 01:10

zdim