How to search and replace string in a file in Perl - perl

The content of my input file is shown below:
abc\**def\ghi**\abc\!!!!!
abc\**4nfiug\frgrefd\gtefe\wf4fs**\abc\df3gwddw
abc\**eg4/refw**\abc\f3
I need to replace whatever string in between abc \ --------------\abc in my input file with ABC\CBA.
I have tried something like below to get the strings that need to be replaced. But I get stuck when I need to use the search and replace:
my $string1 = qr/abc\W+([^a]+)/;
my $string2 = map{/$string1/ => " "} #input_file; # The string that needs to be replaced
my $string3 = 'ABC\CBA' # String in that. I want it to replace to
s/$string2/$string3/g
How can I fix this?

perl -i -pe 's/this/that/g;' file1

A one-liner to fix a file:
perl -plwe 's/abc\\\K.*(?=\\abc)/ABC\\CBA/' input.txt > output.txt
Or as a script:
use strict;
use warnings;
while (<DATA>) {
s/abc\\\K.*(?=\\abc)/ABC\\CBA/;
print;
}
__DATA__
abc\**def\ghi**\abc\!!!!!
abc\**4nfiug\frgrefd\gtefe\wf4fs**\abc\df3gwddw
abc\**eg4/refw**\abc\f3
The \K (keep) escape sequence means these characters will not be removed. Similarly, the look-ahead assertion (?= ... ) will keep that part of the match. I assumed you only wanted to change the characters in between.
Instead of \K one can use a look-behind assertion: (?<=abc\\). As a personal preference, I used \K instead.

#!/usr/bin/perl
use strict;
use warnings;
open my $fh,"<", "tryit.txt" or die $!;
while (my $line = <$fh>) {
$line =~ s/(abc\\)(.*?)(\\abc)/$1ABC\\CBA$3/;
print $line;
}
gives the following with the input data.
abc\ABC\CBA\abc\!!!!!
abc\ABC\CBA\abc\df3gwddw
abc\ABC\CBA\abc\f3

If you do not want the substitution to operate on the default variable $_, you have to use the =~ operator:
#!/usr/bin/perl
use warnings;
use strict;
my #input_file = split /\n/, <<'__EOF__';
abc\**def\ghi**\abc\!!!!!
abc\**4nfiug\frgrefd\gtefe\wf4fs**\abc\df3gwddw
abc\**eg4/refw**\abc\f3
__EOF__
my $pattern = qr/abc\\.*\\abc/; # pattern to be matched
my $string2 = join "\n", #input_file; # the string that need to be replaced
my $string3 = 'ABC\CBA'; # string i that i want it to replace to
$string2 =~ s/$pattern/$string3/g;
print $string2;

To address your comment about replacing text "inplace" in the file directly, you can use the -i switch for a one-liner. In a script, you can perhaps look at using Tie::File, which allows read-write access to lines of a file as (mutable) elements in an array. To copy Mike/TLP's answer:
#!/usr/bin/perl
use strict;
use warnings;
use Tie::File;
tie my #file, "Tie::File", "tryit.txt" or die $!;
# I think you have to use $_ here (done implicitly)
while (#file) {
s/(abc\\)(.*?)(\\abc)/$1ABC\\CBA$3/;
print;
}

Related

Search and replace a string in a file

