I'm writing a tiny program that takes user input using Getops, and based on it, the program will either try to match a pattern against some text, or substitute text for what matched.
The problem I'm having is that I can't get the substitution portion to work. I'm looking at the qr// entry in the man pages: http://perldoc.perl.org/perlop.html#Regexp-Quote-Like-Operators but I'm not having any luck with it. I tried to model my code exactly like the docs in this case. I compile a match pattern, and substitute that into a substitution.
Could someone point out where I'm going wrong? (Don't worry about security too much, this is only a little script for personal use)
Here's what I'm looking at:
if($options{r}){
my $pattern = $options{r};
print "\nEnter Replacement text: ";
my $rep_text = <STDIN>;
#variable grab, add flags to pattern if they exist.
$pattern .= 'g' if $options{g};
$pattern .= 'i' if $options{i};
$pattern .= 's' if $options{s};
#compile that stuff
my $compd_pattern = qr"$pattern" or die $#;
print $compd_pattern; #debugging
print "Please enter the text you wish to run the pattern on: ";
my $text = <STDIN>;
chomp $text;
#do work and display
if($text =~ s/$compd_pattern/$rep_text/){ #if the text matched or whatever
print $text;
}
else{
print "$compd_pattern on \n\t{$text} Failed. ";
}
} #end R FLAG
When I run it with -r "/matt/" -i, and enter the replacement text 'matthew', on the text 'matt', it fails. Why is this?
EDIT:
Thanks for the answers guys ! That was really very helpful. I combined both of your suggestions into a working solution to the problem. I have to handle the /g flag a little differently. Here is the working sample:
if($options{r}){
my $pattern = $options{r};
print "\nEnter Replacement text: ";
my $rep_text = <STDIN>;
chomp $rep_text;
#variable grab, add flags to pattern if they exist.
my $pattern_flags .= 'i' if $options{i};
$pattern_flags .= 's' if $options{s};
print "Please enter the text you wish to run the pattern on: ";
my $text = <STDIN>;
chomp $text;
#do work and display
if($options{g}){
if($text =~ s/(?$pattern_flags:$pattern)/$rep_text/g){ #if the text matched or whatever (with the g flag)
print $text;
}
else{
print "$pattern on \n\t{$text} Failed. ";
}
}
else{
if($text =~ s/(?$pattern_flags:$pattern)/$rep_text/){ #if the text matched or whatever
print $text;
}
else{
print "$pattern on \n\t{$text} Failed. ";
}
}
} #end R FLAG
As chaos points out, you will encounter some difficulties using qr//. Do you really need to precompile the pattern? If not, a strategy like this might work:
my $pattern = 'matt';
my $text = 'Matt';
my $rep_text = 'Matthew';
my $pattern_opts = 'i';
print $text, "\n" if $text =~ s/(?$pattern_opts:$pattern)/$rep_text/;
Update in response to your new code: you might consider using an approach like this:
my ($orig, $patt, $rep, $flags) = qw(FooFooFoo foo bar ig);
my $make_replacement = $flags =~ s/g// ?
sub { $_[0] =~ s/(?$flags:$patt)/$rep/g } :
sub { $_[0] =~ s/(?$flags:$patt)/$rep/ }
;
if ( $make_replacement->($orig) ){
print $orig;
}
else {
print "Failed...";
}
Run it with -r "matt", not -r "/matt/". You don't need to, and in fact can't, supply pattern delimiters in your option string. The quotes are the delimiters in your qr. So it's actually looking for matt with slashes around it, the way you're running it, which isn't what you want. You're trying to use the quotes to tell Perl to treat your pattern string like it were source code, but unfortunately you can't do that.
All those pattern appends you're doing for the other options also won't work. You'll need to change the way you compile the regex if you want to do all that. Something like this might do it for /i and /s:
my $compd_pattern = qr/$pattern/ or die $#;
$compd_pattern = qr/$compd_pattern/i if $options{i};
$compd_pattern = qr/$compd_pattern/s if $options{s};
For /g you'll need to support an alternate version of the search/replace. /g isn't a valid modifier to qr//.
Related
I'm new to programming so bear with me. I'm working on a Perl script that asks the user the number of different items they want to search for and what those items are, separating them by pressing ENTER. That part works okay.
Then, the script is to open up a file, parse through, and print each line that matches with the items that the user initially listed. This is the part that I haven't been able to figure out yet. I've tried different variations of the code. I saw many people suggest using the index function but I had no luck with it. It does seem to be working when I swap $line =~ $array for $line =~ /TEXT/. I'm hoping someone here can shed some light.
Thanks in advance!
#!usr/bin/perl
use strict;
use warnings;
my $line;
my $array;
print "Enter number of items: ";
chomp(my $n = <STDIN>);
my #arrays;
print "Enter items, press enter to separate: \n";
for (1..$n) {
my $input = <STDIN>;
push #arrays, $input;
}
open (FILE, "file.txt") || die "can't open file!";
chomp(my #lines = <FILE>);
close (FILE);
foreach $array (#arrays) {
foreach $line (#lines) {
if ($line =~ $array) {
print $line, "\n";
}
}
}
#purplekushbear Welcome to Perl! In Perl, there is more than one way to do it (TIMTOWTDI) so please take this in the spirit of teaching that it is given.
First off your line one -- the #! (sha bang line) is missing the leading / in the path to perl. In Linux/UNIX environments if your script is executable the path after the #! is used to run your program. --- If you do an ls on /usr/bin/perl you should see it. Sometimes it is found at /bin/perl or /usr/local/bin/perl.
When the person mentioned you forgot to chomp they where referring to where you are setting the $input variable. Just chomp like you did for $n and you will be ok.
As for the main part of your program go back and read what you wanted to do and do exactly that might be simpler to do. I think you have a good start on the problem and seem to know that arrays start with a # and scalar variables use the $ sigil, and you use strict which is great.
Here is one way to solve your problem:
#!/usr/bin/perl
use strict;
use warnings;
print "Enter number of items: ";
chomp(my $num = <STDIN>);
my #items = ();
print "Enter items, press enter to separate: \n";
for (1 .. $num)
{
chomp(my $input = <STDIN>);
push #items, $input;
}
open (FILE, "file.txt") || die "can't open file because $!";
while (my $line = <FILE>)
{
foreach my $item (#items)
{
if ($line =~ m/$item/)
{
print $line;
last;
}
}
}
close (FILE);
Notice I used the name #items for your items instead of #arrays which will make understanding the code easier when you come back to it someday. Always write with an eye towards maintainability. Anyways, ask if you have any questions but since I left much of the code the same I don't think you will have much trouble figuring it out. Perldoc and google are your friends. E.g. you can type:
perldoc -f last
to find out how last works. Have fun!
In you script you have forgot to add the chomp while giving the user input, then you need to last the inside for loop when pattern is matched.
Then here is another way,You can try the following, same thing with different method.
I'm making variable name $regex instead of #array. In $regex variable I'm concatenating user input values with | separated. (In regex | behave like or). While concatenating I'm making the quotemeta to escape the special characters. Then I'm making the precompiled regex with qr for $regex variable
#!usr/bin/perl
use strict;
use warnings;
print "Enter number of items: ";
chomp(my $n = <STDIN>);
my $regex;
print "Enter items, press enter to separate: \n";
for (1..$n)
{
chomp(my $input = <STDIN>);
$regex .= quotemeta($input)."|";
}
chop $regex; #remove the last pipe
$regex = qr($regex);
open my $fh,"<", "file.txt" || die "can't open file!";
while(<$fh>)
{
print if(/$regex/i);
}
Then user #ikegami said his comment, you can use the Perl inbuilt #ARGV method instead of STDIN , for example
Your method
my #array = #ARGV;
Another method
my $regex = join "|", map { quotemeta $_ } #ARGV;
Then run the script perl test.pl input1 input2 input3.
And always use 3 arguments to open a file
The Situation
I am in the process of creating a simple template file that will aid in creating future scripts for doing various tasks via command line on *nix systems. As part of this, I might like to ask the user to input data which needs to validated against a regular expression that is supplied in the source code.
The Issue
Errors are begin generated when I attempt to run the Perl code via command line. I am attempting to pass a regular expression into the repeat subroutine and I'm not sure how to exactly do this. I am aware that I can execute a string using eval, however this is something that I would like to avoid due to convention.
The errors:
Use of uninitialized value $_ in pattern match (m//) at scripts/template line 40.
Use of uninitialized value $resp in concatenation (.) or string at scripts/template line 37.
The code:
#!/usr/bin/env perl
use strict;
use warnings;
use Cwd;
use Term::ANSIColor;
use Data::Dumper;
my $log = "template.log";
my $task = "template";
my $cwd = getcwd();
my $logPath = $cwd . "/". $log;
print ucfirst($task) . " utility starting...\n";
system("cd ~/Desktop");
system("touch " . $log);
&writeLog("Test");
sub writeLog {
open(my $fh, '>>', $logPath) or die "Could not open file '$log' $!";
print $fh $_[0] . localtime() . "\n";
close $fh;
return 1;
}
sub ask {
my $question = $_[0];
my $input = $_[1];
my $resp = <>;
chomp($resp);
}
sub repeat {
my $pat = $_[0];
my $resp = $_[1];
print $pat . "\n";
print $resp . "\n";
}
&repeat(/foo|bar/i, "y");
What I have tried:
Based on these sources:
Match regex and assign results in single line of code
How to assign result of a regex match to a new variable, in a single line?
sub repeat {
my $pat =~ $_[0];
my $resp = $_[1];
if($pat !~ $resp) {
print "foo\n";
} else {
print "bar\n";
}
}
Any help is appreciated!
To create a regular expression for use later, we use qr//:
my $regexp = qr/^Perl$/;
This compiles the regular expression for use later. If there's a problem with your regular expression, you'll hear about it immediately. To use this pre-compiled regular expression you can use any of the following:
# See if we have a match
$string =~ $regexp;
# A simple substitution
$string =~ s/$regexp/Camel/;
# Comparing against $_
/$regexp/;
A bare regex literal like /.../ matches agains $_. To create an independent regex object, use qr// quotes:
repeat(qr/foo|bar/i, "y");
(and please don't invoke subs like &sub unless you know when and why this is neccessary.)
I have a simple program where the user can enter a string.
After this the user can enter a regex. I need the string to be compared against this regex.
The following code do not work - the regex always fails.
And I know that its maybe because I am comparing a string with a string and not a string with a regex.
But how would you do this?
while(1){
print "Enter a string: ";
$input = <>;
print "\nEnter a regex and see if it matches the string: ";
$regex = <>;
if($input =~ $regex){
print "\nThe regex $regex matched the string $input\n\n";
}
}
Use lexical variables instead of global ones.
You should remember that strings read by <> usually contain newlines, so it might be necessary to remove the newlines with chomp, like this:
chomp(my $input = <STDIN>);
chomp(my $regex = <STDIN>);
You might want to interpret regex special characters taken from the user literally, so that ^ will match a literal circumflex, not the beginning of the string, for example. If so, use the \Q escape sequence:
if ($input =~ /\Q$regex\E/) { ... }
Don't forget to read the Perl FAQ in your journey through Perl. It might have all the answers before you even begin to specify the question: How do I match a regular expression that's in a variable?
You need to use a //, m//, or s/// — but you can specify a variable as the pattern.
if ($input =~ /$regex/) {
print "match found\n";
}
I think you need to chomp input and regex variables. and correct the expression to match regex
chomp( $input );
chomp( $regex );
if($input =~ /$regex/){
print "\nThe regex $regex matched the string $input\n\n";
}
I am writing a script to read a LOG file. I want the user to type a word and then look it up and print the line (from a string) matching the word.
I'm just learning Perl so please be very specific and simple so that I can understand it.
print "Please Enter the word to find: ";
chomp ($userInput = <STDIN>);
while ($line = <INPUT>)
if ($line =~ /userInput/)
print $line;
I know that this is not perfect but I'm just learning.
You were close. You need to expand the variable in the pattern match.
print "Please Enter the word to find: ";
chomp ($userInput = <STDIN>);
while ($line = <INPUT>) {
if ($line =~ /$userInput/) { # note extra dollar sign
print $line;
}
}
Be aware that that is a pattern match, so you are searching with a string that potentially contains wildcards in it. If you want a literal string, put a \Q in front of the variable as you interpolate it: /\Q$userInput/.
Something like .\bWORD\b. might work (thou it is not tested)
print $line if ($line =~ /.*\bWORD\b/)
#NewLearner
\b is for word boundaries
http://www.regular-expressions.info/wordboundaries.html
If you're doing just one loopup, using a while loop is fine. Though of course you'll need to fix your syntax.
You could also use grep:
print grep /$userInput/, <INPUT>;
If you want to do multiple lookups, you can either reopen the file handle (if the file is large), or store it in an array:
print grep /$userInput/, #array;
You'll have meta characters in your input, of course. This can be a good thing, or bad, depending on your users. For example, an experienced user would recognize the option to refine his search by entering a search term such as ^foo(?=bar), whereas other people may get very confused when they can't find the string foo+bar.
A way to escape meta characters is by using quotemeta on your input. Another is to use \Q ... \E inside your regex.
$userInput = quotemeta($userInput);
# or
print grep /\Q$userInput\E/, <INPUT>;
I believe if I were you, I would use a subroutine for the lookup. That way you can perform as many lookups as you like rather handily.
use strict;
use warnings; # ALWAYS use these
print "Please Enter the word to find: ";
chomp (my $userInput = <>); # <> is a more flexible handle
print lookup($userInput);
sub lookup {
my $word = shift;
open my $fh, "<", $inputfile or die $!;
my #hits;
while (<$fh>) {
push #hits, $_ if /\Q$word\E/;
}
return #hits;
}
Still plugging away at teaching myself Perl. I'm trying to write some code that will count the lines of a file that contain double letters and then place parentheses around those double letters.
Now what I've come up with will find the first occurrence of double letters, but not any other ones. For instance, if the line is:
Amp, James Watt, Bob Transformer, etc. These pioneers conducted many
My code will render this:
19 Amp, James Wa(tt), Bob Transformer, etc. These pioneers conducted many
The "19" is the count (of lines containing double letters) and it gets the "tt" of "Watt" but misses the "ee" in "pioneers".
Below is my code:
$file = '/path/to/file/electricity.txt';
open(FH, $file) || die "Cannot open the file\n";
my $counter=0;
while (<FH>) {
chomp();
if (/(\w)\1/) {
$counter += 1;
s/$&/\($&\)/g;
print "\n\n$counter $_\n\n";
} else {
print "$_\n";
}
}
close(FH);
What am I overlooking?
use strict;
use warnings;
use 5.010;
use autodie;
my $file = '/path/to/file/electricity.txt';
open my $fh, '<', $file;
my $counter = 0;
while (<$fh>) {
chomp;
if (/(\w)\1/) {
$counter++;
s/
(?<full>
(?<letter>\p{L})
\g{letter}
)
/($+{full})/xg;
$_ = $counter . ' ' . $_;
}
say;
}
You are overlooking a few things. strict and warnings; 5.010 (or higher!) for say; autodie so you don't have to keep typing those 'or die'; Lexical filehandles and the three-argument form of open; A bit nitpicky, but knowing when (not) to use parens for function calls; Understanding why you shouldn't use $&; The autoincrement operator..
But on the regex part specifically, $& is only set on matches (m//), not substitution Actually no, ysth is right as usual. Sorry!
(I took the liberty of modifying your regex a bit; it makes use of named captures - (?) instead of bare parens, accessed through \g{} notation inside the regex, and the %+ hash outside of it - and Unicode-style properties - \p{Etc}). A lot more about those in perlre and perluniprops, respectively.
You need to use a back reference:
#! /usr/bin/env perl
use warnings;
use strict;
my $line = "this is a doubble letter test of my scrippt";
$line =~ s/([[:alpha:]])(\1)/($1$2)/g;
print "$line\n";
And now the test.
$ ./test.pl
this is a dou(bb)le le(tt)er test of my scri(pp)t
It works!
When you do a substitution, you use the $1 to represent what is in the parentheses. When you are referring to a part of the regular expression itself, you use the \1 form.
The [[:alpha:]] is a special POSIX class. You can find out more information by typing in
$ perldoc perlre
at the command line.
You're overcomplicating things by messing around with $&. s///g returns the number of substitutions performed when used in scalar context, so you can do it all in one shot without needing to count matches by hand or track the position of each match:
#!/usr/bin/env perl
use strict;
use warnings;
my $text = 'James Watt, a pioneer of wattage engineering';
my $doubles = $text =~ s/(\w)\1/($1$1)/g;
print "$doubles $text\n";
Output:
4 James Wa(tt), a pion(ee)r of wa(tt)age engin(ee)ring
Edit: OP stated in comments that the exercise in question says not to use =~, so here's a non-regex-based solution, since all regex matches use =~ (implicitly or explicitly):
#!/usr/bin/env perl
use strict;
use warnings;
my $text = 'James Watt, a pioneer of wattage engineering';
my $doubles = 0;
for my $i (reverse 1 .. length $text) {
if (substr($text, $i, 1) eq substr($text, $i - 1, 1)) {
$doubles++;
substr($text, $i - 1, 2) = '(' . substr($text, $i - 1, 2) . ')';
}
}
print "$doubles $text\n";
The problem is that you're using $& in the second regex which only matched the first occurance of a double letter set
if (/(\w)\1/) { #first occurance matched, so the pattern in the replace regex will only be that particular set of double letters
Try doing something like this:
s/(\w)\1/\($1$1\)/g; instead of s/$&/\($&\)/g;
Full code after editing:
$file = '/path/to/file/electricity.txt';
open(FH, $file) || die "Cannot open the file\n";
my $counter=0;
while (<FH>) {
chomp();
if (s/(\w)\1/\($1$1\)/g) {
$counter++;
print "\n\n$counter $_\n\n";
} else {
print "$_\n";
}
}
close(FH);
notice that you can use the s///g replace in a conditional statement which is true when a replace occurred.