I'm new to Stack Overflow and I would like to ask for some advice with regard to a minor problem I have with my Perl code.
In short, I have written a small programme that opens text files from a pre-defined array, then searches for certain strings in them and finally prints out the line containing the string.
my #S1A_SING_Files = (
'S1A-001_SING_annotated.txt',
'S1A-002_SING_annotated.txt',
'S1A-003_SING_annotated.txt',
'S1A-004_SING_annotated.txt',
'S1A-005_SING_annotated.txt'
);
foreach (#S1A_SING_Files) {
print ("\n");
print ("Search results for $_:\n\n");
open (F, $_) or die("Can't open file!\n");
while ($line = <F>) {
if ($line =~ /\$(voc)?[R|L]D|\$Rep|\/\//) {
print ($line);
}
}
}
close (F);
I was wondering whether it is possible to create an exception to the foreach loop, so that the line containing
print ("\n");
not be executed if the file is $S1A_SING_Files[0]. It should then be normally executed if the file is any of the following ones. Do you think this could be accomplished?
Thank you very much in advance!
Yes. Just add a check for the first file. Change:
print ("\n");
to:
print ("\n") if $_ ne $S1A_SING_Files[0];
If the array contains unique strings, you can use the following:
print("\n") if $_ ne $S1A_SING_Files[0]; # Different stringification than 1st element?
The following will work even if the array contains non-strings or duplicate values (and it's faster too):
print("\n") if \$_ != \$S1A_SING_Files[0]; # Different scalar than 1st element?
Both of the above could fail for magical arrays. The most reliable solution is to iterate over the indexes.
for my $i (0..$#S1A_SING_Files) {
my $file = $S1A_SING_Files[$i];
print("\n") if $i; # Different index than 1st element?
...
}
Your code can be written in following form
use strict;
use warnings;
my #S1A_SING_Files = (
'S1A-001_SING_annotated.txt',
'S1A-002_SING_annotated.txt',
'S1A-003_SING_annotated.txt',
'S1A-004_SING_annotated.txt',
'S1A-005_SING_annotated.txt'
);
foreach (#S1A_SING_Files) {
print "\n" unless $_ ne $S1A_SING_Files[0];
print "Search results for $_:\n\n";
open my $fh, '<', $_ or die("Can't open file!\n");
m!\$(voc)?[R|L]D|\$Rep|//! && print while <$fh>;
close $fh;
}
Related
I need to iterate over many files in a directory and split each file into two parts. I need to keep lines intact (I can't split on bite size). I also can't always assume that the file has an equal number of lines. I could use the "split" function, but am looking for a faster way of going through my files and to avoid the standard output names "xaa" and "xab" it generates.
The easiest would be to make two subsequent substrings of an array in the sizes specified ($number_of_group_one and $number_of_group_two). I can't find out how to do this. Instead I am trying to push the lines into different arrays- filling one up until a certain number of lines and then "spill over" into the other array until there are no more lines left to push. However, this approach yields two output arrays that both have exactly double the number of input lines. Here is my code:
#!/usr/bin/perl
use warnings;
use strict;
my ($directory) = #ARGV;
my $dir = "$directory";
my #arrayoffiles = glob "$dir/*";
my #arrayoflines_one;
my #arrayoflines_two;
my $counter = 0;
foreach my $filename(#arrayoffiles){
my #arrayoflines_one;
my #arrayoflines_two;
my #lines = read_lines($filename);
my $NumberofLines = #lines;
my $number_of_group_one = int($NumberofLines/2);
my $number_of_group_two = ($NumberofLines - $number_of_group_one);
foreach my $line (#lines){
$counter++;
push (#arrayoflines_one, $line, "\n");
if ($counter == $number_of_group_one){
push (#arrayoflines_two, $line, "\n");
}
}
}
sub read_lines {
my ($file) = #_;
open my $in, '<', $file or die $!;
local $/ = undef; #slurps the whole file in as one
my $content = <$in>;
return split /\s/, $content;
close $in;
}
I hope this is clear. Thanks for your help!
This is a good use case for splice:
my #lines = read_lines($filename);
my #lines1 = splice #lines, 0, #lines/2;
will put (about) half of your lines from #lines into #lines1, removing them (and leaving about half of the lines) from #lines.
I have an array in Perl that looks like this:
my #dynfiles = ('dyn.abc.transcript', 'dyn.def.transcript', 'dyn.ghi.transcript', 'dyn.jkl.transcript');
I'm trying to open these files and read them one by one. For this I have a code that looks like this:
foreach my $dynfile (#dynfiles) {
print "$dynfile\n";
open my $fh , '<', $dynfile or die "Could not open file\n";
my %data;
$data{$dynfile} = do {
local $/ = undef;
while (my $line = <$fh>) {
chomp $line;
if ($line =~ m/Errors:\s+0/) {
print "Dyn run status: PASS\n";
} else {
print "Dyn Run status : FAIL\n";
}
}
close $fh;
}
}
And I get this error as output:
dyn.bxt.transcript
Dyn run status: FAIL
dyn.cnl.transcript
17:25:19 : -E- Could not open dyn.cnl.transcript
So my concern is that it isn't reading the files in the array at all. Also, this file dyn.bxt.transcript had this string Errors : 0 in it, but I still get Dyn run status: FAIL in the output.
Am I doing anything wrong here? I'm using a simple pattern match, not sure where the problem is..Kindly help.
Thanks in advance!
After viewing reading your code and debugging in chat, I would probably go with something like this:
sub dynamo_check {
opendir(my $dh, $log_file) or die "can't opendir $log_file: $!";
my #dynfiles = grep { /^dynamo.*transcript$/ && -f "$log_file/$_" } readdir($dh);
close $dh;
foreach my $dynamofile (#dynfiles) {
print "Checking file: $dynamofile\n";
open my $fh, '<', $log_file . $dynamofile or die "$!\n";
my $passed = 0;
while(my $line = <$fh>) {
if ($line =~ m/Errors\s*:\s*0/i) {
$passed = 1;
last;
}
}
if ( $passed == 1 ) {
print "Dynamo run status: PASS\n";
$data{$dynamofile} = "pass";
}else {
print "Dynamo run status: FAIL\n";
$data{$dynamofile} = "fail";
}
}
print Dumper(\%data);
}
Summary of changes:
Add on $! to get a better error message as reference in perlvar
Use grep and readdir to find the files you want to read instead of hard coding it.
Prepend the directory path to the file name when we open the files.
Remove the do block;
Set the values in %data to pass or fail.
No need to use chomp here.
No need to set local $/ = undef;, we can go through the lines one by one and break out of the while loop with last when we find the Errors line.
First, let Perl tell you why it couldn't open a file:
open my $fh , '<', $dynfile or die "Could not open file $!\n";
I notice that your error message references dyn.bxt.transcript and dyn.cnl.transcript that are not in your #dynfiles. It helps is you build a complete and minimal script with sample inputs.
Then you are undefining the input record separator, after which you use a while which should only ever return the single line of the entire file. That's typically a bad thing.
Next, if looks as if your pattern doesn't match the string Errors : 0 that has a space in it.
if ($line =~ m/Errors\s*:\s+0/) {
I'm not sure what you're doing with the do. That returns the last evaluated expression, which in your case is close $fh. But, that %data hash disappears at the end of each iteration of the block. Again, strip out everything that isn't part of investigating this problem.
You can reduce the code using some well tested modules. For example using one of my favourite Path::Tiny, you could write:
use 5.014;
use warnings;
use Path::Tiny;
my #dynfiles = map { "dyn.$_.transcript" } qw(abc def ghi jkl);
say "Dyn run status: ",
(path($_)->slurp =~ /error\s*:\s*0\b/i)
? "PASS"
: "FAIL"
for (grep {-f} #dynfiles);
Hello I am in the process of making a program that matches a given set of keywords to a file.
I want to output the matched data to a text file and include the regex keyword that triggered the match.
Below is my code related to my issue:
my $counter = 0;
foreach($words)
{
while($line = <FILE>)
{
if($line =~ /$words/)
{
print "#array[$counter] $line\n";
print OUTPUT $line;
}
}
$counter ++;
}
This does not produce the expected outcome. It works perfectly for the first element in the array but for the rest it just simply prints the first one again. I believe the counter is not being incremented.
Is there a better / easier way to get the current element being used in the loop? or even get the current regex match?
The problem is that <FILE> exhausts the file for the first word. For the next word, <FILE> tries to read at the end of the file, which means the whole loop is skipped.
You can iterate over the words inside the loop over the file, or you can seek
back to the beginning of the file at the end of the loop.
Here is what you should do:
use strict;
use warnings;
use 5.016;
my $fname = 'data.txt';
my #patterns = (
'do.',
'.at',
'.ir.',
);
open my $INFILE, '<', $fname
or die "Couldn't read from $fname: $!";
while (my $line = <$INFILE>) {
for my $pattern (#patterns) {
if ($line =~ /($pattern)/) {
print "$pattern --> $1";
}
}
}
close $INFILE:
Putting parentheses around parts of the regex causes perl to set the match variables $1, $2, $3, etc., which contain the match for each parenthesized group.
$line will have a newline at the end of the line, so if you write print "$line\n", you will add another newline, so your output file will have blank lines between every line you print.
I am trying to print the first 250 entries in an array to a .txt file, but I am having some trouble. When I run the script as is, I get nothing in my output.txt file.
#! /usr/bin/perl
use strict;
use warnings;
my $line;
my #array;
my $file = "moloch_chunker_output.txt";
open (OUT , ">","moloch_chunker_output.txt")or die "cant open: $!";
while ($line = <>){
chomp($line);
push(#array, $line);
if(#array == 250){
print OUT #array;
}
}
I know that I'm missing quite a bit here, but I have tried a couple of other methods after the if statement.
if(array == 250){
print "[", join(",",#array),"]","\n";
works exactly how I want. I just want it to be written to a .txt file instead of simply being printed to the screen. How can I print the array to a .txt file?
Instead of stuffing everything into an array and then printing the array contents when its size gets to 250, perhaps you could simply start a counter and print each line as you see it (and then quit once you reach 250). Kinda like:
$cnt = 0;
while (<>) {
chomp;
print;
last if ++$cnt >= 250;
}
Or... you could just run head -250 moloch_chunker_output.txt from the command line (and skip Perl altogether).
The purpose of the script is to process all words from a file and output ALL words that occur the most. So if there are 3 words that each occur 10 times, the program should output all the words.
The script now runs, thanks to some tips I have gotten here. However, it does not handle large text files (i.e. the New Testament). I'm not sure if that is a fault of mine or just a limitation of the code. I am sure there are several other problems with the program, so any help would be greatly appreciated.
#!/usr/bin/perl -w
require 5.10.0;
print "Your file: " . $ARGV[0] . "\n";
#Make sure there is only one argument
if ($#ARGV == 0){
#Make sure the argument is actually a file
if (-f $ARGV[0]){
%wordHash = (); #New hash to match words with word counts
$file=$ARGV[0]; #Stores value of argument
open(FILE, $file) or die "File not opened correctly.";
#Process through each line of the file
while (<FILE>){
chomp;
#Delimits on any non-alphanumeric
#words=split(/[^a-zA-Z0-9]/,$_);
$wordSize = #words;
#Put all words to lowercase, removes case sensitivty
for($x=0; $x<$wordSize; $x++){
$words[$x]=lc($words[$x]);
}
#Puts each occurence of word into hash
foreach $word(#words){
$wordHash{$word}++;
}
}
close FILE;
#$wordHash{$b} <=> $wordHash{$a};
$wordList="";
$max=0;
while (($key, $value) = each(%wordHash)){
if($value>$max){
$max=$value;
}
}
while (($key, $value) = each(%wordHash)){
if($value==$max && $key ne "s"){
$wordList.=" " . $key;
}
}
#Print solution
print "The following words occur the most (" . $max . " times): " . $wordList . "\n";
}
else {
print "Error. Your argument is not a file.\n";
}
}
else {
print "Error. Use exactly one argument.\n";
}
Your problem lies in the two missing lines at the top of your script:
use strict;
use warnings;
If they had been there, they would have reported lots of lines like this:
Argument "make" isn't numeric in array element at ...
Which comes from this line:
$list[$_] = $wordHash{$_} for keys %wordHash;
Array elements can only be numbers, and since your keys are words, that won't work. What happens here is that any random string is coerced into a number, and for any string that does not begin with a number, that will be 0.
Your code works fine reading the data in, although I would write it differently. It is only after that that your code becomes unwieldy.
As near as I can tell, you are trying to print out the most occurring words, in which case you should consider the following code:
use strict;
use warnings;
my %wordHash;
#Make sure there is only one argument
die "Only one argument allowed." unless #ARGV == 1;
while (<>) { # Use the diamond operator to implicitly open ARGV files
chomp;
my #words = grep $_, # disallow empty strings
map lc, # make everything lower case
split /[^a-zA-Z0-9]/; # your original split
foreach my $word (#words) {
$wordHash{$word}++;
}
}
for my $word (sort { $wordHash{$b} <=> $wordHash{$a} } keys %wordHash) {
printf "%-6s %s\n", $wordHash{$word}, $word;
}
As you'll note, you can sort based on hash values.
Here is an entirely different way of writing it (I could have also said "Perl is not C"):
#!/usr/bin/env perl
use 5.010;
use strict; use warnings;
use autodie;
use List::Util qw(max);
my ($input_file) = #ARGV;
die "Need an input file\n" unless defined $input_file;
say "Input file = '$input_file'";
open my $input, '<', $input_file;
my %words;
while (my $line = <$input>) {
chomp $line;
my #tokens = map lc, grep length, split /[^A-Za-z0-9]+/, $line;
$words{ $_ } += 1 for #tokens;
}
close $input;
my $max = max values %words;
my #argmax = sort grep { $words{$_} == $max } keys %words;
for my $word (#argmax) {
printf "%s: %d\n", $word, $max;
}
why not just get the keys from the hash sorted by their value and extract the first X?
this should provide an example: http://www.devdaily.com/perl/edu/qanda/plqa00016