How to use a variable as the substitution text in perl (s//)? - regex

I want to use a variable as the substitution text in perl (s//)? Apparently, the following code does not work. Is there a way to do what I want?
~$ ./subsuffix.pl
xxx_$1_outsuffix.txt
subsuffix.pl :
#!/usr/bin/env perl
use strict;
use warnings;
my $filename="xxx_5p_insuffix.txt";
my $insuffix="_((5|3)p)_insuffix.txt";
my $outsuffix = '_$1_outsuffix.txt';
#result of the following is what I expect
#$filename =~ s/$insuffix$/_$1_outsuffix.txt/;
#But I want used a variable as the substitution text.
#Unfortunately, the following do not work.
$filename =~ s/$insuffix$/$outsuffix/;
print "$filename\n";

The replacement is normally not evaluated. You have to add a couple of /e's at the end of the substitution and add some quotes to keep it a valid expression after the 1st evaluation:
$filename =~ s/$insuffix$/qq("$outsuffix")/ee;

You can use the /e modifier to treat the substitution pattern as code to be evaluated:
$filename =~ s/$insuffix$/ "_" . $1 . "_outsuffix.txt" /e;

What you have in $outsuffix is a template. Templates don't magically process themselves. You'll need to invoke a processor. String::Interpolate understands templates such as yours.
use String::Interpolate qw( interpolate );
my $filename="xxx_5p_insuffix.txt";
my $insuffix="_((5|3)p)_insuffix.txt";
my $outsuffix = '_$1_outsuffix.txt';
$filename =~ s/$insuffix$/interpolate($outsuffix)/e;
print "$filename\n";

Related

command-line-argument deduction as a special variable

My simple script in Perl
#!/usr/bin/perl
use v5.20.1;
use strict;
use warnings;
$| = 1;
my $string = $ARGV[ 0 ];
my $match = $ARGV[ 1 ];
my $substitute = $ARGV[ 2 ];
$string =~ m/match/g; # just for capturing the pattern
$string =~ s/$match/only $substitute/g;
say "\$1[itself] == $1";
say "\$substitute == $substitute";
say "\$string == $string";
say "prefix == $`";
say "match == $&";
say "suffix == $'";
Input and Output:
$ perl temp 'I have 9 dollars' '(\d+)' '$1'
$1[itself] == 9
$substitute == $1
$string == I have only $1 dollars
prefix == I have
match == 9
suffix == dollars
A thing that I am trying to do is a simple substitution by initializing the match and substitution variable from #ARGV. After that a simple match for initializing the special character such as $1. But when I want to pass the special character $1 from command-line to the script, it is deducted as a regular string and not the special variable $1 that I need for substitution
If I change the code from:
$string =~ s/$match/only $substitute/g;
to:
$string =~ s/$match/only $1/g;
now it works!
1. Why $1 of the command line is different from $1 in the script?
2. Is there any way to solve it?
A screenshot of my console:
Edit
As ikegami suggested I installed and used the String::Substitution and could by using gsub_copy($line, $match, $substitute); deduced the substitution.
But still I need to use this substitution itself for printing it on the screen and colorize it for illustration that what happens.
In fact this purpose, is part of a rename script that read all file and by call rename function, changes the name of the files.
As you can see on the screenshot:
instead of for_$1 it should be for_Level on the screen. So does this module can return what it has deduced?
I am not talking about the sub match of the match group parentheses!
Also the author of the module has said:
This module does not save or interpolate $& to avoid the "considerable
performance penalty"
May this screenshot clarify the subject:
Why $1 of the command line is different from $1 in the script?
You're actually asking why
s/.../...$substitute.../
in your script is different than
s/.../...$1.../
in your script. For the same reason that
print($substitute);
is different than
print($1);
and that is that the value of $1 (the captured text) is different than the value of $substitute ($1).
This is what you need:
use String::Substitution qw( gsub_modify );
gsub_modify($string, $match, $substitue);
That assumes you don't actually need $1 outside of the replacement expression, and that you don't actually need $& and friends at all. If you do need them, then you can use the following:
use String::Substitution qw( interpolate_match_vars last_match_vars );
$string =~ s/$match/ interpolate_match_vars($substitute, last_match_vars()) /eg;
Obligatory discouragement: don't do this. Basically this is just running eval on a user-supplied string, which is really a bad idea.
Actual answer: use the ee modifer (see perlop) to s/// to eval the string in the right hand part of the expression:
$string =~ s/$match/"\"only $substitute\""/gee
By the way, you don't need to capture the pattern separately, just put the $match in a capturing group in the s/// expression:
$string =~ s/($match)/"\"only $substitute\""/gee

Simple perl regex replacement

Here is my perl code:
my $var="[url=/jobs/]click here[/url]";
$var =~ /\[url=(.+?)\](.+?)\[\/url\]/\2/g
I'm very new to perl so i am aware that its incorrect but how do i perform this regex replacement correctly.
The end result would be a transformation of $var to click here
So, with all the answers you know the substitute form is s///
However, with something this big you should break it up into parts
to make it easier to maintain. And also helps to get out of the
quagmire of delimiter hell.
This uses a pre-compiled regex and a callback function invoked with s///e
use strict;
use warnings;
# Pre-compiled regex
my $rx = qr{\[url=(.+?)\](.+?)\[/url\]};
# Callback
sub MakeAnchor {
my ($href,$text) = #_;
return '' . $text . '';
}
my $input = '[url=/jobs/]click here[/url]';
$input =~ s/$rx/MakeAnchor($1,$2)/eg;
print $input;
Outout
click here

How to use regular expression to remove $ character in string?

I want to use regular expression to remove string with $ , % , # these three characters , but it seems can't remove $ and the error information shows undefined variable
How can I solve this problem?
here is my code
perl Remove.pl $ABC#60%
#!/usr/bin/perl
$Input = $ARGV[0];
$Input =~ s/\$|%|#//g;
print $Input;
thanks
I think your problem is with the shell, not with the Perl code. Single quote the argument to the script:
perl remove.pl '$ABC#60%'
The shell can interpret '$ABC' as a variable name in which case the script will receive no arguments. Perl will then complain about undefined variable in substitution.
$Input =~ s/[\$%#]//g;
ought to work
if you just want to remove some charactor, it will be better use tr
try this:
perl -e '$arg = shift; $arg =~ tr/$%#//d; print $arg' '$asdf#$'
your code is just fine, but the parameter you pass to the program will expand in bash. you should put single quote.
try this:
perl Remove.pl '$ABC#60%'

How to pass a replacing regex as a command line argument to a perl script

I am trying to write a simple perl script to apply a given regex to a filename among other things, and I am having trouble passing a regex into the script as an argument.
What I would like to be able to do is somthing like this:
> myscript 's/hi/bye/i' hi.h
bye.h
>
I have produced this code
#!/utils/bin/perl -w
use strict;
use warnings;
my $n_args = $#ARGV + 1;
my $regex = $ARGV[0];
for(my $i=1; $i<$n_args; $i++) {
my $file = $ARGV[$i];
$file =~ $regex;
print "OUTPUT: $file\n";
}
I cannot use qr because apparently it cannot be used on replacing regexes (although my source for this is a forum post so I'm happy to be proved wrong).
I would rather avoid passing the two parts in as seperate strings and manually doing the regex in the perl script.
Is it possible to pass the regex as an argument like this, and if so what is the best way to do it?
There's more than one way to do it, I think.
The Evial Way:
As you basically send in a regex expression, it can be evaluated to get the result. Like this:
my #args = ('s/hi/bye/', 'hi.h');
my ($regex, #filenames) = #args;
for my $file (#filenames) {
eval("\$file =~ $regex");
print "OUTPUT: $file\n";
}
Of course, following this way will open you to some very nasty surprises. For example, consider passing this set of arguments:
...
my #args = ('s/hi/bye/; print qq{MINE IS AN EVIL LAUGH!\n}', 'hi.h');
...
Yes, it will laugh at you most evailly.
The Safe Way:
my ($regex_expr, #filenames) = #args;
my ($substr, $replace) = $regex_expr =~ m#^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/#;
for my $file (#filenames) {
$file =~ s/$substr/$replace/;
print "OUTPUT: $file\n";
}
As you can see, we parse the expression given to us into two parts, then use these parts to build a full operator. Obviously, this approach is less flexible, but, of course, it's much more safe.
The Easiest Way:
my ($search, $replace, #filenames) = #args;
for my $file (#filenames) {
$file =~ s/$search/$replace/;
print "OUTPUT: $file\n";
}
Yes, that's right - no regex parsing at all! What happens here is we decided to take two arguments - 'search pattern' and 'replacement string' - instead of a single one. Will it make our script less flexible than the previous one? No, as we still had to parse the regex expression more-or-less regularly. But now user clearly understand all the data that is given to a command, which is usually quite an improvement. )
#args in both examples corresponds to #ARGV array.
The s/a/b/i is an operator, not simply a regular expression, so you need to use eval if you want it to be interpreted properly.
#!/usr/bin/env perl
use warnings;
use strict;
my $regex = shift;
my $sub = eval "sub { \$_[0] =~ $regex; }";
foreach my $file (#ARGV) {
&$sub($file);
print "OUTPUT: $file\n";
}
The trick here is that I'm substituting this "bit of code" into a string to produce Perl code that defines an anonymous subroutine $_[0] =~ s/a/b/i; (or whatever code you pass it), then using eval to compile that code and give me a code reference I can call from within the loop.
$ test.pl 's/foo/bar/' foo nicefood
OUTPUT: bar
OUTPUT: nicebard
$ test.pl 'tr/o/e/' foo nicefood
OUTPUT: fee
OUTPUT: nicefeed
This is more efficient than putting an eval "\$file =~ $regex;" inside the loop as then it'll get compiled and eval-ed at every iteration rather than just once up-front.
A word of warning about eval - as raina77ow's answer explains, you should avoid eval unless you're 100% sure you are always getting your input from a trusted source...
s/a/b/i is not a regex. It is a regex plus substitution. Unless you use the string eval, make this work might be pretty tough (consider s{a}<b>e and so on).
The trouble is that you are trying to pass a perl operator when all you really need to pass is the arguments:
myscript hi bye hi.h
In the script:
my ($find, $replace, #files) = #ARGV;
...
$file =~ s/$find/$replace/i;
Your code is a bit clunky. This is all you need:
use strict;
use warnings;
my ($find, $replace, #files) = #ARGV;
for my $file (#files) {
$file =~ s/$find/$replace/i;
print "$file\n";
}
Note that this way allows you to use meta characters in the regex, such as \w{2}foo?. This can be both a good thing and a bad thing. To make all characters intepreted literally (disable meta characters), you can use \Q ... \E like so:
... s/\Q$find\E/$replace/i;

How do I substitute with an evaluated expression in Perl?

There's a file dummy.txt
The contents are:
9/0/2010
9/2/2010
10/11/2010
I have to change the month portion (0,2,11) to +1, ie, (1,3,12)
I wrote the substitution regex as follows
$line =~ s/\/(\d+)\//\/\1+1\//;
It's is printing
9/0+1/2010
9/2+1/2010
10/11+1/2010
How to make it add - 3 numerically than perform string concat? 2+1??
Three changes:
You'll have to use the e modifier
to allow an expression in the
replacement part.
To make the replacement globally
you should use the g modifier. This is not needed if you've one date per line.
You use $1 on the replacement side, not a backreference
This should work:
$line =~ s{/(\d+)/}{'/'.($1+1).'/'}eg;
Also if your regex contains the delimiter you're using(/ in your case), it's better to choose a different delimiter ({} above), this way you don't have to escape the delimiter in the regex making your regex clean.
this works: (e is to evaluate the replacement string: see the perlrequick documentation).
$line = '8/10/2010';
$line =~ s!/(\d+)/!('/'.($1+1).'/')!e;
print $line;
It helps to use ! or some other character as the delimiter if your regular expression has / itself.
You can also use, from this question in Can Perl string interpolation perform any expression evaluation?
$line = '8/10/2010';
$line =~ s!/(\d+)/!("/#{[$1+1]}/")!e;
print $line;
but if this is a homework question, be ready to explain when the teacher asks you how you reach this solution.
How about this?
$ cat date.txt
9/0/2010
9/2/2010
10/11/2010
$ perl chdate.pl
9/1/2010
9/3/2010
10/12/2010
$ cat chdate.pl
use strict;
use warnings;
open my $fp, '<', "date.txt" or die $!;
while (<$fp>) {
chomp;
my #arr = split (/\//, $_);
my $temp = $arr[1]+1;
print "$arr[0]/$temp/$arr[2]\n";
}
close $fp;
$