How to print file content in parts to the screen? - linux

I am trying to store command output into a file (that works fine) and then what I want to do, is to display the file content to the screen.
My problem is that I want it to be displayed in parts (for example 20 lines at a time) and let the user press [Enter] or any key to continue to the next part. I was thinking about piping the file content to more however it displays the whole file content at once instead of doing it by parts.
Here is my part of the code that is responsible for opening a file, writing to it, and then displaying it on the screen.
open FILE, '>', $filename;
print FILE #command;
open FILE, '<', $filename;
while (<FILE>) {
open MORE, '| more';
print MORE;
}
close MORE;
close FILE;

use strict;
use warnings;
my #command = map "output line $_\n", 1..100;
my $page_size = 20;
my $n = 0;
for my $line (#command) {
print $line;
$n ++;
if ($n % $page_size == 0) {
print "--More--";
<>;
}
}

You just need the open more out of the loop:
close FILE;
open FILE, '<', $filename;
open MORE, '| more';
while (<FILE>) {
print MORE;
}
close MORE;
close FILE;
or without using more:
open my $file, '<', $filename or die("$!");
while (#command) {
print join("\n", splice(#command, 0, 20));
<>;
}
close $file;

Related

search multi line string from multiple files in a directory

the string to to be searched is:
the file_is being created_automaically {
period=20ns }
the perl script i am using is following ( this script is working fine for single line string but not working for multi line )
#!/usr/bin/perl
my $dir = "/home/vikas";
my #files = glob( $dir . '/*' );
#print "#files";
system ("rm -rf $dir/log.txt");
my $list;
foreach $list(#files){
if( !open(LOGFILE, "$list")){
open (File, ">>", "$dir/log.txt");
select (File);
print " $list \: unable to open file";
close (File);
else {
while (<LOGFILE>){
if($_ =~ /".*the.*automaically.*\{\n.*period\=20ns.*\}"/){
open (File, ">>", "$dir/log.txt");
select (File);
print " $list \: File contain the required string\n";
close (File);
break;
}
}
close (LOGFILE);
}
}
This code does not compile, it contains errors that causes it to fail to execute. You should never post code that you have not first tried to run.
The root of your problem is that for a multiline match, you cannot read the file in line-by-line mode, you have to slurp the whole file into a variable. However, your program contains many flaws. I will demonstrate. Here follows excerpts of your code (with fixed indentation and missing curly braces).
First off, always use:
use strict;
use warnings;
This will save you many headaches and long searches for hidden problems.
system ("rm -rf $dir/log.txt");
This is better done in Perl, where you can control for errors:
unlink "$dir/log.txt" or die "Cannot delete '$dir/log.txt': $!";
foreach my $list (#files) {
# ^^
Declare the loop variable in the loop itself, not before it.
if( !open(LOGFILE, "$list")){
open (File, ">>", "$dir/log.txt");
select (File);
print " $list \: unable to open file";
close (File);
You never have to explicitly select a file handle before you print to it. You just print to the file handle: print File "....". What you are doing is just changing the STDOUT file handle, which is not a good thing to do.
Also, this is error logging, which should go to STDERR instead. This can be done simply by opening STDERR to a file at the beginning of your program. Why do this? If you are not debugging a program at a terminal, for example via the web or some other process where STDERR does not show up on your screen. Otherwise it is just extra work while debugging.
open STDERR, ">", "$dir/log.txt" or die "Cannot open 'log.txt' for overwrite: $!";
This has the added benefit of you not having to delete the log first. And now you do this instead:
if (! open LOGFILE, $list ) {
warn "Unable to open file '$list': $!";
} else ....
warn goes to STDERR, so it is basically the same as print STDERR.
Speaking of open, you should use three argument open with explicit file handle. So it becomes:
if (! open my $fh, "<", $list )
} else {
while (<LOGFILE>) {
Since you are looking for a multiline match, you need to slurp the file(s) instead. This is done by setting the input record separator to undef. Typically like this:
my $file = do { local $/; <$fh> }; # $fh is our file handle, formerly LOGFILE
Next how to apply the regex:
if($_ =~ /".*the.*automaically.*\{\n.*period\=20ns.*\}"/) {
$_ =~ is optional. A regex automatically matches against $_ if no other variable is used.
You should probably not use " in the regex. Unless you have " in the target string. I don't know why you put it there, maybe you think strings need to be quoted inside a regex. If you do, that is wrong. To match the string you have above, you do:
if( /the.*automaically.*{.*period=20ns.*}/s ) {
You don't have to escape \ curly braces {} or equal sign =. You don't have to use quotes. The /s modifier makes . (wildcard character period) also match newline, so we can remove \n. We can remove .* from start or end of string, because that is implied, regex matches are always partial unless anchors are used.
break;
The break keyword is only used with the switch feature, which is experimental, plus you don't use it, or have it enabled. So it is just a bareword, which is wrong. If you want to exit a loop prematurely, you use last. Note that we don't have to use last because we slurp the file, so we have no loop.
Also, you generally should pick suitable variable names. If you have a list of files, the variable that contains the file name should not be called $list, I think. It is logical that it is called $file. And the input file handle should not be called LOGFILE, it should be called $input, or $infh (input file handle).
This is what I get if I apply the above to your program:
use strict;
use warnings;
my $dir = "/home/vikas";
my #files = glob( $dir . '/*' );
my $logfile = "$dir/log.txt";
open STDERR, ">", $logfile or die "Cannot open '$logfile' for overwrite: $!";
foreach my $file (#files) {
if(! open my $input, "<", $file) {
warn "Unable to open '$file': $!";
} else {
my $txt = do { local $/; <$fh> };
if($txt =~ /the.*automaically.*{.*period=20ns.*}/) {
print " $file : File contain the required string\n";
}
}
}
Note that the print goes to STDOUT, not to the error log. It is not common practice to have STDOUT and STDERR to the same file. If you want, you can simply redirect output in the shell, like this:
$ perl foo.pl > output.txt
The following sample code demonstrates usage of regex for multiline case with logger($fname,$msg) subroutine.
Code snippet assumes that input files are relatively small and can be read into a variable $data (an assumption is that computer has enough memory to read into).
NOTE: input data files should be distinguishable from rest files in home directory $ENV{HOME}, in this code sample these files assumed to match pattern test_*.dat, perhaps you do not intend to scan absolutely all files in your home directory (there could be many thousands of files but you interested in a few only)
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
my($dir,$re,$logfile);
$dir = '/home/vikas/';
$re = qr/the file_is being created_automaically \{\s+period=20ns\s+\}/;
$logfile = $dir . 'logfile.txt';
unlink $logfile if -e $logfile;
for ( glob($dir . "test_*.dat") ) {
if( open my $fh, '<', $_ ) {
my $data = do { local $/; <$fh> };
close $fh;
logger($logfile, "INFO: $_ contains the required string")
if $data =~ /$re/gsm;
} else {
logger($logfile, "WARN: unable to open $_");
}
}
exit 0;
sub logger {
my $fname = shift;
my $text = shift;
open my $fh, '>>', $fname
or die "Couldn't to open $fname";
say $fh $text;
close $fh;
}
Reference: regex modifies, unlink, perlvar

I can print to file using > in terminal but how do I print to files where I create the name using a $ in this code

I can print to file using > in terminal but how do I print to files where I create the name using a $ in this code?
use strict;
use warnings;
my $calls_dir = "Ask/";
opendir(my $search_dir, $calls_dir) or die "$!\n";
my #files = grep /\.txt$/i, readdir $search_dir;
closedir $search_dir;
print "Got ", scalar #files, " files\n";
#my %seen = ();
foreach my $file (#files) {
my %seen = ();
my $current_file = $calls_dir . $file;
open my $FILE, '<', $current_file or die "$file: $!\n";
while (<$FILE>) {
#if (/phone/i) {
chomp;
#if (/phone\s*(.*)\r?$/i) {
#if (/^phone\s*:\s*(.*)\r?$/i) {
#if (/Contact\s*(.*)\r?$/i) {
if (/^*(.*)Contact\s*(.*)\r?$/i) {
$seen{$1} = 1;
print $file."\t"."$_\n";# I want to print this line to file named $file."result".txt
#print "\t";
#print "\n";
#print "$_\n";
#print "\t";
#print "\n";
foreach my $addr ( sort keys %seen ) {
...
}
}
}
close $FILE;
}
Open a filehandle for writing to that file and print to it. If it doesn't exist, Perl will create it.
open my $fh, '>', "${file}result.txt" or die $!;
$fh->print("$file\t$_\n");
From perldoc -f open:
If MODE is ">", the file is opened for
output, with existing files first being truncated ("clobbered")
and nonexisting files newly created. If MODE is ">>", the file is
opened for appending, again being created if necessary.
If you want to avoid truncation, check if it exists first using -e and/or add something to the filename to make it reasonably unique (like a Unix timestamp).

Perl: Extract data from different sections of a text file simultaneously

I want to extract data from different sections of a text file simultaneously. Is it possible to open the file using two separate filehandles(as shown below) ? Or is it possible to cache the location of the first file handle and then return to that point in the document when I close the second one?
Note: I am only reading data from the text file, never writing to it.
open( $filehandle, '<:encoding(UTF-8)', $filename )
or die "Could not open file '$filename' $!";
$row = <$filehandle>;
{
replace_unicode_char();
if ( $row =~ /$table_num/ ) {
open( $filehandle_reg, '<:encoding(UTF-8)', $filename )
or die "Could not open file '$filename' $!";
$line = <$filehandle_reg>;
if ( $line =~ /Section\_[0-9]+/ ) {
# Do something...
}
}
}
You can use the seek() function to move around in the file, and the tell() function to get the current position in the file.
So, instead of having two filehandles, have two variables storing a position in the file, and use seek() to jump back and forth between them.

using perl fetch a .txt file and for every line in that file do something [duplicate]

This question already has an answer here:
Why does my file content/user input not match? (missing chomp canonical) [duplicate]
(1 answer)
Closed 8 years ago.
I'm pretty new in perl so please try to understand me.
I have in a .txt file defined some lines like this:
doc1.20131010.zip
doc2.20131010.zip
doc3.20131010.zip
doc4.20131010.zip
I made this code:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use autodie;
use Net::SFTP::Attributes;
use Net::SFTP;
use constant {
HOST => "x.x.x.x",
USER_NAME => "sftptest",
PASSWORD => "**********",
DEBUG => "0",
};
my $REMOTE_DIR = "IN";
my $LOCAL_DIR = "/home/rec";
my $sftp = Net::SFTP->new (
HOST,
timeout => 240,
user => USER_NAME,
password => PASSWORD,
autodie => 1,
);
#
# Fetch Files
#
#my $res = $sftp->ls($REMOTE_DIR,sub { print $_[0]{longname}, "\n" });
#print "$res";
my $ls = $sftp->ls($REMOTE_DIR)
or die "ls failed: " . $sftp->error;
open my $fh, '>', '/home/rec/listing' or die "unable to create file: $!";
print $fh $_->{filename}, "\n" for #$ls;
close $fh;
open F, "</home/docs/listing";
for my $line (<F>)
{
#print "$line";
$sftp->get("$line","$line") ;
}
Now when I run the above code it should give me the above files listed, instead I get this:
Couldn't stat remote file: No such file or directory at ./r.pl line 40.
You probably need to remove newline after reading file names from filehandle:
for my $line (<F>) {
chomp($line);
$sftp->get($line, $line);
}
or more commonly,
while (my $line = <F>) {
chomp($line);
$sftp->get($line, $line);
}
You use use autodie;, yet you have:
open my $fh, '>', '/home/rec/listing' or die "unable to create file: $!";
No need for the or die... since the program will automatically die.
You also have use feature qw(say);, yet you use print instead of say. The whole purpose of say is to prevent issues that might be the cause of your error.
You also should check the return results of your $sftp->get($line, $line); line to see if it was successful or not.
If you did both of these, you would have seen that your $sftp->get($line, $line) was failing because you forgot to chomp that NL at the end of the file.
Instead, you used:
`print $line;`
which printed the file out, but since the file name had a NL, it looked fine. Otherwise, you would have see the extra space and immediately seen the problem.

copying data from multiple files and adding to different files

okay, i am not sure whether this is even possible or not..I may sound stochastic...
There are around 250 files with names as
Eg:1
1_0.pdb,1_60.pdb,1_240.pdb,....50_0.pdb,50_60.pdb,50_240.pdb..... having some data.
Now for each of the above file there is a another file of same name....just prefix file is added...Like:
E.g:2
file1_0.pdb,file1_60.pdb,file1_240.pdb,....file50_0.pdb,file50_60.pdb,file50_240.pdb..... again having some data.
is there a code possible that can copy data from each file from first example and paste it to its corresponding file in example2..? like from 1_0.pdb to file1_0.pdb...I hope iam not random and more clear...
With perl you could do something like
#!/usr/bin/perl -w
use strict;
my #filenames = qw(1_0.pdb 1_60.pdb 1_240.pdb);
for my $filename (#filenames) {
open(my $fr, '<', $filename) or next;
open(my $fw, '>>', "file$filename") or next;
local($/) = undef;
my $content = <$fr>;
print $fw $content;
close $fr;
close $fw;
}
EDIT:
Instead of listing all filnames in
my #filenames = qw(1_0.pdb 1_60.pdb 1_240.pdb);
you could do something like
my #filenames = grep {/^\d+_\d+/} glob "*.pdb";
Give this code a try:
use strict;
use warnings;
foreach my $file (glob "*.pdb") {
next if ($file =~ /^file/);
local $/ = undef;
my $newfile = "file$file";
open(my $fh1, "<", $file) or die "Could not open $file: " . $!;
open(my $fh2, ">>", $newfile) or die "Could not open $newfile: " . $!;
my $contents = <$fh1>;
print $fh2 $contents;
close($fh1);
close($fh2);
}
If you want to overwrite the contents of the files rather than appending, change ">>" to ">" in the second open statement.
This shell script will also work
foreach my_orig_file ( `ls *.pdb | grep -v ^file` )
set my_new_file = "file$my_orig_file"
cat $my_orig_file >> $my_new_file
end

Resources