Perl code to check the last line number of a file - perl

I have writeen a perl code that writes the number of lines it is one by one processing. I would like to get only the last line number of a file. The code is as follows:
#!/usr/bin/perl
using strict;
using warnings;
my $qbsid_dir = "/root/deep/";
opendir (DIR, "$qbsid_dir") or die "Cannot open the directory!\n";
while (my $file = readdir DIR){
next if ($file =~ m/^\./);
open(FH, "$qbsid_dir/$file") or die "Cannot open the file\n";
while (my $line = <FH>){
print "$.\n";
}
close (FH);
}
closedir (DIR);
The '/root/deep' directory contains two files. One with 90 lines and other with 100 lines written in the files.
I want those numbers to be printed instead of individual numbers such as 1..90 and 1..100 by $.
Thanks.

Do you really want to use Perl ?
wc -l <File>

If you want the last line number, wait to print $. until outside the while loop for processing the file:
open my $fh, '<', "$qbsid_dir/$file" or die "Can't open $file: $!";
1 while (<$fh>);
print "$file -> $.\n";
close $fh;
Be sure to read: perlfaq5 - How do I count the number of lines in a file?

Try this. It also print the last number of the filename.
$filedir = '/root/deep/';
opendir (dir, "$filedir");
#directory = readdir(dir);
#grep = grep{m/.*\.txt/g} #directory; #It matches the particular file format
foreach $dir(#grep){
open(file,"$filedir/$dir");
#line =<file>;
$total = #line; #$total variable store the last file number from array (#line)
print "File $dir Last line \t: $total\n";
}

You can use oneliner too
perl -nE '}{say $.' filename
#or
command | perl -nE '}{say $.'
test
$ seq 10 | perl -nE '}{say $.'
10
$ seq 10 | wc -l
10

Related

Counting number of lines with conditions