I'm trying to read contents from an input file, copy only certain lines of code from the file and print in an output file.
Certain lines of code is determined by:
Code name to determine the first line (IP1_NAME or IP2_NAME)
Pattern to determine the last line (END_OF_LIST)
Input file:
IP1_NAME
/ip1name/ip1dir/ //CLIENT_NAME/ip1name/ip1dir
/ip1testname/ip1testdir/ //CLIENT_NAME/ip1testname/ip1testdir
END_OF_LIST
IP2_NAME
/ip2name/ip2dir/ //CLIENT_NAME/ip2name/ip2dir
/ip2testname/ip2testdir/ //CLIENT_NAME/ip2testname/ip2testdir
END_OF_LIST
Output file:
(If IP1_NAME is chosen and the CLIENT_NAME should be replaced by tester_ip)
/ip1name/ip1dir/ //tester_ip/ip1name/ip1dir
/ip1testname/ip1testdir/ //tester_ip/ip1testname/ip1testdir
You could use the following one-liner to pull out the lines between the two patterns:
perl -0777 -ne 'print "$1\n" while /IP1_NAME(.*?)END_OF_LIST/gs' in.txt > out.txt
Where in.txt is your input file and out.txt is the output file.
This use case is actually described in perlfaq6: Regular Expressions.
You can then modify the output file to replace CLIENT_NAME with tester_ip:
perl -pi -e 's/CLIENT_NAME/tester_ip/' y.txt
As a script instead of a one-liner, using the scalar range operator:
#/usr/bin/env perl
use warnings;
use strict;
use autodie;
use feature qw/say/;
process('input.txt', qr/^IP1_NAME$/, qr/^END_OF_LIST$/, 'tester_ip');
sub process {
my ($filename, $startpat, $endpat, $newip) = #_;
open my $file, '<', $filename;
while (my $line = <$file>) {
chomp $line;
if ($line =~ /$startpat/ .. $line =~ /$endpat/) {
next unless $line =~ /^\s/; # Skip the start and lines.
$line =~ s/^\s+//; # Remove indentation
$line =~ s/CLIENT_NAME/$newip/g; # Replace with desired value
say $line;
}
}
}
Running this on your sample input file produces:
/ip1name/ip1dir/ //tester_ip/ip1name/ip1dir
/ip1testname/ip1testdir/ //tester_ip/ip1testname/ip1testdir
I am assuming there is additional stuff in your input file, otherwise we would not have to jump through the hoops with these start and end markers as and we could just say
perl -ne "print if /^ /"
and that would be silly, right ;-)
So, the flipflop has potential problems as I stated in my comment. And while clever, it does not buy you that much in terms of readability or verbosement (verbocity?), since you have to test again anyway in order to not process the marker lines.
As long as there is no exclusive flip flop operator, I would go for a more robust solution.
my $in;
while (<DATA>) {
$in = 1, next if /^IP\d_NAME/;
$in = 0 if /^END_OF_LIST/;
if ( $in )
{
s/CLIENT_NAME/tester_ip/;
print;
}
}
__DATA__
cruft
IP1_NAME
/ip1name/ip1dir/ //CLIENT_NAME/ip1name/ip1dir
/ip1testname/ip1testdir/ //CLIENT_NAME/ip1testname/ip1testdir
END_OF_LIST
more
cruft
IP2_NAME
/ip2name/ip2dir/ //CLIENT_NAME/ip2name/ip2dir
/ip2testname/ip2testdir/ //CLIENT_NAME/ip2testname/ip2testdir
END_OF_LIST
Lore Ipsargh!

Regex characters should be replaced as a text in perl

I have two input files one is *TEX file and *INI.
INI file contains: (User can add if some more patters but this way)
\bra{([^{}]*)} \langle $1|\sprangle
\ket{([^{}]*)} \splangle|$1\rangle
\braket{([^{}]*)} \langle $1\rangle
\set{([^{}]*)} \{$1\}
INPUT file contains:
\bra{ahdhaodoaddo4092039585038}
\ket{su093unfs}
\braket{adlkgnaogoaj}
\set{982509unflksndl}
OUTPUT file should be:
\langle ahdhaodoaddo4092039585038|\sprangle
\splangle|su093unfs\rangle
\langle adlkgnaogoaj\rangle
\{982509unflksndl\}
My code:
use strict;
use warnings;
my $texfile = "brktt.tex"; my $inifile = "brck.ini";
my $texcnt = ""; my $inicnt = "";
readFileinString($texfile,\$texcnt);
readFileinString($inifile,\$inicnt);
my %iniStore = ();
while($inicnt=~m/^([^\t]*)\t([^\t]*)/mgs)
{
my $find = $1; my $rep = $2;
$texcnt=~s/$find/$rep/g;
}
Could someone help me on this one.
Several issues:
readFileinString isn't provided, so we don't know exactly what's going on.
[\t]* matches newlines as well
$1 inside a variable doesn't interpolate in replacement. You need either /e or eval, but such an approach is fragile and dangerous. I tried to find a better way: just split the replacement on $1 and glue the parts back with the string $1 replaced by the real $1.
\ need to be doubled to keep their literal meaning.
And some minor ones:
/s changes what . matches, but you use no dot in the regex.
#! /usr/bin/perl
use warnings;
use strict;
sub readFileinString {
my ($filename, $content) = #_;
open my $fh, '<', $filename or die "$filename: $!";
$$content = do { local $/; <$fh> };
}
my ($texfile, $inifile) = #ARGV;
readFileinString($texfile, \ my $texcnt);
readFileinString($inifile, \ my $inicnt);
while ($inicnt =~ /^([^\t]*)\t(.*)/mg) {
my ($find, $rep) = ($1, $2);
$find =~ s/\\/\\\\/g;
my ($pre, $post) = split /\$1/, $rep;
$texcnt =~ s/$find/$pre$1$post/g;
}
print $texcnt;
You want to evalue the second part of the inifile, so replacing
$texcnt=~s/$find/$rep/g;
with
$texcnt=~s/$find/eval "qq($rep)"/eg;
might do the job, though using eval is usally a bad (and slow) idea.

