perl regex: searching thru entire line of file - regex

I'm a regex newbie, and I am trying to use a regex to return a list of dates from a text file. The dates are in mm/dd/yy format, so for years it would be '55' for '1955', for example. I am trying to return all entries from years'50' to '99'.
I believe the problem I am having is that once my regex finds a match on a line, it stops right there and jumps to the next line without checking the rest of the line. For example, I have the dates 12/12/12, 10/10/57, 10/09/66 all on one line in the text file, and it only returns 10/10/57.
Here is my code thus far. Any hints or tips? Thank you
open INPUT, "< dates.txt" or die "Can't open input file: $!";
while (my $line = <INPUT>){
if ($line =~ /(\d\d)\/(\d\d)\/([5-9][0-9])/g){
print "$&\n" ;
}
}

A few points about your code
You must always use strict and use warnings 'all' at the top of all your Perl programs
You should prefer lexical file handles and the three-parameter form of open
If your regex pattern contains literal slashes then it is clearest to use a non-standard delimiter so that they don't need to be escaped
Although recent releases of Perl have fixed the issue, there used to be a significant performance hit when using $&, so it is best to avoid it, at least for now. Put capturing parentheses around the whole pattern and use $1 instead
This program will do as you ask
use strict;
use warnings 'all';
open my $fh, '<', 'dates.txt' or die "Can't open input file: $!";
while ( <$fh> ) {
print $1, "\n" while m{(\d\d/\d\d/[5-9][0-9])}g
}
output
10/10/57
10/09/66

You are printing $& which gets updated whenever any new match is encountered.
But in this case you need to store the all the previous matches and the updated one too, so you can use array for storing all the matches.
while(<$fh>) {
#dates = $_ =~ /(\d\d)\/(\d\d)\/([5-9][0-9])/g;
print "#dates\n" if(#dates);
}

You just need to change the 'if' to a 'while' and the regex will take up where it left off;
open INPUT, "< a.dat" or die "Can't open input file: $!";
while (my $line = <INPUT>){
while ($line =~ /(\d\d)\/(\d\d)\/([5-9][0-9])/g){
print "$&\n" ;
}
}
# Output given line above
# 10/10/57
# 10/09/66
You could also capture the whole of the date into one capture variable and use a different regex delimiter to save escaping the slashes:
while ($line =~ m|(\d\d/\d\d/[5-9]\d)|g) {
print "$1\n" ;
}
...but that's a matter of taste, perhaps.

You can use map also to get year range 50 to 99 and store in array
open INPUT, "< dates.txt" or die "Can't open input file: $!";
#as = map{$_ =~ m/\d\d\/\d\d\/[5-9][0-9]/g} <INPUT>;
$, = "\n";
print #as;

Another way around it is removing the dates you don't want.
$line =~ s/\d\d\/\d\d\/[0-4]\d//g;
print $line;

Related

use of uninitialized value $line in string ne

I am writing a program of pattern matching in perl ..but getting a error ..I have seen all the earlier posts regarding this matter but didn't find the solution...As I am new to perl So I am not getting exactly what is this error all about..
use of uninitialized value $line in string ne at line .. and in line ...
I am attaching here a perl file
use strict;
use warnings;
my $line = "";
open(OUTFILE, ">output.txt") or die ("cannot open file.\n");
if(open(file1,"match.txt") or die "Cannot open file.\n"){
$line = <file1>;
while ($line ne "") {
if (defined($line) && (line =~ m/\sregion\s/i)) {
print OUTFILE ("$line")};
$line = <file1>; # Problem Here
if (defined($line) && ($line =~ /\svth\s/)) {
print OUTFILE ("$line")
};
$line = <file1>; # Problem Here
}
}
My match.txt file contain this type of data..
Some text here
region Saturati Saturati Linear Saturati Saturati
id -2.1741m -2.1741m -4.3482m 2.1741m 2.1741m
vth -353.9140m -353.9141m -379.2704m 419.8747m 419.8745m
Some text here
Please solve the problem....thanks
The reason you are seeing those errors is that the variable $line contains undef. The reason it contains undef is that you assigned it a value from readline() (the <file1>) after the file had reached its end eof. This is described in perldoc -f readline:
In scalar context, each
call reads and returns the next line until end-of-file is
reached, whereupon the subsequent call returns "undef".
The reason you are encountering this error is that you are not using a traditional method of reading a file. Usually, you would do this to read a file:
while (<$fh>) {
...
}
This will iterate over all the lines in the file until it reaches end of file, after which, as you now know, the readline returns undef and the while loop is exited.
This also means that you do not have to check every other line whether $line is defined or empty. Moreover, you can combine your regexes into one, and generally remove a lot of redundant code:
while (<>) {
if (/\b(?:region|vth)\b/i) {
print;
}
}
This is the core of the functionality you are after, and I am using some Perl idioms here: the diamond operator <> will read from the file names you give the script as argument, or from STDIN if no arguments are given. Many built-in functions use the $_ variable as default if no argument is given, which is what print does, and the while loop condition.
You might also note that I use word boundary \b instead of whitespace \s in the regex, and also use alternation | with non-capturing parentheses (?:...), meaning it can match one of those strings.
With this simplified script, you can do:
perl script.pl match.txt > output.txt
To provide your file names.
If you can't read anything, your string will come back undefined... which is why you are seeing that message.
Also, probably better to check that you open input file first before creating an output file at all, so something like this:
open(INFILE, "<match.txt") or die "Cannot open input file";
open(OUTFILE, ">output.txt") or die "cannot open output file";
my $line;
while($line = <INFILE>){
...
}
Perl will end the loop if $line is undefined or an empty string.
From the looks of it, it seems like you're trying to go through the match file and print all the lines that match region or vth to output.txt.
I simplified the code for you to do this:
use strict;
use warnings;
open(my $out_fh, ">", "output.txt") || die ("Cannot open file.\n");
open(my $file1, "<", "match.txt") || die ("Cannot open file.\n");
while( <$file1> ) {
if ( /\s(region|vth)\s/i) {
print $out_fh $_;
}
}
This question goes into more detail about checking whether a variable is defined or empty: In Perl, how can I concisely check if a $variable is defined and contains a non zero length string?
Here is more information about opening files: What's the best way to open and read a file in Perl?

open file for each element in array and check against a regex Perl

I have an array filled with 4 digit numbers (#nums) that correspond
to conf files which use the numbers as the file name, like so: 0000.conf
I am reading a file foreach element in the array and checking
the file for a pattern like this :
use strict;
use warnings;
foreach my $num (#nums) {
open my $fh, "<", "$num.conf"
or warn "cannot open $num.conf : $!";
while(<$fh>) {
if (/^SomePattern=(.+)/) {
print "$num : $1\n";
}
}
}
I am extracting the part of the pattern I want using () and the
special var $1.
This seems to be working except it only prints the results of the last file
that is opened, instead of printing the results each time the foreach loop
passes and opens a file, which is what I expected.
I am still learning Perl, so any detailed explanations of what I missing here
will be greatly appreciated.
use v5.16;
use strict;
use warnings;
my #nums = qw/ 0000 0200 /;
for my $num (#nums){
open my $fh, "<", "$num.conf" or die;
while (<$fh>) {
chomp;
if( /^somePattern=(.+)/ ) {
say "$1";
}
}
close $fh;
}
this seems to be working for me..You are missing the close $fh; in your code, maybe that is wrong. Secondly, maybe only one of your files matches you regex, check the content for typos. I myself don't use foreach, maybe you are missing 'my' before $num. Depending of your regex, it might be useful to strim newline characters from the end of line with 'chomp'.
Your code is excellent for a learner.
The problem is that, using "$num.conf", you are trying to open files named 0.conf etc. instead of 0000.conf.
You should also use the value of $! in your die string so that you know why the open failed.
Write this instead
my $file = sprintf '%04d.conf', $num;
open my $fh, '<', $file or die "Unable to open '$file': $!";
I have left my previous answer as it may be useful to someone. But I missed your opening "I have an array filled with 4 digit numbers".
Doubtless you are populating your array wrongly.
If you are reading from a file then most usually you have forgotten to chomp the newline from the end of the lines you have read.
You may also have non-printable characters (usually tabs or spaces) in each number.
You should use Data::Dumper or the better and more recent Data::Dump to reveal the contents of your array.

Perl Regex Match Text String and Extract Following Number

I have a giant text data file (~100MB) that is a concatenation of a bunch of data files with various header information then some columns of data. Here's the problem. I want to extract a particular number from the header info before each of these data sets and then append that to another column in the data (and write out that data to a different file).
The header info that I want is of the format ex: BGA 1
Where what I want for that extra data column is the # after word BGA. It will be a number between 1 and maybe 20000. I can write the regex to pull the word BGA, but I don't seem to be able to figure out how to just get the digit after it.
To add EXTRA fun, that text "BGA 1" is repeated in each data section TWICE.
Here's what I have so far, which actually doesn't work... I want it to at least print "BGA" everytime it encounters the word BGA, but it prints nothing.... Any help would be appreciated.
#!/usr/bin/perl
use strict;
use warnings;
my $file = 'alldata.txt';
open my $info, $file or die "Could not open $file: $!";
$_="";
while(my $line = <$info>){
if ($line eq "/BGA/"){
print <>,"\n";
}
}
close $file;
if ($line =~ /BGA\s(\d+)/){
#your code
print "BGA number $1 \n";
#your code
}
And $1 variable will have the number you want
If there is more than one BGA per line, you'll need to allow the regex to match more than once per line:
while (my $line = <$info>) {
while ( $line =~ /BGA\s(\d+)/g ) {
print "$1\n";
}
}
This should print out all the BGA numbers as a single column. Without any further information it's hard to answer this any better.
First, a 100 MB file is not giant. Don't be so defeatist. You could even slurp it into memory:
Let's look at the few critical places in your code:
while(my $line = <$info>) {
if ($line eq "/BGA/") {
Your condition $line eq "/BGA/" tests if the line literally consists of the string "/BGA/". But, that can never be true for the line with at least have the input record separator, i.e. the contents of $/ at the end because you did not chomp it. In any case, what you want is to match lines that contain "BGA" anywhere and the proper Perl syntax to do that is
if ($line =~ /BGA/) {
Now, once you fix that, you are going to run into a problem with the following statement:
print <>,"\n";
What you really want is print $line;. The diamond operator, <>, in list context is going to try to slurp from STDIN or any files specified as arguments on the command line. Not a good idea.
Others have pointed out how to match the string "BGA" followed by a digit. For better answers, you are going to need to show examples of input and expected output.

Perl multiple line matching while reading from file, line by line

Lets say I open a file like this:
#!/usr/bin/perl
open FILE, "8882099";
while ($line = <FILE>) {
if ($line =~ /accepted by(.*?)\./s) {
print "accepted by: $1";
}
}
the problem is the regex is working, but since the file is read line by line, how should I go about matching this string which continues on to a new line?
Thank you
It's often simplest just to read the entire file at once.
my $file;
{
local $/;
$file = <$fh>;
}
First of all, you must always use strict and use warnings at the top of every program. That way simple mistakes you would otherwise overlook will be pointed out to you.
Secondly, you should use lexical file names, the three-parameter form of open, and always check the status of every open call.
To solve you problem, you should simply look for a line that contains the prefix accepted by, and then append lines to the string you have found until you see the complete string match. It is also better to use an explicit [^.]+ than the non-greedy .*? to avoid backtracking.
Note that you should reinstate the file open that I have commented out, and remove the assignment to $file, as I have written the program this way for test purposes.
This solution will have a problem if accepted by is split over multiple lines. If you expect this then something slightly different will have to be coded.
use strict;
use warnings;
# open my $file, '<', '8882099' or die $!;
my $file = \*DATA;
my $line;
while ($line = <$file>) {
if ($line =~ /accepted by/) {
$line .= <$file> until $line =~ /accepted by\s*([^.]*)\./;
print "accepted by: $1\n";
}
}
__DATA__
accepted by Tim.
accepted by The
Financial
Director.
Today
output
accepted by: Tim
accepted by: The
Financial
Director

help with perl code to parse a file

I am new to Perl and have a question about the syntax. I received this code for parsing a file containing specific information. I was wondering what the if (/DID/) part of the subroutine get_number is doing? Is this leveraging regular expressions? I'm not quite sure because regular-expression matches look like $_ =~ /some expression/. Finally, is the while loop in the get_number subroutine necessary?
#!/usr/bin/env perl
use Scalar::Util qw/ looks_like_number /;
use WWW::Mechanize;
# store the name of all the OCR file names in an array
my #file_list=qw{
blah.txt
};
# set the scalar index to zero
my $file_index=0;
# open the file titled 'outputfile.txt' and write to it
# (or indicate that the file can't be opened)
open(OUT_FILE, '>', 'outputfile.txt')
or die "Can't open output file\n";
while($file_index < 1){
# open the OCR file and store it in the filehandle IN_FILE
open(IN_FILE, '<', "$file_list[$file_index]")
or die "Can't read source file!\n";
print "Processing file $file_list[$file_index]\n";
while(<IN_FILE>){
my $citing_pat=get_number();
get_country($citing_pat);
}
$file_index=$file_index+1;
}
close IN_FILE;
close OUT_FILE;
The definition of get_number is below.
sub get_number {
while(<IN_FILE>){
if(/DID/){
my #fields=split / /;
chomp($fields[3]);
if($fields[3] !~ /\D/){
return $fields[3];
}
}
}
}
Perl has a variable $_ that is sort of the default dumping ground for a lot of things.
In get_number, while(<IN_FILE>){ is reading a line into $_, and the next line is checking if $_ matches the regular expression DID.
It's also common to see chomp; which also operates on $_ when no argument is given.
In that case, if (/DID/) by default searches the $_ variable, so it is correct. However, it is a rather loose regex, IMO.
The while loop in the sub may be necessary, it depends on what your input looks like. You should be aware that the two while loops will cause some lines to get completely skipped.
The while loop in the main program will take one line, and do nothing with it. Basically, this means that the first line in the file, and every line directly following a matching line (e.g. a line that contains "DID" and the 4th field is a number), will also be discarded.
In order to answer that question properly, we'd need to see the input file.
There are a number of issues with this code, and if it works as intended, it's probably due to a healthy amount of luck.
Below is a cleaned up version of the code. I kept the modules in, since I do not know if they are used elsewhere. I also kept the output file, since it might be used somewhere you have not shown. This code will not attempt to use undefined values for get_country, and will simply do nothing if it does not find a suitable number.
use warnings;
use strict;
use Scalar::Util qw/ looks_like_number /;
use WWW::Mechanize;
my #file_list=qw{ blah.txt };
open(my $outfile, '>', 'outputfile.txt') or die "Can't open output file: $!";
for my $file (#file_list) {
open(my $in_file, '<', $file) or die "Can't read source file: $!";
print "Processing file $file\n";
while (my $citing_pat = get_number($in_file)) {
get_country($citing_pat);
}
}
close $out_file;
sub get_number {
my $fh = shift;
while(<$fh>) {
if (/DID/) {
my $field = (split)[3];
if($field =~ /^\d+$/){
return $field;
}
}
}
return undef;
}