This is my script count.pl, I am trying to count the number of lines in a file.
The script's code :
chdir $filepath;
if (-e "$filepath"){
$total = `wc -l < file.list`;
printf "there are $total number of lines in file.list";
}
i can get a correct output, but i do not want to count blank lines and anything in the file that start with #. any idea ?
As this is a Perl program already open the file and read it, filtering out lines that don't count with
open my $fh, '<', $filename or die "Can't open $filename: $!";
my $num_lines = grep { not /^$|^\s*#/ } <$fh>;
where $filename is "file.list." If by "blank lines" you mean also lines with spaces only then chagne regex to /^\s*$|^\s*#/. See grep, and perlretut for regex used in its condition.
That filehandle $fh gets closed when the control exits the current scope, or add close $fh; after the file isn't needed for processing any more. Or, wrap it in a block with do
my $num_lines = do {
open my $fh, '<', $filename or die "Can't open $filename: $!";
grep { not /^$|^\s*#/ } <$fh>;
};
This makes sense doing if the sole purpose of opening that file is counting lines.
Another thing though: an operation like chdir should always be checked, and then there is no need for the race-sensitive if (-e $filepath) either. Altogether
# Perhaps save the old cwd first so to be able to return to it later
#my $old_cwd = Cwd::cwd;
chdir $filepath or die "Can't chdir to $filepath: $!";
open my $fh, '<', $filename or die "Can't open $filename: $!";
my $num_lines = grep { not /^$|^\s*#/ } <$fh>;
A couple of other notes:
There is no reason for printf. For all normal prints use say, for which you need use feature qw(say); at the beginning of the program. See feature pragma
Just in case, allow me to add: every program must have at the beginning
use warnings;
use strict;
Perhaps the original intent of the code in the question is to allow a program to try a non-existing location, and not die? In any case, one way to keep the -e test, as asked for
#my $old_cwd = Cwd::cwd;
chdir $filepath or warn "Can't chdir to $filepath: $!";
my $num_lines;
if (-e $filepath) {
open my $fh, '<', $filename or die "Can't open $filename: $!";
$num_lines = grep { not /^$|^\s*#/ } <$fh>;
}
where I still added a warning if chdir fails. Remove that if you really don't want it. I also added a declaration of the variable that is assigned the number of lines, with my $total_lines;. If it is declared earlier in your real code then of course remove that line here.
perl -ne '$n++ unless /^$|^#/ or eof; print "$n\n" if eof'
Works with multiple files too.
perl -ne '$n++ unless /^$|^#/ or eof; END {print "$n\n"}'
Better for a single file.
open(my $fh, '<', $filename);
my $n = 0;
for(<$fh>) { $n++ unless /^$|^#/}
print $n;
Using sed to filter out the "unwanted" lines in a single file:
sed '/^\s*#/d;/^\s*$/d' infile | wc -l
Obviously, you can also replace infile with a list of files.
The solution is very simple, no any magic.
use strict;
use warnings;
use feature 'say';
my $count = 0;
while( <> ) {
$count++ unless /^\s*$|^\s*#/;
}
say "Total $count lines";
Reference:
<>

How to rename all files in a directory to have an equal amount of digits?

I took a lot of pictures with my camera and I wanted to make a timelapse out of them. The camera saved the pictures as picture1, picture2 ... picture956 etc but the timelapse software I'm using only accepts numbers of equal length like this: picture001, picture002, picture003 etc.
I thought Perl would be a good fit for this kind of problem so I gave it a shot. This is a shorter and translated version of the original code so if anything is unclear I can change it to the longer version.
use strict;
use warnings;
use diagnostics;
print "Give the path to the directory where the pictures are stored:\n";
my $filename = <STDIN>;
chomp $filename;
chdir $filename or die "Couldn't change the directory: $!\n";
my #files = <*>;
#gives a list of all filehandles in the given directory
my $amount_of_digits = int( log($#files)/log(10) +1);
#how many digits should the new number have?
#Example: If there are 400 files -> 3 digits per file: 001, 002 etc
foreach my $file (#files){
next if($file =~ /^\.$/);
next if($file =~ /^\.\.$/);
#skip the . and .. files
print "$file\n";
if ($file =~ /(\d+)/){
my $amount_of_padded_zeroes = $amount_of_digits - length($1);
if($amount_of_padded_zeroes > 0){
my $new_number = '0' x $amount_of_padded_zeroes . $1;
(my $new_name = $file) =~ s/$1/$new_number/;
print "Changing name too: $new_name\n";
rename ($file, $new_name) or die "Couldn't rename the file: $!";
}
}
}
print "Program completed. Press any key to continue.\n";
my $einde = <STDIN>;
The code works but I wanted to know if there is a better/cleaner/shorter/more Pearlesque way to do this. I'm learning Perl for uni so any feedback is welcome. I suspect this problem is so trivial in Perl that there might be readable one-liners that are able to replace all of this.
Honestly, I don't think I'd change very much. Here's my version.
#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;
print "Give the path to the directory where the pictures are stored:\n";
chomp(my $dir = <STDIN>);
chdir $dir or die "Couldn't change the directory: $!\n";
my #files = <*>;
#gives a list of all filehandles in the given directory
my $amount_of_digits = int( log($#files)/log(10) +1);
#how many digits should the new number have?
#Example: If there are 400 files -> 3 digits per file: 001, 002 etc
foreach (#files) {
next if -d;
#skip the . and .. files
print "$_\n";
if (/(\d+)/) {
my $new_number = sprintf "%0${amount_of_digits}d", $1;
(my $new_name = $_) =~ s/$1/$new_number/;
print "Changing name to: $new_name\n";
rename ($_, $new_name) or die "Couldn't rename the file: $!";
}
}
print "Program completed. Press any key to continue.\n";
my $einde = <STDIN>;
I've combined the chomp() into the line that reads from STDIN.
I renamed the variable that holds the directory name. It's now called $dir.
I use $_ as the loop variable - because many of the operations inside the loop use $_ by default, so it simplifies some of the code.
Instead of looking for . and .. specifically, I just look for directories (with -d).
I use sprintf() as a simpler(?) way to generate a zero-padded version of the number.
Hope this is useful.

Search a word in file and replace in Perl

I want to replace word "a" to "red" in a.text files. I want to edit the same file so I tried this code but it does not work. Where am I going wrong?
#files=glob("a.txt");
foreach my $file (#files)
{
open(IN,$file) or die $!;
<IN>;
while(<IN>)
{
$_=~s/a/red/g;
print IN $file;
}
close(IN)
}
I'd suggest it's probably easier to use perl in sed mode:
perl -i.bak -p -e 's/a/red/g' *.txt
-i is inplace edit (-i.bak saves the old as .bak - -i without a specifier doesn't create a backup - this is often not a good idea).
-p creates a loop that iterates all the files specified one line at a time ($_), applying whatever code is specified by -e before printing that line. In this case - s/// applies a sed-style patttern replacement to $_, so this runs a search and replace over every .txt file.
Perl uses <ARVG> or <> to do some magic - it checks if you specify files on your command line - if you do, it opens them and iterates them. If you don't, it reads from STDIN.
So you can also do:
somecommand.sh | perl -i.bak -p -e 's/a/red/g'
In your code you are using same filehandle to write which you have used for open the file to reading. Open the same file for write mode and then write.
Always use lexical filehandle and three arguments to open a file. Here is your modified code:
use warnings;
use strict;
my #files = glob("a.txt");
my #data;
foreach my $file (#files)
{
open my $fhin, "<", $file or die $!;
<$fhin>;
while(<$fhin>)
{
$_ =~ s/\ba\b/red/g;
push #data, $_;
}
open my $fhw, ">", $file or die "Couldn't modify file: $!";
print $fhw #data;
close $fhw;
}
Here is another way (read whole file in a scalar):
foreach my $file (glob "/path/to/dir/a.txt")
{
#read whole file in a scalar
my $data = do {
local $/ = undef;
open my $fh, "<", $file or die $!;
<$fh>;
};
$data =~ s/\ba\b/red/g; #replace a with red,
#modify the file
open my $fhw, ">", $file or die "Couldn't modify file: $!";
print $fhw $data;
close $fhw;
}

Search string with multiple words in the pattern

My program is trying to search a string from multiple files in a directory. The code searches for single patterns like perl but fails to search a long string like Status Code 1.
Can you please let me know how to search for strings with multiple words?
#!/usr/bin/perl
my #list = `find /home/ad -type f -mtime -1`;
# printf("Lsit is $list[1]\n");
foreach (#list) {
# print("Now is : $_");
open(FILE, $_);
$_ = <FILE>;
close(FILE);
unless ($_ =~ /perl/) { # works, but fails to find string "Status Code 1"
print "found\n";
my $filename = 'report.txt';
open(my $fh, '>>', $filename) or die "Could not open file '$filename' $!";
say $fh "My first report generated by perl";
close $fh;
} # end unless
} # end For
There are a number of problems with your code
You must always use strict and use warnings at the top of every Perl program. There is little point in delcaring anything with my without strict in place
The lines returned by the find command will have a newline at the end which must be removed before Perl can find the files
You should use lexical file handles (my $fh instead of FILE) and the three-parameter form of open as you do with your output file
$_ = <FILE> reads only the first line of the file into $_
unless ($_ =~ /perl/) is inverted logic, and there's no need to specify $_ as it is the default. You should write if ( /perl/ )
You can't use say unless you have use feature 'say' at the top of your program (or use 5.010, which adds all features available in Perl v5.10)
It is also best to avoid using shell commands as Perl is more than able to do anything that you can using command line utilities. In this case -f $file is a test that returns true if the file is a plain file, and -M $file returns the (floating point) number of days since the file's modification time
This is how I would write your program
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
for my $file ( glob '/home/ad/*' ) {
next unless -f $file and int(-M $file) == 1;
open my $fh, '<', $file or die $!;
while ( <$fh> ) {
if ( /perl/ ) {
print "found\n";
my $filename = 'report.txt';
open my $out_fh, '>>', $filename or die "Could not open file '$filename': $!";
say $fh "My first report generated by perl";
close $out_fh;
last;
}
}
}
it should have matched unless $_ contains text in different case.
try this.
unless($_ =~ /Status\s+Code\s+1/i) {
Change
unless ($_ =~ /perl/) {
to:
unless ($_ =~ /(Status Code 1)/) {
I am certain the above works, except it's case sensitive.
Since you question it, I rewrote your script to make more sense of what you're trying to accomplish and implement the above suggestion. Correct me if I am wrong, but you're trying to make a script which matches "Status Code 1" in a bunch of files where last modified within 1 day and print the filename to a text file.
Anyways, below is what I recommend:
#!/usr/bin/perl
use strict;
use warnings;
my $output_file = 'report.txt';
my #list = `find /home/ad -type f -mtime -1`;
foreach my $filename (#list) {
print "PROCESSING: $filename";
open (INCOMING, "<$filename") || die "FATAL: Could not open '$filename' $!";
foreach my $line (<INCOMING>) {
if ($line =~ /(Status Code 1)/) {
open( FILE, ">>$output_file") or die "FATAL: Could not open '$output_file' $!";
print FILE sprintf ("%s\n", $filename);
close(FILE) || die "FATAL: Could not CLOSE '$output_file' $!";
# Bail when we get the first match
last;
}
}
close(INCOMING) || die "FATAL: Could not close '$filename' $!";
}

Extract file contents between given lines using perl

I want to use only Sed in Perl to capture the file contents between 1000 and 2000 lines in a given file.
I tried the below but it didn't work,Can someone help me on this please.
$firstLIne="1000";
$lastline="2000";
$output=`sed -n '$firstLIne,$lastline'p sample.txt`;
Here is another pure perl solution:
my ($firstline, $lastline) = (1000,2000);
open my $fh, '<', 'sample.txt' or die "$!";
while(<$fh>){
print if $. == $firstline .. $. == $lastline;
}
if you don't use the variables anywhere else, you can use the special use case of .. with constants (4th paragraph if you use constant expression they automatically get compared to $.):
while(<$fh>){
print if 1000 .. 2000;
}
Here is the important part from the perldoc for the .. operator:
In scalar context, ".." returns a boolean value. The operator is bistable, like a flip-flop, and emulates the line-range (comma) operator of sed, awk, and various editors.
Edit Per request, with storing the intermediate lines in a variable.
my ($firstline, $lastline) = (1000,2000);
my $output = '';
open my $fh, '<', 'sample.txt' or die $!;
while(<$fh>){
$output .= $_ if $. == $firstline .. $. == $lastline;
}
print $ouput;
Also, if your file isn't too big (it fits completely into memory) you also can read it into a list and select the lines you're interested in:
my $output = join '', (<$fh>)[$firstline+1..$lastline]
For comparison, to do this in Perl only, one could write:
my $firstLine=1000;
my $lastLine=2000;
my $fn="sample.txt";
my $output;
open (my $fh, "<", $fn) or die "Could not open file '$fn': $!\n";
while (<$fh>) {
last if $. > $lastLine;
$output .= $_ if $. >= $firstLine;
}
close($fh);
Note that this will stop reading from file after line $lastLine.. so if the file contains 100,000 lines it will only read the first 2000 lines..
If you just want to print out the lines then:
perl -ne 'print if 1000 .. 2000' example_data.txt
should work.
If you want to incorporate that into a script somehow then you can "semi-slurp" the filehandle:
use strict;
use warnings;
open my $filehandle, 'example_data.txt' or die $!;
my $lines_1k_to_2k ;
while (<$filehandle>) {
$lines_1k_to_2k .= $_ if 1000 .. 2000 ;
}
print $lines_1k_to_2k ;
The .= operator will add the lines to the string in variable $lines_1k_to_2k only if they are in the range 1000 .. 2000