Reference a variable within variable - hashes? - perl

I have been looking for a solution to my problem and Hashes seem to be the answer after reading several posts but I am unsure how to implement this for my requirement, can anyone suggest how or even a better option?
In my code the variable $host is being set from values in a database. I loop through these values changing the value of $host each time.
I want to discard some types of host names, and to determine which hosts to discard I read in a user-configurable file which holds the Perl regex for that exclude. i.e. the config file has a line
EXCLUDE=\d+DAT\d+,\d+INF\d+
I then want to build up the Perl regexp match (logical OR), i.e.
if ( $host =~ m/\d+DAT\d+/ || $host =~ m/\d+INF\d+/ ) {
# do something
}
At the moment my code is hard wired as in the above example, but how can I dynamically construct the Perl regex after reading in the config file?
I have read the config file into an array and will start from there. The code above needs to end up like this:
if ($exclude clause) {
# do something
}
This is how I set about achieving that reading from the array:
for ($i = 1; $i < #conf; $i++) {
$exclude_clause .= "$host =~/" . #conf[$i] . "/ || ";
}
$exclude_clause =~ s/ \|\| $//;
The problem is referencing $host within the $exclude_clause. My regex string is built OK apart from the $host.

I would suggest a different approach that doesn't require you to build up a big Regex string and then evaluate it. Instead, what about using the List::MoreUtils module's any function, which accepts a block of code, evaluates it for each member of a list, and returns true once the block returns true for at least one entry in the list. For example:
use List::MoreUtils qw{ any };
if ( any { $host =~ $_ } #conf ) {
# do something
}
In the code block passed to any, the temp variable $_ contains the current entry in the list. That way you can avoid constructing a Regex in the first place.

I think you should store the complete regex in the configuration file, but it can be be a set of comma separated alternatives if need be.
You would use the qr// construct to build the regex:
my $exc1 = "\d+DAT\d+"; # Read from configuration file
my $ecc2 = "\d+INF\d+";
my $rex1 = qr/$exc1/;
my $rex2 = qr/$exc2/;
...populate $host...
if ($host =~ $rex1 || $host =~ $rex2)
{
...exclude $host...
}
else
{
...include $host...
}
Alternatively, you can build a single regex:
my $exc1 = "\d+DAT\d+"; # Read from configuration file
my $ecc2 = "\d+INF\d+";
my $rex = qr/$exc1|$exc2/;
...populate $host...
if ($host =~ $rex)
{
...exclude $host...
}
else
{
...include $host...
}
The single regex can be built from as many alternative exclusionary regex fragments as you like. Of course, if the value in the file is:
EXCLUDE=\d+DAT\d+|\d+INF\d+
then your code simplifies once more, assuming the regex string is read into $exc:
my $exc = "\d+DAT\d+|\d+INF\d+"; # Read from file
my $rex = qr/$exc/;
...populate $host...
if ($host =~ $rex)
{
...exclude $host...
}
else
{
...include $host...
}

Related

use of require in Perl

I'm trying to use require multiple times in my code, but I can to run it once.
if ($value =~ "test") {
require "mycode1.pm"; # Execute perfect
}
if ($value =~ "code") {
require "mycode1.pm"; # Not execute
}
if ($value =~ "last") {
require "mycode1.pm"; # # Not execute
}
I don't understand why it doesn't run, could you please help me?
As you've been told in the comments, Perl keeps a record of which libraries have already been loaded, so require will only be called once for each file.
A couple of notes on your code.
The right-hand operand to the binding operator should be a match (m/.../), a substitution (s/.../.../) or a transliteration (tr/.../.../). You're using strings, which are silently converted to matches at runtime - but it's better to be more explicit.
$value =~ /test/
Given that the results of your three if tests are all the same, it's probably worth converting them into a single test.
if ($value =~ /test|code|last/) {
...
}

Perl issue - If-Statement in OTRS Module

