Unable to read the input file content that containing % sign - perl

I am having simple open file input as showing below, which I have no problem reading the content from the input file, but not entirely readable.
open(IN,"<$modelRoot/Local_$project.pm") || die "ERROR\: $!";
while(<IN>)
{
$temp = $_;
chomp($temp);
printf "$temp\n";
}
The content that I printed out looks fine until the point that is a % sign.
This is the original input file content
my %LocalToolData = (
This is the content that I print out, it gives a warning too
Use of uninitialized value in printf at rfinteg_v4.pl line 846, <IN> line 24.
Use of uninitialized value in printf at rfinteg_v4.pl line 847, <IN> line 24.
my 0calToolData = (
Question: How do I read % sign from the input file and at the same time avoid having the warning?

printf does formatting using %, it's a reserved character. The first argument to printf is a template string, not any string.
You should be using print unless you want to make use of that feature.
You should not use printf with any content containing % unless you have corresponding placeholder values for that.
It even says in the documentation:
Don't fall into the trap of using a printf when a simple print would do. The print is more efficient and less error prone.

Related

Perl chomp doesn't remove the newline

I want to read a string from a the first line in a file, then repeat it n repetitions in the console, where n is specified as the second line in the file.
Simple I think?
#!/usr/bin/perl
open(INPUT, "input.txt");
chomp($text = <INPUT>);
chomp($repetitions = <INPUT>);
print $text x $repetitions;
Where input.txt is as follows
Hello
3
I expected the output to be
HelloHelloHello
But words are new line separated despite that chomp is used.
Hello
Hello
Hello
You may try it on the following Perl fiddle CompileOnline
The strange thing is that if the code is as follows:
#!/usr/bin/perl
open(INPUT, "input.txt");
chomp($text = <INPUT>);
print $text x 3;
It will work fine and displays
HelloHelloHello
Am I misunderstanding something, or is it a problem with the online compiler?
You have issues with line endings; chomp removes trailing char/string of $/ from $text and that can vary depending on platform. You can however choose to remove from string any trailing white space using regex,
open(my $INPUT, "<", "input.txt");
my $text = <$INPUT>;
my $repetitions = <$INPUT>;
s/\s+\z// for $text, $repetitions;
print $text x $repetitions;
I'm using an online Perl editor/compiler as mentioned in the initial post http://compileonline.com/execute_perl_online.php
The reason for your output is that string Hello\rHello\rHello\r is differently interpreted in html (\r like line break), while in console \r returns cursor to the beginning of the current line.

Correct use of input file in perl?

database.Win.txt is a file that contains a multiple of 3 lines. The second of every three lines is a number. The code is supposed to print out the three lines (in a new order) on one line separated by tabs, but only if the second line is 1.
Am I, by this code, actually getting the loop to create an array with three lines of database.Win.txt each time it runs through the loop? That's my goal, but I suspect this isn't what the code does, since I get an error saying that the int() function expects a numeric value, and doesn't find one.
while(<database.Win.txt>){
$new_entry[0] = <database.Win.txt>;
$new_entry[1] = <database.Win.txt>;
$new_entry[2] = <database.Win.txt>;
if(int($new_entry[1]) == 1) {
chomp($new_entry);
print "$new_entry[1], \t $new_entry[2], \t $new_entry[0], \n"
}
}
I am a total beginner with Perl. Please explain as simply as possible!
I think you've got a good start on the solution. However, your while reads one line right before the next three lines are read (if those were <$file_handles>). int isn't necessary, but chomp is--before you check the value of $new_entry[1] else there's still a record separator at the end.
Given this, consider the following:
use strict;
use warnings;
my #entries;
open my $fh, '<', 'database.Win.txt' or die $!;
while (1) {
last if eof $fh;
chomp( $entries[$_] = <$fh> ) for 0 .. 2;
if ( $entries[1] == 1 ) {
print +( join "\t", #entries ), "\n";
}
}
close $fh;
Always start with use strict; use warnings. Next, open the file using the three-argument form of open. A while (1) is used here, so three lines at a time can be read within the while loop. Since it's an 'infinite' while loop, the last if eof $fh; gives a way out, viz., if the next file read produces an end of file, it's the last. Right below that is a for loop that effectively does what you did: assign a file line to an array position. Note that chomp is used to remove the record separator during the assignment. The last part is also similar to yours, as it checks whether the second of the three lines is 1, and then the line is printed if it is.
Hope this helps!

How to match an integer after finding a keyword?

I have a text file content as below:
Starting log...
Sample at 10000000
Mode is set to 0
0007F43: CHANGE DETECTED at 290313 line 0 from 00 to 04
0007F46: Mismatched at 290316 line 0
0007F50: Matched occur at 290326 line 1
0007F53: Mismatched at 290336 line 2
0007F56: Matched occur at 290346 line 0
0007F60: Mismatched at 290356 line 2
0007F63: Matched occur at 290366 line 0
Saving log....
DONE!!!
I am running simple perl program as below to get the value for the line contains "Mismatched"
#!/usr/bin/perl
print "Starting perl script\n\n";
open (LOG,"dump.log");
while (<LOG>) {
next if !/Mismatched/;
/at\s+"([^"]+)"/;
print $1,"\n";
}
close(LOG);
print "DONE!!\n";
exit;
but what i get the error message as below, may I know what's wrong with my coding? Is it I miss anything related with chomp()?
Use of uninitialized value in print at test.pl line 9, <LOG> line 5.
Use of uninitialized value in print at test.pl line 9, <LOG> line 7.
Use of uninitialized value in print at test.pl line 9, <LOG> line 9.
DONE!!
And.. is there any suggestion to get the integer (i.e. 290316) after searching the keyword "Mismatched" by using more simple scripting? I just want to get the first value only..
$1 is getting printed even if it does not have anything. It should be in a condition:
print $1,"\n" if (/Mismatched at (\d+)/);
To store all values in an array:
push #arr,$1 if (/Mismatched at (\d+)/);
change regex to:
/at\s+(\d+)/;
You've got answers that show you the correct way to do this, but nothing yet that explains what you were doing wrong. The problem is in your regex.
/at\s+"([^"]+)"/
Let's break it down and see what it's trying to match.
at : the string 'at'
\s+ : one or more whitespace characters
" : a double quote character
([^"]+) : one or more characters that aren't double quote characters
" : a double quote character
So, effectively, you're looking for 'at' followed by a double quoted string. And you're capturing (into $1) the contents of the double quoted string.
But none of your data contains any double quote characters. So there are no double quoted strings. So nothing ever matches and nothing ever gets captured into $1. Which is why you get the 'uninitialised value' error when you try to print $1.
I'd be interested to hear why you thought you wanted to match double quote characters in a piece of text that doesn't contain any of them.
I'd change your script to implement a more modern perl style:
#!/usr/bin/perl
use strict;
use warnings;
print "Starting perl script\n\n";
open my $LOG, '<', 'dump.log' or die $!;
while( <$LOG> ) {
print "$1\n" if /Mismatched at (\d+)/;
}
close $LOG;
print "DONE!!\n";

Perl: How to add a line to sorted text file

I want to add a line to the text file in perl which has data in a sorted form. I have seen examples which show how to append data at the end of the file, but since I want the data in a sorted format.
Please guide me how can it be done.
Basically from what I have tried so far :
(I open a file, grep its content to see if the line which I want to add to the file already exists. If it does than exit else add it to the file (such that the data remains in a sorted format)
open(my $FH, $file) or die "Failed to open file $file \n";
#file_data = <$FH>;
close($FH);
my $line = grep (/$string1/, #file_data);
if($line) {
print "Found\n";
exit(1);
}
else
{
#add the line to the file
print "Not found!\n";
}
Here's an approach using Tie::File so that you can easily treat the file as an array, and List::BinarySearch's bsearch_str_pos function to quickly find the insert point. Once you've found the insert point, you check to see if the element at that point is equal to your insert string. If it's not, splice it into the array. If it is equal, don't splice it in. And finish up with untie so that the file gets closed cleanly.
use strict;
use warnings;
use Tie::File;
use List::BinarySearch qw(bsearch_str_pos);
my $insert_string = 'Whatever!';
my $file = 'something.txt';
my #array;
tie #array, 'Tie::File', $file or die $!;
my $idx = bsearch_str_pos $insert_string, #array;
splice #array, $idx, 0, $insert_string
if $array[$idx] ne $insert_string;
untie #array;
The bsearch_str_pos function from List::BinarySearch is an adaptation of a binary search implementation from Mastering Algorithms with Perl. Its convenient characteristic is that if the search string isn't found, it returns the index point where it could be inserted while maintaining the sort order.
Since you have to read the contents of the text file anyway, how about a different approach?
Read the lines in the file one-by-one, comparing against your target string. If you read a line equal to the target string, then you don't have to do anything.
Otherwise, you eventually read a line 'greater' than your current line according to your sort criteria, or you hit the end of the file. In the former case, you just insert the string at that position, and then copy the rest of the lines. In the latter case, you append the string to the end.
If you don't want to do it that way, you can do a binary search in #file_data to find the spot to add the line without having to examine all of the entries, then insert it into the array before outputting the array to the file.
Here's a simple version that reads from stdin (or filename(s) specified on command line) and appends 'string to append' to the output if it's not found in the input. Outuput is printed on stdout.
#! /usr/bin/perl
$found = 0;
$append='string to append';
while(<>) {
$found = 1 if (m/$append/o);
print
}
print "$append\n" unless ($found);;
Modifying it to edit a file in-place (with perl -i) and taking the append string from the command line would be quite simple.
A 'simple' one-liner to insert a line without using any module could be:
perl -ni -le '$insert="lemon"; $eq=($insert cmp $_); if ($eq == 0){$found++}elsif($eq==-1 && !$found){print$insert} print'
giver a list.txt whose context is:
ananas
apple
banana
pear
the output is:
ananas
apple
banana
lemon
pear
{
local ($^I, #ARGV) = ("", $file); # Enable in-place editing of $file
while (<>) {
# If we found the line exactly, bail out without printing it twice
last if $_ eq $insert;
# If we found the place where the line should be, insert it
if ($_ gt $insert) {
print $insert;
print;
last;
}
print;
}
# We've passed the insertion point, now output the rest of the file
print while <>;
}
Essentially the same answer as pavel's, except with a lot of readability added. Note that $insert should already contain a trailing newline.

Why doesn't die $template->error() show a line number?

In the following short program:
use Template;
my $template = Template->new (INCLUDE_PATH => ".");
$template->process ("non-existent-file")
or die $template->error ();
why doesn't die produce a line number and newline? My output looks like this:
~ 502 $ perl template.pl
file error - non-existent-file: not found ~ 503 $
Template is returning an error object of type Template::Exception. The object has overloaded stringification which applies when the value is printed, but when die looks at the value, it sees a reference and doesn't append the line number and newline. Force the value into a string earlier to fix the problem:
use Template;
my $template = Template->new (INCLUDE_PATH => ".");
$template->process ("non-existent-file")
or die '' . $template->error ();
prints
file error - non-existent-file: not found at scratchpad.pl line 25.
While #Eric's answer does solve the OPs issue, I suggest appending a space instead of pre-pending an empty string.
The reason is that if there are problems in the template, the error will be reported as coming from the template text instead of the line number in the perl file (which is what I want). See this short example:
use Template;
my $template = Template->new();
# Clearly a division by zero bug
$template->process(\"[% 1 / 0 %]")
or die $template->error();
This results in:
undef error - Illegal division by zero at input text line 1.
Which isn't very helpful. I want the perl file location. Instead I suggest:
my $template = Template->new();
$template->process(\"[% 1 / 0 %]")
or die $template->error() . ' ';
which produces:
undef error - Illegal division by zero at input text line 1.
at test.pl line 11.
This way I get the line number in the perl file too. It does look a little ugly, though. (You can stop reading now, if you like...)
An even more correct way would be:
use Template;
my $template = Template->new();
$template->process(\"[% 1 / 0 %]")
or do {
my $error = $template->error . '';
chomp $error;
die $error;
};
which produces this output:
undef error - Illegal division by zero at input text line 1. at t2.pl line 15.
But it is just so verbose and has a weird . in there. I've actually ended up creating:
sub templateError {
my ($template) = #_;
my $string = $template->error->as_string;
chomp $string;
$string =~ s/(line \d+)\.$/$1/;
return $string;
}
...
use Template;
my $template = Template->new ();
$template->process (\"[% 1 / 0 %]")
or die templateError($template);
So that I get this:
undef error - Illegal division by zero at input text line 1 at test.pl line 30.
as well as this for the OPs example:
file error - non-existent-file: not found at test.pl line 31.