Why won't my Perl function work? - perl

I am having trouble with a function I wrote...
sub TemplateReplace
{
my($regex, $replacement, $text) = #_;
$text =~ s/($regex)/($replacement)/gs;
}
my $text = "This is a test.";
TemplateReplace("test", "banana", $text);
But it doesn't work. I thought arguments were sent by reference in Perl. Does the line my($regex, $replacement, $text) = #_; then copy them? How do I fix this?

sub TemplateReplace
{
my($regex, $replacement, $text) = #_;
$text =~ s/($regex)/($replacement)/gs;
return $text;
}
my $text = "This is a test.";
$text = TemplateReplace("test", "banana", $text);
There. That should work.
And yes, your my( ..) = #_ does copy the args. So if you're modifying a variable, you need to return it unless it's a global.

You are modifying a copy of the $text you passed in; this will have no effect on the original.
#!/usr/bin/perl
use strict;
use warnings;
my $text = "This is a test.";
template_replace(qr/test/, "bannana", $text);
print "$text\n";
sub template_replace {
my $regex = shift;
my $replacement = shift;
$_[0] =~ s/$regex/$replacement/gs;
}
The code above works because the elements of #_ are aliased to the variables passed in. But Adnan's answer is the more commonly done. Modifying arguments passed into functions is surprising behavior and makes things like template_replace(qr/foo/, "bar", "foo is foo") not work.

It's the "assignment" part of the sub-routine that is making the copies of the data.
If you modify the #_ arguments directly, they work as you expect. It is however, not very readable. :-)
use strict;
umask(0);
$|=1;
my $debug = 0;
my $text = "This is a test.";
print "Before 1: [$text]\n";
TemplateReplace("test", "banana", $text);
print "After 1: [$text]\n";
print "Before 2: [$text]\n";
TemplateReplace2("test", "banana", $text);
print "After 2: [$text]\n";
sub TemplateReplace
{
my ($regex, $replacement, $text) = #_;
$text =~ s/($regex)/($replacement)/gs;
}
sub TemplateReplace2
{
$_[2] =~ s/$_[0]/$_[1]/gs;
}
returns:
Before 1: [This is a test.]
After 1: [This is a test.]
Before 2: [This is a test.]
After 2: [This is a banana.]

Here is a variation on how to do it, which is almost identical to your code with a slight difference.
use strict;
use warnings;
sub TemplateReplace {
my($regex, $replacement, $text) = #_;
$$text =~ s/($regex)/$replacement/gs;
}
my $text = "This is a test.";
TemplateReplace("test", "banana", \$text);
print $text;
This behavior is explicit instead of implicit. In practice, it works identically to Chas. Owens result, but uses scalar-refs instead of relying on understanding the behaviour of arrays.
This will make it more obvious to anybody reading your code that the function "TemplateReplace" is intentionally modifying $text.
Additionally, it will tell you you're using it wrong by squawking with :
Can't use string ("This is a test.") as a SCALAR ref while "strict refs" in use at replace.pl line 9.
If you happen to forget the \ somewhere.

Related

Find words in string

