So I'm trying to read in a config. file in Perl. The config file uses a trailing backslash to indicate a line continuation. For instance, the file might look like this:
=== somefile ===
foo=bar
x=this\
is\
a\
multiline statement.
I have code that reads in the file, and then processes the trailing backslash(es) to concatenate the lines. However, it looks like Perl already did it for me. For instance, the code:
open(fh, 'somefile');
#data = <fh>;
print join('', #data);
prints:
foo=bar
x=thisisamultiline statement
Lo and behold, the '#data = ;' statement appears to have already handled the trailing backslash!
Is this defined behavior in Perl?
I have no idea what you are seeing, but that is not valid Perl code and that is not a behavior in Perl. Here is some Perl code that does what you want:
#!/usr/bin/perl
use strict;
use warnings;
while (my $line = <DATA>) {
#collapse lines that end with \
while ($line =~ s/\\\n//) {
$line .= <DATA>;
}
print $line;
}
__DATA__
foo=bar
x=this\
is\
a\
multiline statement.
Note: If you are typing the file in on the commandline like this:
perl -ple 1 <<!
foo\
bar
baz
!
Then you are seeing the effect of your shell, not Perl. Consider the following counterexample:
printf 'foo\\\nbar\nbaz\n' | perl -ple 1
My ConfigReader::Simple module supports continuation lines in config files, and should handle your config if it's the format in your question.
If you want to see how to do it yourself, check out the source for that module. It's not a lot of code.
I don't know what exactly you are doing, but the code you gave us doesn't even run:
=> cat z.pl
#!/usr/bin/perl
fh = open('somefile', 'r');
#data = <fh>;
print join('', #data);
=> perl z.pl
Can't modify constant item in scalar assignment at z.pl line 2, near ");"
Execution of z.pl aborted due to compilation errors.
And if I change the snippet to be actual perl:
=> cat z.pl
#!/usr/bin/perl
open my $fh, '<', 'somefile';
my #data = <$fh>;
print join('', #data);
it clearly doesn't mangle the data:
=> perl z.pl
foo=bar
x=this\
is\
a\
multiline statement.
Related
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!
I'm trying to create a script that will generate perl code from a template, and I'm having trouble understanding the error being thrown and why my workaround fixes it.
This example is contrived, but it demonstrates the issue:
use strict;
use warnings;
my $name = shift; # from #ARGV
my $file = sprintf "%s.pm", $name;
open my $fh, ">", $file
or die "error: open(>, '$file'): $!";
print $fh << "MODULE";
package $name;
#
# blah blah
#
use strict;
use warnings;
require Exporter;
our \#ISA = qw| Exporter |;
our \#EXPORT = qw| |; # automatic exports
our \#EXPORT_OK = qw| |; # on-demand exports
# CODE
1;
MODULE
close $fh;
When running this script, I get the following error:
$ perl script.pl Foo
Invalid version format (non-numeric data) at script.pl line 11, near "package "
syntax error at script.pl line 11, near "package $name"
BEGIN not safe after errors--compilation aborted at script.pl line 17.
Originally this script was just printing to stdout instead of writing to file -- no errors thrown. After adding the file handling and receiving this error, I then tried to just use a bare filehandle -- again no errors thrown.
So if I merely replace "$fh" with "FH" everywhere, the script works as expected. What is it about the lexical filehandle causing this to choke?
There should be no space after << marking the here document, so
print $fh << "MODULE";
should be
print $fh <<"MODULE";
or more neatly
print $fh <<MODULE;
or perhaps
print $fh (<< "MODULE");
As it is the << is being treated as a left-shift operator and Perl continues to try to compile the package statement. Finding no valid package name it tries to use $nameas a version number, and complains because it isn't one
Perl is an ambiguous language. It means that it's not always clear how it should be parsed. In some situations, perl has to guess how to parse something. There's a grammatical ambiguity in
print $fh << "MODULE";
Specifically, the << can be a left shift operator or the start of here-doc.
There are two paths you can follow to address the issue.
You can remove the ambiguity:
print $fh +<< "MODULE";
print $fh (<< "MODULE");
print { $fh } << "MODULE";
$fh->print(<< "MODULE");
You can trick perl into guessing correctly:
print $fh <<"MODULE";
Note that print $fh +<< "MODULE"; introduces an alternate ambiguity. Is + a binary or unary + operator? Thankfully, it's interpreted as a unary-+ as desired.
By the way, <<"MODULE" can be shortened to <<MODULE.
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//;
I'm trying to remove one line from a text file. Instead, what I have wipes out the entire file. Can someone point out the error?
removeReservation("john");
sub removeTime() {
my $name = shift;
open( FILE, "<times.txt" );
#LINES = <FILE>;
close(FILE);
open( FILE, ">times.txt" );
foreach $LINE (#LINES) {
print NEWLIST $LINE unless ( $LINE =~ m/$name/ );
}
close(FILE);
print("Reservation successfully removed.<br/>");
}
Sample times.txt file:
04/15/2012&08:00:00&bob
04/15/2012&08:00:00&john
perl -ni -e 'print unless /whatever/' filename
Oalder's answer is correct, but he should have tested whether the open statements succeeded or not. If the file times.txt doesn't exist, your program would continue on its merry way without a word of warning that something terrible has happened.
Same program as oalders' but:
Testing the results of the open.
Using the three part open statement which is more goof proof. If your file name begins with > or |, your program will fail with the old two part syntax.
Not using global file handles -- especially in subroutines. File handles are normally global in scope. Imagine if I had a file handle named FILE in my main program, and I was reading it, I called this subroutine. That would cause problems. Use locally scoped file handle names.
Variable names should be in lowercase. Constants are all uppercase. It's just a standard that developed over time. Not following it can cause confusion.
Since oalders put the program in a subroutine, you should pass the name of your file in the subroutine as well...
Here's the program:
#!/usr/bin/env perl
use strict;
use warnings;
removeTime( "john", "times.txt" );
sub removeTime {
my $name = shift;
my $time_file = shift;
if (not defined $time_file) {
#Make sure that the $time_file was passed in too.
die qq(Name of Time file not passed to subroutine "removeTime"\n);
}
# Read file into an array for processing
open( my $read_fh, "<", $time_file )
or die qq(Can't open file "$time_file" for reading: $!\n);
my #file_lines = <$read_fh>;
close( $read_fh );
# Rewrite file with the line removed
open( my $write_fh, ">", $time_file )
or die qq(Can't open file "$time_file" for writing: $!\n);
foreach my $line ( #file_lines ) {
print {$write_fh} $line unless ( $line =~ /$name/ );
}
close( $write_fh );
print( "Reservation successfully removed.<br/>" );
}
It looks like you're printing to a filehandle which you have not yet defined. At least you haven't defined it in your sample code. If you enable strict and warnings, you'll get the following message:
Name "main::NEWLIST" used only once: possible typo at remove.pl line 16.
print NEWLIST $LINE unless ($LINE =~ m/$name/);
This code should work for you:
#!/usr/bin/env perl
use strict;
use warnings;
removeTime( "john" );
sub removeTime {
my $name = shift;
open( FILE, "<times.txt" );
my #LINES = <FILE>;
close( FILE );
open( FILE, ">times.txt" );
foreach my $LINE ( #LINES ) {
print FILE $LINE unless ( $LINE =~ m/$name/ );
}
close( FILE );
print( "Reservation successfully removed.<br/>" );
}
A couple of other things to note:
1) Your sample code calls removeReservation() when you mean removeTime()
2) You don't require the round brackets in your subroutine definition unless your intention is to use prototypes. See my example above.
This is in the FAQ.
How do I change, delete, or insert a line in a file, or append to the beginning of a file?
It's always worth checking the FAQ.
Just in case someone wants to remove all lines from a file.
For example, a file (4th line is empty; 5th line has 3 spaces):
t e st1
test2 a
e
aa
bb bb
test3a
cc
To remove lines which match a pattern some might use:
# Remove all lines with a character 'a'
perl -pi -e 's/.*a.*//' fileTest && sed -i '/^$/d' fileTest;
The result:
t e st1
e
bb bb
cc
Related:
perl -h
# -p assume loop like -n but print line also, like sed
# -i[extension] edit <> files in place (makes backup if extension supplied)
# -e program one line of program (several -e's allowed, omit programfile)
sed -h
# -i[SUFFIX], --in-place[=SUFFIX]
# edit files in place (makes backup if SUFFIX supplied)
Reference 1, Reference 2
I have a file containing the following content 1000 line in the following format:
abc def ghi gkl
How can I write a Perl script to print only the first and the third fields?
abc ghi
perl -lane 'print "#F[0,2]"' file
If no answer is good for you yet, I'll try to get the bounty ;-)
#!/usr/bin/perl
# Lines beginning with a hash (#) denote optional comments,
# except the first line, which is required,
# see http://en.wikipedia.org/wiki/Shebang_(Unix)
use strict; # http://perldoc.perl.org/strict.html
use warnings; # http://perldoc.perl.org/warnings.html
# http://perldoc.perl.org/perlsyn.html#Compound-Statements
# http://perldoc.perl.org/functions/defined.html
# http://perldoc.perl.org/functions/my.html
# http://perldoc.perl.org/perldata.html
# http://perldoc.perl.org/perlop.html#I%2fO-Operators
while (defined(my $line = <>)) {
# http://perldoc.perl.org/functions/split.html
my #chunks = split ' ', $line;
# http://perldoc.perl.org/functions/print.html
# http://perldoc.perl.org/perlop.html#Quote-Like-Operators
print "$chunks[0] $chunks[2]\n";
}
To run this script, given that its name is script.pl, invoke it as
perl script.pl FILE
where FILE is the file that you want to parse. See also http://perldoc.perl.org/perlrun.html. Good luck! ;-)
That's really kind of a waste for something as powerful as perl, since you can do the same thing in one trivial line of awk.
awk '{ print $1 $3 }'
while ( <> ) {
my #fields = split;
print "#fields[0,2]\n";
}
and just for variety, on Windows:
C:\Temp> perl -pale "$_=qq{#F[0,2]}"
and on Unix
$ perl -pale '$_="#F[0,2]"'
As perl one-liner:
perl -ane 'print "#F[0,2]\n"' file
Or as executable script:
#!/usr/bin/perl
use strict;
use warnings;
open my $fh, '<', 'file' or die "Can't open file: $!\n";
while (<$fh>) {
my #fields = split;
print "#fields[0,2]\n";
}
Execute the script like this:
perl script.pl
or
chmod 755 script.pl
./script.pl
I'm sure I shouldn't get the bounty since the question asks for the result to be given in perl, but anyway:
In bash/ksh/ash/etc:
cut -d " " -f 1,3 "file"
In Windows/DOS:
for /f "tokens=1-4 delims= " %i in (file) do (echo %i %k)
Advantages: like others said, no need to learn Pearl, Awk, nothing, just knowing some tools. The result of both calls can be saved to the disk by using the ">" and the ">>" operator.
while(<>){
chomp;
#s = split ;
print "$s[0] $s[2]\n";
}
please start to go through the documentation as well
#!/usr/bin/env perl
open my$F, "<", "file" or die;
print join(" ",(split)[0,2])."\n" while(<$F>);
close $F
One easy way is:
(split)[0,2]
Example:
$_ = 'abc def ghi gkl';
print( (split)[0,2] , "\n");
print( join(" ", (split)[0,2] ),"\n");
Command line:
perl -e '$_="abc def ghi gkl";print(join(" ",(split)[0,2]),"\n")'