unexpected result of string concatenation - perl

I have written following code to read from a file list of filenames on each line and append some data to it.
open my $info,'<',"abc.txt";
while(<$info>){
chomp $_;
my $filename = "temp/".$_.".xml";
print"\n";
print $filename;
print "\n";
}
close $info;
Content of abc.txt
file1
file2
file3
Now I was expecting my code to give me following output
temp/file1.xml
temp/file2.xml
temp/file3.xml
but instead I am getting output
.xml/file1
.xml/file2
.xml/file3

Your file has windows line endings \r\n. chomp removes the \n (Newline) but leaves the \r (Carriage return). Using Data::Dumper with Useqq you can examine the variable:
use Data::Dumper;
$Data::Dumper::Useqq = 1;
print Dumper($filename);
This should output something like:
$VAR1 = "temp/file1\r.xml";
When printed normally, it will output temp/file, move the cursor to the start of the line and overwrite temp with .xml.
To remove the line endings, replace chomp with:
s/\r\n$//;
or as noted by #Borodin:
s/\s+\z//;
which "has the advantage of working for any line terminator, as well as removing trailing whitespace, which is commonly unwanted"

As has been stated, your file has windows line endings.
The following self-contained example demonstrates what you're working with:
use strict;
use warnings;
open my $info, '<', \ "file1\r\nfile2\r\nfile3\r\n";
while(<$info>){
chomp;
my $filename = "temp/".$_.".xml";
use Data::Dump;
dd $filename;
print $filename, "\n";
}
Outputs:
"temp/file1\r.xml"
.xml/file1
"temp/file2\r.xml"
.xml/file2
"temp/file3\r.xml"
.xml/file3
Now there are two ways to fix this
Adjust the $INPUT_RECORD_SEPARATOR to that of your file.
local $/ = "\r\n";
while(<$info>){
chomp;
chomp automatically works on the value of $/.
Use a regex instead of chomp to strip the line endings
Since perl 5.10 there is a escape code \R which stands for a generic newline.
while(<$info>){
s/\R//;
Alternatively, you could just strip all trailing spacing to be even more sure of covering your bases:
while(<$info>){
s/\s+\z//;

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!

Is it wrong to print $/ instead of \n?

Perl's documentation says that $/ is:
The input record separator, newline by default. This influences Perl's
idea of what a "line" is.
So, is it basically wrong to:
print STDERR $var, $/;
instead of:
print STDERR "$var\n";
?
What could go wrong if I do the former?
Perhaps you are looking for the output record separator instead?
perldoc perlvar:
IO::Handle->output_record_separator( EXPR )
$OUTPUT_RECORD_SEPARATOR
$ORS
$\
The output record separator for the print operator.
If defined, this value is printed after the last of print's arguments. Default is "undef".
You cannot call "output_record_separator()" on a handle, only as a static method. See IO::Handle.
Mnemonic: you set "$\" instead of adding "\n" at the end of the print. Also, it's just like $/, but it's what you get "back" from Perl.
For example,
$\ = $/;
print STDERR $var;
$/ is LF (U+000A) by default. This is the same character produced by "\n"[1]. So unless you changed $/, $/ and "\n" are equivalent. If you did change $/, then only you know why, and therefore only you know whether $/ or "\n" is more appropriate.
On ancient MacOS boxes, $/'s default was CR (U+000D), but that's also what "\n" produced there.
You need output record separator $\ as xxfelixxx has answered.
$/ as you read is input record separator. Manipulating it can affect how Perl reads the file data you've provided. For example:
open my $fh, "<", $filename or die $!;
local $/; # enable localized slurp mode
my $content = <$fh>;
close $fh;
The above causes whole content of file to slurp in scalar $content because we had reset $/.
Consider the below code:
#!/usr/bin/perl
use strict;
use warnings;
my $content;
{local $/; $content = <DATA>}
print "Content is $content";
__DATA__
line 1
line 2
line 3
Output:
Content is line 1
line 2
line 3
But if you do not reset $/, like in below code:
#!/usr/bin/perl
use strict;
use warnings;
my $content = <DATA>;
print "Content is $content";
__DATA__
line 1
line 2
line 3
Output will be Content is line 1.
This is because the input record separator was set to newline and it returned after first line.

Need to replace value from one file to another file using perl

I am writing a program using perl which read a value from one file and replace this value in other file. Program runs successfully, but value didn't get replaced. Please suggest me where is the error.
use strict;
use warnings;
open(file1,"address0.txt") or die "Cannot open file.\n";
my $value;
$value=<file1>;
system("perl -p -i.bak -e 's/add/$value/ig' rough.sp");
Here the value which I want to replace exists in address0.txt file. It is a single value 1. I want to place this value in place of add in other file rough.sp.
My rough.sp looks like
Vdd 1 0 add
My address0.txt looks like
1
So output should be like
Vdd 1 0 1
Please help me out. Thanks in advance
Assuming that there is a 1:1 relationship between lines in adress0.txt and rough.sp, you can proceed like this:
use strict;
use warnings;
my ($curline_1,$curline_2);
open(file1, "address0.txt") or die "Cannot open file.\n";
open(file2, "rough.sp") or die "Cannot open file.\n";
open(file3, ">out.sp") or die "Cannot open file.\n";
while (<file1>) {
$curline_1 = $_;
chomp($curline_1);
$curline_2 = <file2>;
$curline_2 =~ s/ add/ $curline_1/;
print file3 $curline_2;
}
close(file1);
close(file2);
close(file3);
exit(0);
Explanation:
The code iterates through the lines of your input files in parallel. Note that the lines read include the line terminator. Line contents from the 'address' file are taken as replacement values fpr the add literal in your .sp file. Line terminators from the 'address' file are eliminated to avoid introducing additional newlines.
Addendum:
An extension for multi-replacements might look like this:
$curline_1 = $_;
chomp($curline_1);
my #parts = split(/ +/, $curline_1); # splits the line from address0.txt into an array of strings made up of contiguous non-whitespace chars
$curline_2 = <file2>;
$curline_2 =~ s/ add/ $parts[0]/;
$curline_2 =~ s/ sub/ $parts[1]/;
# ...

Any sequence of \r or \n as a line separator?

I'm maintaining a Perl script (Perl 5.10 on Linux) which needs to process a file line-by-line while being as flexible as possible regarding line separator characters. Any sequence of newlines and/or carriage return characters should mark the end of a line. Blank lines in the file aren't significant. For example, all of these should yield two lines:
FOO\nBAR FOO\rBAR
FOO\r\nBAR FOO\n\rBAR
FOO\r\n\r\r\r\n\n\nBAR
It doesn't look like it's possible to get this behavior through PerlIO or by setting $/. The files aren't large, so I suppose I could just read the whole file into memory and then split it with a regular expression. Is there are more clever way to do this in Perl?
Just slurp the file and use split:
use strict;
use warnings;
use autodie;
use Data::Dump;
my #data = (
"FOO\nBAR",
"FOO\rBAR",
"FOO\r\nBAR",
"FOO\n\rBAR",
"FOO\r\n\r\r\r\n\n\nBAR",
);
for my $filedata (#data) {
dd $filedata;
open my $fh, "<", \"$filedata";
local $/;
for my $line (split /[\n\r]+/, <$fh>) {
print " $line\n";
}
}
Outputs:
"FOO\nBAR"
FOO
BAR
"FOO\rBAR"
FOO
BAR
"FOO\r\nBAR"
FOO
BAR
"FOO\n\rBAR"
FOO
BAR
"FOO\r\n\r\r\r\n\n\nBAR"
FOO
BAR

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: