Perl regexp substitution of an array - regex

I would like to use Perl script A to generate and replace an array in Perl script B.
Script B originally contains something like:
my #old_array = (value1, value2, etc);
Script A contains something like:
for ( $a = 0; $a < $nr_values; $a++ ) {
$list .= "$new_values[$a], ";
}
`perl -pi -e 's/^my \#old_array.*/my \#new_array \= \( $list \)\;/g' script_B.pl;`
However, when I run Perl script A
The substitution occurs to all of the my declared variables
The array # symbol and name are not changed: only the updated values
Please advise how to properly substitute arrays using Perl?

I found a proper solution using triple backslashes:
perl -pi -e 's/^my \\\#temps.*/my \\\#temps \= \( NEW \)\;/g' modify.pl;
This solves both my regex find and replace issues.
For those attempting to help with substantive comments, thank you! For those insisting that I was not providing enough information, or that my issue was caused by unlisted code, I found your responses to be highly unprofessional and not focused on the question being asked.

Related

Regex (or bash), get pipes between quotes (perl)

Update: Please keep in mind is that regex is my only option.
Update 2: Actually, I can use a bash based solution as well.
Trying to replace the pipes(can be more than one) that are between double quotes with commas in perl regex
Example
continuer|"First, Name"|123|12412|10/21/2020|"3|7"||Yes|No|No|
Expected output (3 and 7 are separated by a comma)
continuer|"First, Name"|123|12412|10/21/2020|"3,7"||Yes|No|No|
There may be more digits, it may not be just the two d\|d. It could be "3|7|2" and the correct output has to be "3,7,2" for that one. I've tried the following
cat <filename> | perl -pi -e 's/"\d+\|[\|\d]+/\d+,[\|\d]+/g'
but it just puts the actual string of d+ etc...
I'd really appreciate your help. ty
If it must be a regex here is a simpler one
perl -wpe's/("[^"]+")/ $1 =~ s{\|}{,}gr /eg' file
Not bullet-proof but it should work for the shown use case.†
Explanation. With /e modifier the replacement side is evaluated as code. There, a regex runs on $1 under /r so that the original ($1) is unchanged; $N are read-only and so we can't change $1 and thus couldn't run a "normal" s/// on it. With this modifier the changed string is returned, or the original if there were no changes. Just as ordered.
Once it's tested well enough add -i to change the input file "in-place" if wanted.
I must add, I see no reason that at least this part of the job can't be done using a CSV parser...
Thanks to ikegami for an improved version
perl -wpe's/"[^"]+"/ $& =~ tr{|}{,}r /eg' file
It's simpler, with no need to capture, and tr is faster
† Tested with strings like in the question, extended only as far as this
con|"F, N"|12|10/21|"3|7"||Yes|"2||4|12"|"a|b"|No|""|end|
I'd use a CSV parser, not regular expressions:
#!/usr/bin/env perl
use warnings;
use strict;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new({ binary => 1, sep_char => "|"});
while (my $row = $csv->getline(*ARGV)) {
#$row = map { tr/|/,/r } #$row;
$csv->say(*STDOUT, $row);
}
example:
$ perl demo.pl input.txt
continuer|"First, Name"|123|12412|10/21/2020|3,7||Yes|No|No|
More verbose, but also more robust and a lot easier to understand.
If you cannot install modules, Text::ParseWords is a core module you can try. It can split a string and handle quoted delimiters.
use Text::ParseWords;
my $q = q(continuer|"First, Name"|123|12412|10/21/2020|"3|7"||Yes|No|No|);
print join "|", map { tr/|/,/; $_ } quotewords('\|', 1, $q);
As a one-liner, it would be:
perl -MText::ParseWords -pe'$_ = join "|", map { tr/|/,/; $_ } quotewords('\|', 1, $_);' yourfile.txt
You said Update 2: Actually, I can use a bash based solution as well. and while this script isn't bash you could call it from bash (or any other shell) which I assume is what you really mean by "bash based" so - this will work using any awk in any shell in every Unix box:
$ awk 'BEGIN{FS=OFS="\""} {for (i=2; i<=NF; i+=2) gsub(/\|/,",",$i)} 1' file
continuer|"First, Name"|123|12412|10/21/2020|"3,7"||Yes|No|No|
Imagine yourself having to debug or enhance the clear, simple loop above above vs the regexp incantation you posted in your answer:
's/(?:(?<=")|\G(?!^))(\s*[^"|\s]+(?:\s+[^"|\s]+)*)\s*\|\s*(?=[^"]*")/$1,/g'
Remember - Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems..
I'm sure you could do what I'm doing with awk above natively in perl instead if you're trying to modify a perl script to add this functionality.
I'd use Text::CSV_XS.
perl -MText::CSV_XS=csv -e'
csv
in => \*ARGV,
sep_char => "|",
on_in => sub { tr/|/,/ for #{ $_[1] } };
'
You can provide the file name as an argument or provide the data via STDIN.
This is working right now
's/(?:(?<=")|\G(?!^))(\s*[^"|\s]+(?:\s+[^"|\s]+)*)\s*\|\s*(?=[^"]*")/$1,/g'
Credit goes to my boss at work
Thanks everyone for looking.
I hope some of you realize that some projects require certain ways and complicating an already very complicated pre existing structure is not always an option at work. I knew there would be a one liner for this, do not hate because you did not like that.

Regex works in terminal but not in Perl script

This is related to this question
Where combination of all lines that end with a backslash character is done. The following Perl command works in terminal but not in Perl script:
perl -i -p -e 's/\\\n//' input_file
In Perl script, I set it this way but it does not work:
`perl -i -p -e 's/\\\n//' $input_file_path_variable`;
**Updated the file content and code used
Input file content as follows:
foo bar \
bash \
baz
dude \
happy
Desired output content:
foo bar bash baz
dude happy
Current script:
#!/usr/bin/env perl
use Getopt::Long;
use FindBin;
use File::Path;
use File::Copy;
use DirHandle;
use FileHandle;
use File::Basename;
use Env;
use File::Slurp;
my $dummy_file = "/wdir/dummy.txt";
my $file_content = read_file($dummy_file);
print "$file_content\n";
print "==============================================\n"
my $ttest = $file_content =~ s/\\\n//;
print "$ttest\n";
Current Output
foo bar \
bash \
baz
dude \
happy
==============================================
1
I think I realized what your problem is. You need to make your substitution match multiple times.
You are working with this one-liner:
perl -i -p -e 's/\\\n//' input_file
Which will replace the backslash and newline once per line in a file read in line-by-line mode. And now you are trying to implement it inside another program, where you are slurping a whole file into a variable, as mentioned in your comment on another answer:
I tried reading the whole file into a variable and applied your solution, not working for me: my $file_content = read_file($input_file_path_variable) and then $file_content =~ s/\\n//;
This will only replace one time. The first match in the file. You need to add the modifier /g to make the match global, as many times as possible:
$file_content =~ s/\\\n//g;
I am not sure how you are using your code, since you are not showing us. This makes answering simple questions hard. But if my assumptions are correct, this will fix your problem.
I think you've changed your code/approach at least once since originally asking the question, but based on your current code, your issue is this line:
my $ttest = $file_content =~ s/\\\n//;
Firstly, the s/// operator needs a g at the end so that it's a global search and replace, instead of stopping after the first substitution.
Secondly, $file_content =~ s/\\\n// doesn't return the final string; it modifies the string in-place. (So it's changing $file_content.) In recent versions of Perl, you can add an r modifier to the end to get it to return the modified string.
So either of these will work for you:
my $ttest = ( $file_content =~ s/\\\n//gr );
Or:
( my $ttest = $file_content ) =~ s/\\\n//g;
Full script which produces the output you want:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Slurp;
my $file_content = read_file('/tmp/dummy.txt');
(my $ttest = $file_content) =~ s/\\\n//g;
print "$file_content\n";
print "==============================================\n";
print "$ttest\n";
In a Perl you should write:
$input_file_variable =~ s/\\\n//;

Perl regexp substitution - multiple matches

Friends,
need some help with substitution regex.
I have a string
;;;;;;;;;;;;;
and I need to replace it by
;\N;\N;\N;\N;\N;\N;\N;\N;\N;\N;\N;\N;
I tried
s/;;/;\\N/;/g
but it gives me
;\N;;\N;;\N;;\N;;\N;;\N;;
tried to fiddle with lookahead and lookbehind, but can't get it solved.
I wouldn't use a regex for this, and instead make use of split:
#!/usr/bin/env perl
use strict;
use warnings;
my $str = ';;;;;;;;;;;;;';
print join ( '\N', split ( //, $str ) );
Splitting on nulls, to get each character, and making use of the fact that join puts delimiters between characters. (So not before first, and not after last).
This gives:
;\N;\N;\N;\N;\N;\N;\N;\N;\N;\N;\N;\N;
Which I think matches your desired output?
As a oneliner, this would be:
perl -ne 'print join ( q{\N}, split // )'
Note - we need single quotes ' rather than double around the \N so it doesn't get interpolated.
If you need to handle variable content (e.g. not just ; ) you can add grep or map into the mix - I'd need some sample data to give you a useful answer there though.
I use this for infile edit, the regexp suits me better
Following on from that - perl is quite clever. It allows you to do in place editing (if that's what you're referring to) without needing to stick with regular expressions.
Traditionally you might do
perl -i.bak -p -e 's/something/somethingelse/g' somefile
What this is doing is expanding out that out into a loop:
LINE: while (defined($_ = <ARGV>)) {
s/someting/somethingelse/g;
}
continue {
die "-p destination: $!\n" unless print $_;
}
E.g. what it's actually doing is:
opening the file
iterating it by lines
transforming the line
printing the new line
And with -i that print is redirected to the new file name.
You don't have to restrict yourself to -p though - anything that generates output will work in this way - although bear in mind if it doesn't 'pass through' any lines that it doesn't modify (as a regular expression transform does) it'll lose data.
But you can definitely do:
perl -i.bak -ne 'print join ( q{\N}, split // )'
And inplace edit - but it'll trip over on lines that aren't just ;;;;; as your example.
So to avoid those:
perl -i.bak -ne 'if (m/;;;;/) { print join ( q{\N}, split // ) } else { print }'
Or perhaps more succinctly:
perl -i.bak -pe '$_ = join ( q{\N}, split // ) if m/;;;/'
Since you can't match twice the same character you approach doesn't work. To solve the problem you can only check the presence of a following ; with a lookahead (the second ; isn't a part of the match) :
s/;(?=;)/;\\N/g

Replace the character # with incrementally increasing numbers

I am working with text, and wish to replace a # character with an incrementally increasing number. The text looks like this:
Chap.2.#
Chap.2.#
Chap.2.#
Chap.2.#
And I am trying to get it to read:
Chap.2.1
Chap.2.2
Chap.2.3
Chap.2.4 and on up to triple digits.
My source document is Mellel, but I also have Nisus Writer Pro, and a host of other text editors such as TextMate, Atom, TextWrangler, Brackets, CotEditor, jEdit, etc. I have tried using Regular Expressions in apps that indicate availability of using that, but to no avail.
I have tried searching for #
then replacing with \i, or \1, or \1\i, or \1\i.
Can someone help please? I've read many other similar questions on SO, and other sites, bt I can not seem to get the syntax correct (plus the other examples are not close enough to mine to help me figure this out).Thanks.
One solution is to use perl:
perl -pe 'BEGIN { our $i = 1; } s/Chap\.2\.#/"Chap.2.".($i++)/ge;' <chapters.txt;
Extension to handle multiple top-level numbers, incrementing the second-level number from 1 for each top-level number independently:
perl -pe 'BEGIN { our $h = {}; } s/Chap\.(\d+)\.#/"Chap.$1.".(exists($h->{$1}) ? $h->{$1}++ : ($h->{$1} = 1)++)/ge;' <chapters.txt;
To only find and incrementally replace the # character you can do:
perl -pe 'BEGIN { our $i = 1; } s/#/$i++/ge;' <chapters.txt;
Here's a way to do this with PHP at the commandline with a file called convert.php which takes a filename as an argument. Note: no for-loop:
<?php
if ( count($argv) < 2 ) {
echo "Correct syntax is: convert.php filename.ext\n";
exit;
}
$file = $argv[1];
$contents = file_get_contents( $file );
$nu = preg_replace_callback('/\#/',function($matches){
static $i=1;
return $i++;
},$contents);
echo '<pre>',"\n";
echo $nu;
file_put_contents("bestNumberedChapters.txt",$nu);
So, it is feasible with PHP to replace all chapter headings containing a '#' with an incremental, numerical value.

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%'