I would like to show in result three words "hidden". I don't know how to do this. Right now, when I start the program, I don't have effect. I see only my text. How to repair it ? I want to create simple program. I have my string and I want to show words which I want to find in my result.
use strict;
my $text='Try to find the hidden string here! Hidden or hidden';
my $a = m/\bhidden\b/;
if ($text=~ $a) {
print "I found word: $a";
}
else {
print "No match, sorry.\n";
}
Let's see how we could debug this problem.
Your first step should be to add use warnings to your code. You should always include both use strict and use warnings. They are like Perl's safety nets and only a foolhardy programmer codes without them.
#!/usr/bin/perl
use strict;
use warnings; # ADDED THIS
my $text='Try to find the hidden string here! Hidden or hidden';
my $a = m/\bhidden\b/;
if ($text=~ $a) {
print "I found word: $a";
}
else {
print "No match, sorry.\n";
}
Now we get a warning:
Use of uninitialized value $_ in pattern match (m//) at test_re line 8.
Line 8 is this:
if ($text=~ $a) {
So let's see what is in $a (as an aside - please don't use $a as a variable name. Firstly, it's a terrible, generic name; and, secondly, Perl uses it as a special variable in the sort function):
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say'; # ADDED THIS
my $text='Try to find the hidden string here! Hidden or hidden';
my $a = m/\bhidden\b/;
say "\$a is [$a]"; # ADDED THIS
if ($text=~ $a) {
print "I found word: $a";
}
else {
print "No match, sorry.\n";
}
Now, as well as our warning, we get this output:
$a is []
I don't think you're expecting $a to be empty at that point, are you? We can go further and determine whether it's an empty string or an undefined value.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $text='Try to find the hidden string here! Hidden or hidden';
my $a = m/\bhidden\b/;
say "\$a is [$a]";
say "\$a is ", (defined $a ? 'defined' : 'undefined'); # ADDED THIS
if ($text=~ $a) {
print "I found word: $a";
}
else {
print "No match, sorry.\n";
}
And we see:
$a is defined
So it's an empty string. What do you expect it to be?
Here's where is it set?
my $a = m/\bhidden\b/;
I'm not sure what you expected this to do, but it actually means "match \bhidden\b and assign the result of the match to $a". And what does it match against that regex? Well, m/.../ matches the contents of$` by default. (That, incidentally, explains our original "uninitialized value $" warning - Perl just got the line number wrong by one.)
So what actually happens on that line is that Perl tries to match \bhidden\b against $_. This fails (as $_ is undefined) and the match returns a false value (the empty string) that gets assigned to $a.
Then your code goes on to run this:
if ($text=~ $a) {
And, as $a is the empty string, it matches (any string will match against an empty string and Perl tells you it has matched $a (which still contains the empty string).
I'm not sure what you were trying to do with this line:
my $a = m/\bhidden\b/;
Perhaps you were trying "pre-compile" the regex in some way. In which case, you were looking for qr/.../, not m/.../.
So maybe you wanted this:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $text = 'Try to find the hidden string here! Hidden or hidden';
my $a = qr/\bhidden\b/; # CHANGED THIS
say "\$a is [$a]";
say "\$a is ", (defined $a ? 'defined' : 'undefined');
if ($text =~ $a) {
print "I found word: $a";
}
else {
print "No match, sorry.\n";
}
Which gives this output:
$a is [(?^:\bhidden\b)]
$a is defined
I found word: (?^:\bhidden\b)
Or, if you want to show what has been matched, rather than the regex that you are matching, then you need to "capture" that text:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $text = 'Try to find the hidden string here! Hidden or hidden';
my $a = qr/\b(hidden)\b/; # CHANGED THIS
say "\$a is [$a]";
say "\$a is ", (defined $a ? 'defined' : 'undefined');
if (my ($matched) = $text =~ $a) { # CHANGED THIS
print "I found word: $matched"; # CHANGED THIS
}
else {
print "No match, sorry.\n";
}
Which gives us:
$a is [(?^:\b(hidden)\b)]
$a is defined
I found word: hidden
Ah, but you want to see all of the words. So capture the matches in an array and use the /g option to match multiple times:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $text = 'Try to find the hidden string here! Hidden or hidden';
my $a = qr/\b(hidden)\b/;
say "\$a is [$a]";
say "\$a is ", (defined $a ? 'defined' : 'undefined');
if (my (#matched) = $text =~ /$a/g) { # CHANGED THIS
print "I found word: #matched"; # CHANGED THIS
}
else {
print "No match, sorry.\n";
}
We now get this:
$a is [(?^:\b(hidden)\b)]
$a is defined
I found word: hidden hidden
That only matches two instances of "hidden" as the match is case-sensitive. So turn off the case sensitivity with /i.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $text = 'Try to find the hidden string here! Hidden or hidden';
my $a = qr/\b(hidden)\b/i; # CHANGED THIS
say "\$a is [$a]";
say "\$a is ", (defined $a ? 'defined' : 'undefined');
if (my (#matched) = $text =~ /$a/g) {
print "I found word: #matched";
}
else {
print "No match, sorry.\n";
}
Which gives us:
$a is [(?^i:\b(hidden)\b)]
$a is defined
I found word: hidden Hidden hidden
But, honestly, I think pre-compiling the regex here is just over-complicating things. I'd write this code like this (removing the $a variable completely):
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my $text = 'Try to find the hidden string here! Hidden or hidden';
if (my (#matched) = $text =~ /\b(hidden)\b/gi) {
print "I found word: #matched";
}
else {
print "No match, sorry.\n";
}
Perhaps you are looking for this piece of code
use strict;
use warnings;
use feature 'say';
my $text='Try to find the hidden string here! Hidden or hidden';
my $re = qr/(hidden)/i; # () capture found ignoring case
my #match = $text =~ /$re/g; # g specifies to look for all accurances
say for #match; # output each element of #match
Output
hidden
Hidden
hidden

How to pass an anonymous sub to Find::File

I know I can do this as an expression modifier:
#!/usr/bin/perl -w
use strict;
use File::Find;
sub file_find{
my ($path,$filter) = #_;
find(sub {print $File::Find::name."\n" if /$filter/}, $path);
}
file_find($newdir,'\.txt');
or this which is less readable:
find(sub {if(/$filter/){print $File::Find::name."\n"}}, $path);
But if I wanted to do something like this, how can I do it?
sub file_find{
my ($path,$filter) = #_;
find(\&print, $path);
sub print {
if(/$filter/){ #Variable $filter will not stay shared
print $File::Find::name."\n";
}
}
}
file_find($newdir,'\.txt')
I get 'variable will not stay shared'. I believe I'm supposed to make it an anonymous sub:
my $print = sub {
if(/$filter/){
print $File::Find::name."\n";
}
}
But then I don't know how to pass the reference to the find sub. Perhaps it's somthing silly I'm missing.
Edit: Never mind, this seems to work:
sub file_find{
my ($path,$filter) = #_;
my $subref = sub{
if(/$filter/){
print $File::Find::name."\n";
}
};
find($subref,$path);
}
file_find($newdir,'\.txt');
I had to push the find sub to the bottom! Man I feel so dumb :)
I would separate the subs apart (and rename the print() one as it conflicts with the built-in with the same name!), then you can do something along these lines (if I'm understanding what you want correctly):
use warnings;
use strict;
use File::Find;
file_find('.', '.txt');
sub file_find{
my ($path,$filter) = #_;
my #files = find(sub {my_print($filter)}, $path);
}
sub my_print {
my $filter = shift;
my $fname = $File::Find::name;
if($fname =~ /$filter/){
print "$fname\n";
}
}
However, with that said, File::Find::Rule can make these things very, very easy (particularly handling the file filters as it handles regex natively):
use warnings;
use strict;
use File::Find::Rule;
my $filter = '*.txt';
my $dir = '.';
my #files = File::Find::Rule->file()
->name($filter)
->in($dir);
print "$_\n" for #files;

Use of reference to elements in #_ to avoid duplicating code

Is it safe to take reference of elements of #_ in a subroutine in order to avoid duplicating code? I also wonder if the following is good practice or can be simplified. I have a subroutine mod_str that takes an option saying if a string argument should be modified in-place or not:
use feature qw(say);
use strict;
use warnings;
my $str = 'abc';
my $mstr = mod_str( $str, in_place => 0 );
say $mstr;
mod_str( $str, in_place => 1 );
say $str;
sub mod_str {
my %opt;
%opt = #_[1..$#_];
if ( $opt{in_place} ) {
$_[0] =~ s/a/A/g;
# .. do more stuff with $_[0]
return;
}
else {
my $str = $_[0];
$str =~ s/a/A/g;
# .. do more stuff with $str
return $str;
}
}
In order to avoid repeating/duplicating code in the if and else blocks above, I tried to improve mod_str:
sub mod_str {
my %opt;
%opt = #_[1..$#_];
my $ref;
my $str;
if ( $opt{in_place} ) {
$ref = \$_[0];
}
else {
$str = $_[0]; # make copy
$ref = \$str;
}
$$ref =~ s/a/A/g;
# .. do more stuff with $$ref
$opt{in_place} ? return : return $$ref;
}
The "in place" flag changes the function's interface to the point where it should be a new function. It will simplify the interface, testing, documentation and the internals to have two functions. Rather than having to parse arguments and have a big if/else block, the user has already made that choice for you.
Another way to look at it is the in_place option will always be set to a constant. Because it fundamentally changes how the function behaves, there's no sensible case where you'd write in_place => $flag.
Once you do that, the reuse becomes more obvious. Write one function to do the operation in place. Write another which calls that on a copy.
sub mod_str_in_place {
# ...Do work on $_[0]...
return;
}
sub mod_str {
my $str = $_[0]; # string is copied
mod_str_in_place($str);
return $str;
}
In the absence of the disgraced given I like using for as a topicalizer. This effectively aliases $_ to either $_[0] or the local copy depending on the value of the in_place hash element. It's directly comparable to your $ref but with aliases, and a lot cleaner
I see no reason to return a useless undef / () in the case that the string is modified in place; the subroutine may as well return the new value of the string. (I suspect the old value might be more useful, after the fashion of $x++, but that makes for uglier code!)
I'm not sure whether this is readable code to anyone but me, so comments are welcome!
use strict;
use warnings;
my $ss = 'abcabc';
printf "%s %s\n", mod_str($ss), $ss;
$ss = 'abcabc';
printf "%s %s\n", mod_str($ss, in_place => 1), $ss;
sub mod_str {
my ($copy, %opt) = #_;
for ( $opt{in_place} ? $_[0] : $copy ) {
s/a/A/g;
# .. do more stuff with $_
return $_;
}
}
output
AbcAbc abcabc
AbcAbc AbcAbc

Improvised way to use split function

I am using split function to extract the a string from given line.
exp :
\abc\Logs\PostBootLogs\05-27-2014_17-05-51\UILogs_05-27-2014_17-05-52.txt
\abc\Logs\PostBootLogs\01-10-1970_20-33-22\01-10-1970_21-18-26\UILogs_01-10-1970_21-18-27.txt
In the above exp we need to capture string which is there between PostBootLogs and UILogs_01-10-1970_21-18-27.txt
Code :
use strict;
use warnings;
my $data = '\abc\Logs\PostBootLogs\05-27-2014_17-05-51\UILogs_05-27-2014_17-05-52.txt';
my $test1 = '\abc\Logs\PostBootLogs\01-10-1970_20-33-22\01-10-1970_21-18-26\UILogs_01-10-1970_21-18-27.txt';
my #values = split('UILogs'(split('PostBootLogs', $data)));
my #values1 = split('UILogs', $values[1]);
print "$values1[0]\n\n";
print "$test1\n\n";
my #values_1= split('PostBootLogs', $test1);
my #values1_1 = split('UILogs', $values_1[1]);
print $values1_1[0];
exit 0;
is there any better way to do this thing?
You can use regex to capture between PostBootLogs\ and \UILogs
my ($captured) = $data =~ /PostBootLogs\\ (.+?) \\UILogs/x;
Regular expressions make this sort of thing much easier.
$data =~ m/PostBootLogs(.*?)UILogs/ or die "Misformatted data";
my $timestamps = $1;
Creative use of splits. As has been demonstrated, the easiest method to do this is to use a regex.
However, if you want to see how to use splits to more cleanly accomplish this observe the following:
use strict;
use warnings;
my #data = qw(
\abc\Logs\PostBootLogs\05-27-2014_17-05-51\UILogs_05-27-2014_17-05-52.txt
\abc\Logs\PostBootLogs\01-10-1970_20-33-22\01-10-1970_21-18-26\UILogs_01-10-1970_21-18-27.txt
);
print "splits: \n";
for (#data) {
my ($vol, #path) = split /\\/;
my $subdir = join '\\', #path[3..$#path-1];
print $subdir, "\n";
}
print "regex: \n";
for (#data) {
if (/PostBootLogs\\(.*)\\UILogs/) {
print "$1\n";
} else {
warn "Data format not recognized: $_";
}
}
Outputs:
splits:
05-27-2014_17-05-51
01-10-1970_20-33-22\01-10-1970_21-18-26
regex:
05-27-2014_17-05-51
01-10-1970_20-33-22\01-10-1970_21-18-26

Unable to pass a hash and a string to a function, together in perl!

I am basically trying to pass a string and a hash to a subroutine in perl.
sub coru_excel {
my(%pushed_hash, $filename) = #_;
print Dumper(%pushed_hash);
}
But it seems data is getting mixed up. The dumped data also includes the $filename. here is the output.
...................
$VAR7 = 'Address';
$VAR8 = [
'223 VIA DE
................
];
$VAR9 = 'data__a.xls' <----- $filename
$VAR10 = undef;
$VAR11 = 'DBA';
$VAR12 = [
'J & L iNC
..................
];
Here is how I called the subroutine.
coru_excel(%hash, "data_".$first."_".$last.".xls");
Arguments are passed to subroutines as one undifferentiated list.
One solution is to reverse the order of the arguments so that the scalar is first.
sub coru_excel {
my($filename, %pushed_hash) = #_;
}
coru_excel("FILE_NAME", %hash);
Another approach is to pass the hash by reference:
sub coru_excel {
my($pushed_hash_ref, $filename) = #_;
}
coru_excel(\%hash, "FILE_NAME");
You could pass the hash as a reference:
sub coru_excel {
my($pushed_hashref, $filename) = #_;
print Dumper(%$pushed_hashref);
}
coru_excel(\%my_hash, $file);
Or you could give special treatment to the final argument before you initialize the hash:
sub coru_excel {
my $filename = pop #_;
my(%pushed_hash) = #_;
print Dumper(%pushed_hash);
}
You have to pass the hash as a reference:
coru_excel(\%hash, "data_".$first."_".$last.".xls");
You use it like this:
sub coru_excel {
my($pushed_hash_ref, $filename) = #_;
my %pushed_hash = %{$pushed_hash_ref};
print Dumper(%pushed_hash); # better: \%pushed_hash or $pushed_hash_ref
}
See perlreftut for a tutorial on references and perlref for further information.
Dumper also produces better usable information when you pass a hash (or array) reference.
See also the related Perl FAQ. From the command line:
perldoc -q pass
or
perldoc -q hash
Refer to perlfaq7: How can I pass/return a {Function, FileHandle, Array, Hash, Method, Regex}?
A small program demonstrating how to do this using reference notation when passing the hash and shift in the subroutine to pull out the parameters.
#!/usr/bin/perl -w
use strict;
sub coru_excel(%$);
my %main_hash = ('key1' => 'val1', 'key2' => 'val2');
my $first = "ABC";
my $last = "xyz";
coru_excel(\%main_hash, "data_" . $first . "_" . $last . ".xls");
exit;
sub coru_excel(%$)
{
my %passed_hash = %{(shift)};
my $passed_string = shift;
print "%passed_hash:\n";
for my $k (keys %passed_hash) {
print " $k => $passed_hash{$k}\n";
}
print "\$passed_string = $passed_string\n";
return;
}