Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Passing variables into regular expressions

by wanderer (Initiate)
on Apr 20, 2006 at 07:43 UTC ( #544539=perlquestion: print w/replies, xml ) Need Help??

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

Hi I'm new to Perl. The reason for the program is part of a procedure to ensure that the configured server IP addrss(obtained from ifconfig) is the same as that contained within the hosts file. The code has been checked by some more experienced Perl programmers and is shown below. All I want to extract is the first IP address for fred in the hosts file also shown below
#!/usr/bin/perl my $HN = `hostname`; my $hosts =`cat /etc/hosts`; if ( $hosts =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+(${$HN})\s/g ) { print "$1\n"; print "$2\n"; }
This program prints the IP address but not the host name ($2) as defined( I thought) by the second set of parentheses (${$HN})-- Without the ${$HN} NO match is made at all. I know I already have the hostname ($HN) but dont understand why $2 is not set to $HN The format of the hosts file(or at least the entries I am interested in) is as follows
x.y.z.22 fred<space or tab>localhost x.y.z.23 fred-test-0 x.y.z.24 fred-test-1
Can you explain a) why $2 is not populated and b) why just supplying ($HN) instead of (${$HN}) does not work

Replies are listed 'Best First'.
Re: Passing variables into regular expressions
by Tanalis (Curate) on Apr 20, 2006 at 08:23 UTC
    Hi,

    There's a couple of issues with your code that you should be aware of.

    Firstly, when you slurp a file into a variable like that, the newline characters (\n) remain within the variable (i.e., you get exactly what was in the file).

    To have your regular expression take that into account, you need to add a m alongside the g on the right-hand-side of the expression, which has Perl's regular expression engine match over multiple lines, rather than stopping when it hits the newline.

    You can simplify the regular expression too to make your life a little easier. The following (working, but only partially tested) code snippet seems to do the job for me:

    #! /usr/bin/perl use strict; use warnings; my $hn = 'localhost.localdomain'; my $hosts = `cat /etc/hosts`; if ( $hosts =~ /^([\d\.]+)\s+($hn)/mg ) { print "true: $1 $2\n"; } else { print "false!\n"; }
    Having said that, there's almost certainly a better way to parse this file .. but I'm not caffienated enough to think of it at the minute.

    Hope that helps!

      I could be misunderstanding something but I thought you had to use the s flag to get the regular expression to match across newlines. The following script

      use strict; use warnings; my $str = "ab12c\nde34f\ngh56i\njk78l"; my @digits = $str =~ /(\d+)/g; print "\@digits -- @digits\n\n"; my ($span_d) = $str =~ /(\d\d.*?\d\d)/; print "\$span_d -- $span_d\n\n"; my ($span_m) = $str =~ /(\d\d.*?\d\d)/m; print "\$span_m -- $span_m\n\n"; my ($span_s) = $str =~ /(\d\d.*?\d\d)/s; print "\$span_s -- $span_s\n\n";

      produces

      @digits -- 12 34 56 78 Use of uninitialized value in concatenation (.) or string at reSorM li +ne 12. $span_d -- Use of uninitialized value in concatenation (.) or string at reSorM li +ne 15. $span_m -- $span_s -- 12c de34

      The m flag doesn't seem to so the trick. Have I missed something?

      Cheers,

      JohnGG

        Interesting.

        The docs indicate that the s flag causes the input string to be treated as if it's a single line. The m flag, on the other hand, causes the string to be treated as if it's multi-line.

        It seems that .*? can't cross the newline character when using the m flag:

        # doesn't match my ($span_m) = $str =~ /(\d\d.*?\d\d)/m; # matches my ($span_m) = $str =~ /(\d\d.*?\n.*?\d\d)/m; # so does my ($span_m) = $str =~ /(\d\d\w+\s\w+\d\d/m;
        Treating the \n as whitespace (or explicitly naming it in the regex) seems to solve the problem. Any ideas why that'd be the case?
Re: Passing variables into regular expressions
by GrandFather (Saint) on Apr 20, 2006 at 08:16 UTC

    Your code doesn't make a great deal of sense to me. In particular I can't see why you are interpolating $HN into the regex. However the following sample does what you ask and should serve as a starting point for you to ask what you really want to know:

    use strict; use warnings; while (<DATA>) { next if ! /^((?:\d{1,3}\.){3}\d{1,3})\s+(\S*)/; print "$1\n"; print "$2\n"; } __DATA__ 0.0.0.22 fred localhost 0.0.63.23 fred-test-0 1.0.128.24 fred-test-1

    Prints:

    0.0.0.22 fred 0.0.63.23 fred-test-0 1.0.128.24 fred-test-1

    Note the use of \d{1,3} to match 1 to 3 digits and \s and \S to match white space and anything else respectively.


    DWIM is Perl's answer to Gödel
      Thanks for your quick response. Just to clarify the requirement I only want to get the IP address on the line containing 'fred' not 'fred-test..' In addition this script will be run on over 300 servers so that is why I am passing the $HN into the regexp

        So something like:

        use strict; use warnings; my $HN = 'fred'; while (<DATA>) { next if ! /^((?:\d{1,3}\.){3}\d{1,3})\s+(\S*)/; next if $HN ne $2; print "$1\n"; print "$2\n"; } __DATA__ 0.0.0.22 fred localhost 0.0.63.23 fred-test-0 1.0.128.24 fred-test-1

        Prints:

        0.0.0.22 fred

        is what you are after? You could do the test in one line or you could interpolate the variable into the regex, but has the advantage of being pretty clean and clear I think.


        DWIM is Perl's answer to Gödel
Re: Passing variables into regular expressions
by japhy (Canon) on Apr 20, 2006 at 11:39 UTC
Re: Passing variables into regular expressions
by gloryhack (Deacon) on Apr 20, 2006 at 11:23 UTC
    Liberating GrandFather's example and twiddling it to use hostname and /etc/hosts:
    #!/usr/bin/perl use strict; use warnings; chomp(my $HN = `hostname`); if (open(HOSTS, "</etc/hosts")) { while (<HOSTS>) { next if ! /^((?:\d{1,3}\.){3}\d{1,3})\s+($HN)\b/; print "$2: $1\n"; last; } close HOSTS; } else { die "Could not open /etc/hosts for reading: $!\n"; }

    ... might save a few minutes of head scratching for someone.

      Thanks for the changes to the code- this is exactly what I was looking for. Only problem I had was with understanding the meaning of the '?:' at the beginning of the regexp. The tutorial was not too helpful either. However a better understanding was gained from 'Perl Programming'{O'Reilly]

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://544539]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (4)
As of 2021-01-24 18:43 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?