Actually I don't really know Perl, but now I need to change a snippet for OTRS.
To explain the context shortly, it's in the Module SystemMonitoring.pm of OTRS:
The System writes the values out of an E-Mail via regular expressions and saves in $1.
There are three relevant Elements: State, Host and Service.
Only in Element State I want to write the value out of $2. In case of Host and Service the informations comes from $1.
Here is the snippet:
for my $Element (qw(State Host Service)) {
next ELEMENT if $AlreadyMatched{$Element};
my $Regex = $Self->{Config}->{ $Element . 'RegExp' };
if ( $Line =~ /$Regex/ ) {
# get the found element value
$Self->{$Element} = $1;
# remember that we found this element already
$AlreadyMatched{$Element} = 1;
}
}
I hope somebody can help me with this issue.
Thanks alot!
I'll assume that the regex is \s*State:\s+(\w+)\s->\s+(\w+) as mentioned by the OP.
In that case, you can simply test for the value of $Element.
if ($Element eq 'State') {
print $2;
}
else {
print $1;
}
or more concisely,
print ($Element eq 'State') ? $2 : $1;

How to extract directory names from a path in Perl

I have a path like this
/home/user/doc/loc
I want to extract home, user, doc, loc separately. I tried split (////) and also split("/")
but none of them worked. Please give me sample script:
while (<EXPORT>) {
if (/^di/) {
($key, $curdir) = split(/\t/);
printf "the current dir is %s\n", $curdir;
printf("---------------------------------\n");
($home_dir, $user_dir, $doc_dir, $loc_dir) = split("/");
}
}
But it didn't work; hence please help me.
Given $curdir containing a path, you'd probably use:
my(#names) = split m%/%, $curdir;
on a Unix-ish system. Or you would use File::Spec and splitdir. For example:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
my $curdir = "/home/user/doc/loc";
my(#names) = split m%/%, $curdir;
foreach my $part (#names)
{
print "$part\n";
}
print "File::Spec->splitdir()\n";
my(#dirs) = File::Spec->splitdir($curdir);
foreach my $part (#dirs)
{
print "$part\n";
}
Ouput (includes a leading blank line):
home
user
doc
loc
File::Spec->splitdir()
home
user
doc
loc
split's first result will be the string preceding the first instance of the regular expression passed to it. Since you have a leading "/" here you would get an empty string in $home_dir, 'user' in $user_dir and so on. Add undef to the list assignment's first position or alternatively trim a leading slash first.
Also I'm not sure if you can call split without passing it $curdir here. Try:
(undef, $home_dir, $user_dir, $doc_dir, $loc_dir) = split("/", $curdir);

Perl: How to detect which file exists among foo.(txt|abc)

My perl script needs to detect the extension of an existing file and print out the filename. The input that specifies the filename with a vague extension would be in this format:
foo.(txt|abc)
and the script would print "foo.txt" if it exists. If foo.txt does not exist and foo.abc exists, then it would print "foo.abc."
How can I do this detection and printing of the correct existing file in a neat and clean way?
Thanks!
Actually, you've almost got the regular expression right there: the only thing you need to do is escape the . with a backslash (since . means "any character except the newline character" in regular expressions), and it would also help to put a ?: inside of the parentheses (since you don't need to capture the file extension). Also, ^ and $ denote markers for the beginning and the end of the string (so we're matching the entire string, not just part of a string...that way we don't get a match for the file name "thisisnotfoo.txt")
Something like this should work:
use strict;
use warnings;
my $file1="foo.txt";
my $file2="foo.abc";
my $file3="some_other_file";
foreach ($file1,$file2,$file3)
{
if(/^foo\.(?:txt|abc)$/)
{
print "$_\n";
}
}
When the above code is run, the output is:
foo.txt
foo.abc
Take a look at perldoc perlretut for more stuff about regular expressions.
You may want to look at glob, but you'd have to use a different syntax. The equivalent would be:
foo.{txt,abc}
See File::Glob for more information. Also note that this will return a list of all of the matches, so you'll have to do your own rules if it should prefer one when multiple exist.
sub text_to_glob {
my ($s) = #_;
$s =~ s/([\\\[\]{}*?~\s])/\\$1/g;
return $s;
}
my $pat = 'foo.(txt|abc)';
my #possibilities;
if (my ($base, $alt) = $pat =~ /^(.*\.)\(([^()]*)\)\z/s) {
#possibilities = glob(
text_to_glob($base) .
'{' . join(',', split(/\|/, $alt)) . '}'
);
} else {
#possibilities = $pat;
}
for my $possibility (#possibilities) {
say "$possibility: ", -e $possibility ? "exists" : "doesn't exist";
}
glob, but also see File::Glob
-e
use strict;
use warnings;
FILE:
for (glob "file.{txt,abc}") {
if (-f $_) {
print $_, "\n";
last FILE;
}
}

What is the idiomatic way in Perl to determine whether a string variable matches a string in a list?

Part of the specification says "Some names are special, e.g. Hughie, Dewey, Louis, and Donald. Other names may be added over the lifetime of the project at arbitrary times. Whenever you input one of those names, play quack.wav."
I could write ...
while (<>) {
if ($_ =~ /Hughie|Dewey|Louis/) {
quack() ;
}
elsif ($_ =~ /Donald/ {
quack() ;
you_re_fired_apprentice() ; # Easter egg don't tell QA
}
}
... but though untaxing to implement, it looks WTF-y: Where's the binary search? What if there were a sudden stupendous increase in the number of duck names? It would not scale at all!
I could create empty files using those names in a temporary directory, and then use the "file exists" API, but that seems roundabout, and I would have to be sure they were deleted at the end.
Surely there is a better way?
You could write that, but you should write this:
my %ducks = map {$_ => 1} qw(Hughie Dewey Louis);
while (<>) {
if ($ducks{$_}) {
quack() ;
}
elsif ($_ eq 'Donald') {
quack() ;
you_re_fired_apprentice() ; # Easter egg don't tell QA
}
}
Creating the hash takes a little bit of time, but not more than O(n). Lookup with a hash is O(1) though, so it is much more efficient than sequential search (via grep or a regex with alternation) assuming you will be checking for more than one or two items.
By the way, the regex that you have will match the words anywhere in the search string. You need to add start and end anchors if you want an exact match.
Alternatively, you could use smart matching
my #ducks = qw(Hughie Dewey Louis);
my $name = 'Dewey';
say 'smart match' if $name ~~ #ducks;
This is what is used by switch statements, so you could write
given ($name) {
when (#ducks) {
quack();
}
when ('Donald') {
quack();
you_re_fired_apprentice(); # Easter egg don't tell QA
}
}
As mentioned, hashes are the way to go for this. This is sort of what OOP looked like before OOP.
use strict;
use warnings;
my %duck_action = (
Donald => sub {quack(); you_re_fired_apprentice()},
Hughie => sub {quack()},
Dewie => sub {quack()},
Louis => sub {quack()},
);
for my $duck (qw( Hughie Dewie Donald Louis Porkie )) {
print "$duck: ";
my $action = $duck_action{$duck} || &no_such_duck;
$action->();
}
sub quack {
print "Quack!\n";
}
sub you_re_fired_apprentice {
print "You're fired!\n";
}
sub no_such_duck {
print "No such duck!\n";
}
You can use a Perl Hash. See also How can I represent sets in Perl? and Representing Sets in Perl.
Using hashes to implement a set is not exactly pretty, but it should be fast.
To find a string in a list, you could also use any in List::MoreUtils
use List::MoreUtils qw(any);
my #ducks = qw(Hughie Dewey Louis);
my $name = 'Dewey';
say 'any' if any {$name eq $_} #ducks;
If you're tied to using an array rather than a hash, you can use perl's grep function to search the array for a string.
#specialnames = qw(Hughie Dewey Louis);
while (my $value = <>) {
if (grep {$value eq $_}, #specialnames) {
quack() ;
}
elsif ($_ =~ /Donald/ {
quack() ;
you_re_fired_apprentice() ; # Easter egg don't tell QA
}
}
This does scale a lot worse than a hash, and might even scale worse than copying the array into a hash and then doing hash lookups.