Split lines in a file and compare with a hostname in perl

I got a file separated by ":"
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
antbexw:x:59000:80::/usr/var/log:/bin/ksh
Each ":" is a separator, thus the aim is to extract position 0 and position 5 to have:
uucp /var/spool/uucp
operator /root
games /usr/games
antbexw /usr/var/log
Then only print line containing antbexw which is in fact the machine hostname.
I have achieved to read the file, split but not the compare against the machine hostname to only print out the antbexw line
antbexw /usr/var/log
Here my script, would you help me out to construct the Condition to print only the line I need? or to propose another method.
#!/usr/bin/perl -w
use strict;
use warnings;
use diagnostics;
use Sys::Hostname;
my $host = hostname;
print "$host\n";
open(my $fh, '<', 'file.txt') or die "cannot open < file.txt: $!";
while (my $line = <$fh>)
{
my#fields = split(":",$line);
print "=*=*=*=*User=*=*=*=* \n$fields[0] \nDirectory $fields[5]\n";
}
close($fh) || warn "close failed: $!";
To compare strings, use the eq operator:
if ($fields[0] eq $host) {
print "=== User ===\n$fields[0]\nDirectory $fields[5]\n";
}
Some unrelated details:
You already have use warnings, no need to specify -w on the shebang line.
The first argument to split is a regex (or a space), so it's better not to use strings.
Your code would be more readable if you indented properly and added some whitespace around punctuation (after commas, before #).
in the mean time i did some other tries and got to this solution:
while (my $line = <$fh>)
{
if($line =~ m/$host/)
{
my#fields = split(":",$line);
print MYFILE"=*=*=*=*SftpUser=* \n$fields[0] \nDirectory $fields[5]\n";
}}

find a match and replace next line in perl

I am working on the perl script and need some help with it. The requirement is, I have to find a lable and once the label is found, I have to replace the word in a line immediately following the label. for Example, if the label is ABC:
ABC:
string to be replaced
some other lines
ABC:
string to be replaced
some other lines
ABC:
string to be replaced
I want to write a script to match the label (ABC) and once the label is found, replace a word in the next line immediately following the label.
Here is my attempt:
open(my $fh, "<", "file1.txt") or die "cannot open file:$!";
while (my $line = <$fh>))
{
next if ($line =~ /ABC/) {
$line =~ s/original_string/replaced_string/;
}
else {
$msg = "pattern not found \n ";
print "$msg";
}
}
Is this correct..? Any help will be greatly appreciated.
The following one-liner will do what you need:
perl -pe '++$x and next if /ABC:/; $x-- and s/old/new/ if $x' inFile > outFile
The code sets a flag and gets the next line if the label is found. If the flag is set, it's unset and the substitution is executed.
Hope this helps!
You're doing this in your loop:
next if ($line =~ /ABC/);
So, you're reading the file, if a line contains ABC anywhere in that line, you skip the line. However, for every other line, you do the replacement. In the end, you're replacing the string on all other lines and printing that out, and your not printing out your labels.
Here's what you said:
I have to read the file until I find a line with the label:
Once the label is found
I have to read the next line and replace the word in a line immediately following the label.
So:
You want to read through a file line-by-line.
If a line matches the label
read the next line
replace the text on the line
Print out the line
Following these directions:
use strict;
use warnings; # Hope you're using strict and warnings
use autodie; # Program automatically dies on failed opens. No need to check
use feature qw(say); # Allows you to use say instead of print
open my $fh, "<", "file1.txt"; # Removed parentheses. It's the latest style
while (my $line = <$fh>) {
chomp $line; # Always do a chomp after a read.
if ( $line eq "ABC:" ) { # Use 'eq' to ensure an exact match for your label
say "$line"; # Print out the current line
$line = <$fh> # Read the next line
$line =~ s/old/new/; # Replace that word
}
say "$line"; # Print the line
}
close $fh; # Might as well do it right
Note that when I use say, I don't have to put the \n on the end of the line. Also, by doing my chomp after my read, I can easily match the label without worrying about the \n on the end.
This is done exactly as you said it should be done, but there are a couple of issues. The first is that when we do $line = <$fh>, there's no guarantee we are really reading a line. What if the file ends right there?
Also, it's bad practice to read a file in multiple places. It makes it harder to maintain the program. To get around this issue, we'll use a flag variable. This allows us to know if the line before was a tag or not:
use strict;
use warnings; # Hope you're using strict and warnings
use autodie; # Program automatically dies on failed opens. No need to check
use feature qw(say); # Allows you to use say instead of print
open my $fh, "<", "file1.txt"; # Removed parentheses. It's the latest style
my $tag_found = 0; # Flag isn't set
while (my $line = <$fh>) {
chomp $line; # Always do a chomp after a read.
if ( $line eq "ABC:" ) { # Use 'eq' to ensure an exact match for your label
$tag_found = 1 # We found the tag!
}
if ( $tag_found ) {
$line =~ s/old/new/; # Replace that word
$tag_found = 0; # Reset our flag variable
}
say "$line"; # Print the line
}
close $fh; # Might as well do it right
Of course, I would prefer to eliminate mysterious values. For example, the tag should be a variable or constant. Same with the string you're searching for and the string you're replacing.
You mentioned this was a word, so your regular expression replacement should probably look like this:
$line =~ s/\b$old_word\b/$new_word/;
The \b mark word boundaries. This way, if you're suppose to replace the word cat with dog, you don't get tripped up on a line that says:
The Jeopardy category is "Say what".
You don't want to change category to dogegory.
Your problem is that reading in a file does not work like that. You're doing it line by line, so when your regex tests true, the line you want to change isn't there yet. You can try adding a boolean variable to check if the last line was a label.
#!/usr/bin/perl;
use strict;
use warnings;
my $found;
my $replacement = "Hello";
while(my $line = <>){
if($line =~ /ABC/){
$found = 1;
next;
}
if($found){
$line =~ s/^.*?$/$replacement/;
$found = 0;
print $line, "\n";
}
}
Or you could use File::Slurp and read the whole file into one string:
use File::Slurp;
$x = read_file( "file.txt" );
$x =~ s/^(ABC:\s*$ [\n\r]{1,2}^.*?)to\sbe/$1to was/mgx;
print $x;
using /m to make the ^ and $ match embedded begin/end of lines
x is to allow the space after the $ - there is probably a better way
Yields:
ABC:
string to was replaced
some other lines
ABC:
string to was replaced
some other lines
ABC:
string to was replaced
Also, relying on perl's in-place editing:
use File::Slurp qw(read_file write_file);
use strict;
use warnings;
my $file = 'fakefile1.txt';
# Initialize Fake data
write_file($file, <DATA>);
# Enclosed is the actual code that you're looking for.
# Everything else is just for testing:
{
local #ARGV = $file;
local $^I = '.bac';
while (<>) {
print;
if (/ABC/ && !eof) {
$_ = <>;
s/.*/replaced string/;
print;
}
}
unlink "$file$^I";
}
# Compare new file.
print read_file($file);
1;
__DATA__
ABC:
string to be replaced
some other lines
ABC:
string to be replaced
some other lines
ABC:
string to be replaced
ABC:
outputs
ABC:
replaced string
some other lines
ABC:
replaced string
some other lines
ABC:
replaced string
ABC:

How to match multiline data in perl

This question refers to
How to replace text using greedy approach in sed?
I have to match multiline data in file and need to replace them with some other text using perl.
cat file
<strong>ABC
</strong>
perl script: code.pl
#!/bin/perl
open(fh, $ARGV[0]) or die "could not open file\n";
while($input = <fh>)
{
if($input =~/<strong>(.*?)\n(\s)*<\/strong>/)
{
print($1,"\n");
}
}
close(fh);
perl code.pl file
Output: No output
How to solve above pblm.
Regards
use File::Slurp qw( read_file );
my $string = read_file( $ARGV[0] );
$string =~ s/\<strong>(.*?)<\/strong>/<b>${1}<\/b>/gs;
print $string;
This example uses the File::Slurp module to read in the entire file at once.
It then uses a regex with the g and s modifiers. The s allows .*? to match newline characters. The g makes the search global. Global meaning it will find all matches in the given string. Without the g only the first instance would be replaced. If you want your search to be case insensitive, you can use the i regex modifier.
The ${1} is a back-reference to the match in parentheses.
This example produces:
<b>ABC
</b>