How to get filename of a opened file? - perl

I have this simple script that I'm working on. I must admit, I'm totally new to PERL and kinda stuck with this stupid problem.
open(IN, "<def/t.html") or die();
while(<IN>) {
chomp;
if($_ =~ m/FF0000/) {
print "\n".$_."\n";
}
}
So... I opened the t.html and found the given string in the file. Output was ok, but I need also filename of a file in which string was found, to be printed. I really don't know how to return this, and I need it right after the $_. Thanks for the help in advance.

Simply save the file name in a variable before you open it, then go from there:
my $filename = 'def/t.html';
open( IN, '<', $filename ) or die $!;
...
print "\n$filename: " . $_ . "\n";
Notice that the above uses the 3-arg form of open(), which is safer.
(Also, the language is "Perl", not "PERL".)

That is a strange idea, but you can if you want:
$ cat 1.pl
#somewhere in the code
open(F, "f.txt");
my $f = fileno(F);
#here you want to find the filename
open(FILENAME, "ls -l /proc/$$/fd/$f|");
my #fn = split(/\s+/, <FILENAME>);
print $fn[$#fn],"\n";
$ perl 1.pl
/home/ic/f.txt
Here you know only the filedescriptor and find the filename using it.
You can also write it much shorter with readlink:
open(F, "f.txt");
my $f = fileno(F);
#here you want to find the filename
print readlink("/proc/$$/fd/$f"), "\n";
I must note that the file can be already deleted (but it exists still if it is open).

Related

Perl file exists test fails when path contains spaces

Below is an example of what I am talking about
$PathPlusFileName gets set in Gtk2 File Chooser dialog SUB
if (-e $PathPlusFileName) {
print "found file\n";
} else {
print "did not find file\n";
}
IF block works as long as there are no spaces in the PATH name.
Did try double quoting the path, didn't help.
Any suggestions on how to correct would be appreciated.
It works fine:
#!/usr/bin/perl
use strict;
use warnings;
my $filename = 'name with spaces';
open ( my $testfile, '>', $filename ) or warn $!;
if ( -e "name with spaces" ) {
print "\"$filename\" exists\n";
}
I suspect you'll find something else is going wrong. My first guess would be - did you remember to chomp your input? But we can't answer without seeing how this value is set.
As it turn out, the previous programmer that work on the package GUI section had left this line of test code active.
$PathPlusFileName =~ s/ /\ /g;
One it was removed, if(-e worked as expected. Thank all for the help and suggestions!
Just put your variable in quotes:
if (-e "$PathPlusFileName")

Replace a string in file

My file is like this
DIV=25
FACILITY=11111
and I want to use Perl to replace DIV=25 into DIV=30. Below is my script to do it, but the output of the file is DIV=3030
open( IN_IOE, $FILE_NAME ) || die "Cannot open file";
my #line_ioe = <IN_IOE>;
close(IN_IOE);
chomp #line_ioe;
foreach $_ ( #line_ioe ) {
s/DIV=/DIV=30/
}
open( OUT, ">test.txt" );
foreach $_ (#line_ioe) {
print OUT "$_ \n";
}
close(OUT);
The output of my file is
DIV=3030
FACILITY=11111
Can anyone please show me how to replace that line in file with Perl, and point out where I was wrong.
You can do that in one line of Perl at the command line:
perl -pi -e 's/DIV=25/DIV=30/' file.txt
if you have multiple lines with different numeric numbers (i.e. DIV=25, DIV =31, DIV=21) you could do following.
s/DIV=\d+/DIV=25/g
here \d is to replace any digits and 'g' to perform this globally.
The code you show certainly didn't change DIV=30 into DIV=3030. It didn't do anything at all because you have opened your output file for input
This line
open( OUT, "<test.txt");
should look like this
open OUT, '>', 'test.txt' or die $!;
Also, if you want to replace DIV=30 with DIV=25 then you need to write that. I think it's clear that the substitution
s/DIV=/DIV=25/
will change DIV=30 into DIV=2530. Use this instead
s/DIV=30/DIV=25/

How do I change values with perl and regex/sed inside a file?

I'm pretty sure I am doing something stupid and I apologize for this ahead of time. I have looked at the one-liners that were suggested elsewhere on similar searches and I like the idea of them, I'm just not sure how to apply because it's not a direct swap. And if the answer is that this can't be done, then that is fine and I will script around that.
The problem: I have log files I need to send through a parser that requires the dates to be in YYYY-MM-DD. The files can be saved this way; however, some people prefer them in YYYY/MM/DD for their own viewing and send those to me. I can modify one or two dates with sed and this works beautifully; however, when there are 2-3+ years in the files, it would be nice not to have to do it manually for each date.
My code (I have left the debugging commands in place):
use strict;
use File::Copy;
use Getopt::Std;
my %ARGS = ();
getopts('f:v', \%ARGS);
my $file = $ARGS{f};
&main();
sub main($)
{
open (FIN, "<$file") || die ("Cannot open file");
print "you opened the file\n";
while (<FIN>) {
my $line = $_;
if ($line =~ /(\d*)\/(\d*)\/(\d*) /i) {
#print "you are in the if";
my $year = $1;
my $month = $2;
my $day = $3;
print $line;
print "\nyou have year $1\n";
print "you have month $2\n";
print "you have day $3\n";
s/'($1\/$2\/$3)/$1-$2-$3'/;
}
}
close FIN;
}
I can see that the regex is getting the right values into my variables but the original line is not being replaced in the file.
Questions:
1) Should this be possible to do within the same file or do I need to output it to a different file? Looking at other answers, same file should be fine.
2) Does the file need to be opened in another way or somehow set to be written to rather than merely running the replace command like I do with sed? <--I am afraid that the failure may be in here somewhere simple that I am overlooking.
Thanks!
You never write to the file. With sed, you'd use -i, and you can do exactly the same in Perl.
perl -i -pe's{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g' file
Or with a backup:
perl -i~ -pe's{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g' file
That's equivalent to
local $^I = ''; # Or for the second: local $^I = '~';
while (<>) {
s{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g;
print;
}
If you didn't want to rely on $^I, you'd have to replicate its behaviour.
for my $qfn (#ARGV) {
open($fh_in, '<', $qfn)
or do { warn("Can't open $ARGV: $!\n"); next; };
unlink($qfn)
or do { warn("Can't overwrite $ARGV: $!\n"); next; };
open(my $fh_out, '>', $qfn) {
or do { warn("Can't create $ARGV: $!\n"); next; };
while (<$fh_in>) {
s{(\d{4})/(\d{2})/(\d{2})}{$1-$2-$3}g;
print $fh_out $_;
}
}
perl -pi.bak -e 's|(\d{4})/(\d\d)/(\d\d)|$1-$2-$3|g;' input
Replace input with your log file name. A backup file input.bak will be created in case you ever need the original data.

How can I search multiple files for a string in Perl?

My question is probably simple but I'm a complete newbie. I want to search the contents of multiple text files for a particular phrase and then display the lines of the finds on screen. I've already learnt how to deal with a single file. For example, if I want to search for a word, say "Okay" in a text file named "wyvern.txt" in the root directory of F. The following code works:
#!/usr/bin/perl
$file = 'F:\wyvern.txt';
open(txt, $file);
while($line = <txt>) {
print "$line" if $line =~ /Okay/;
}
close(txt);
But what should I do if I want to search for the same phrase in two text files, say "wyvern' and "casanova" respectively? or how about all the files in the directory "novels" in the root directory of F.
Any help would be greatly appreciated.
Thanks in advance
Mike
Edit:
Haha, I finally figured out how to search all the files in a directory for a pattern match:)
The following code works great:
#!/usr/bin/perl
#files = <F:/novels/*>;
foreach $file (#files) {
open (FILE, "$file");
while($line= <FILE> ){
print "$line" if $line =~ /Okay/;
}
close FILE;
}
Extending the good answer provided by Jonathan Leffler:
The filename where the match was found is in $ARGV, and with a small change, the line number can be found in $.. Example:
while (<>) {
print "$ARGV:$.:$_" if /pattern/;
} continue {
close ARGV if eof; # Reset $. at the end of each file.
}
Furthermore, if you have a list of filenames and they're not on the commandline, you can still get the magic ARGV behavior. Watch:
{
local #ARGV = ('one.txt', 'two.txt');
while (<>) {
print "$ARGV:$.:$_" if /Okay/;
} continue {
close ARGV if eof;
}
}
Which is a generally useful pattern for doing line-by-line processing on a series of files, whatever it is -- even if I might recommend File::Grep or App::Ack for this specific problem :)
On a system where command line arguments are properly expanded, you can use:
[sinan#host:~/test]$ perl -ne 'print "$.:$_" if /config/' *
1:$(srcdir)/config/override.m4
The problem with Windows is:
C:\Temp> perl -ne "print if /perl/" *.txt
Can't open *.txt: Invalid argument.
On Windows, you could do:
C:\Temp> for %f in (*.txt) do perl -ne "print if /perl/" %f
But, you might just want to use cmd.exe builtin findstr or the grep command line tool.
The easiest way is to list the files on the command line, and then simply use:
while (<>)
{
print if m/Okay/;
}
File::Grep is what you need here
Just a tweak on your line: <F:/novels/*>, I prefer to use the glob keyword - it works the same in this context and avoids the chances of confusing the many different uses of angle brackets in perl. Ie:
#files = glob "F:/novels/*";
See perldoc glob for more.
put the files in a for loop, or something along those lines:
i.e.
for $file ('F:\wyvern.txt','F:\casanova.txt') {
open(TXT, $file);
while($line = <txt>) {
print "$line" if $line =~ /Okay/;
}
close TXT;
}
Okay, I'm a complete dummie. But to sum up, I now can search one single text file or multiple text files for a specified string. I'm still trying to figuring out how to deal with all the files in one folder.
the following codes work.
Code 1:
#!/usr/bin/perl
$file = 'F:\one.txt';
open(txt, $file);
while($line = <txt>) {
print "$line" if $line =~ /Okay/;
}
close(txt);
Code 2:
#!/usr/bin/perl
{
local #ARGV = ('F:\wyvern.txt', 'F:\casanova.txt');
while (<>) {
print "$ARGV:$.:$_" if /Okay/;
} continue {
close ARGV if eof;
}
}
Thanks again for your help. I really appreciate it.

How do I read the contents of a small text file into a scalar in Perl?

I have a small text file that I'd like to read into a scalar variable exactly as it is in the file (preserving line separators and other whitespace).
The equivalent in Python would be something like
buffer = ""
try:
file = open("fileName", 'rU')
try:
buffer += file.read()
finally:
file.close()
except IOError:
buffer += "The file could not be opened."
This is for simply redisplaying the contents of the file on a web page, which is why my error message is going into my file buffer.
From the Perl Cookbook:
my $filename = 'file.txt';
open( FILE, '<', $filename ) or die 'Could not open file: ' . $!;
undef $/;
my $whole_file = <FILE>;
I would localize the changes though:
my $whole_file = '';
{
local $/;
$whole_file = <FILE>;
}
As an alternative to what Alex said, you can install the File::Slurp module (cpan -i File::Slurp from the command line) and use this:
use File::Slurp;
# Read data into a variable
my $buffer = read_file("fileName");
# or read data into an array
my #buffer = read_file("fileName");
Note that this dies (well... croaks, but that's just the proper way to call die from a module) on errors, so you may need to run this in an eval block to catch any errors.
If I don't have Slurp or Perl6::Slurp near by then I normally go with....
open my $fh, '<', 'file.txt' or die $!;
my $whole_file = do { local $/; <$fh> };
There is a discussion of the various ways to read a file here.
I don't have enough reputation to comment, so I apologize for making this another post.
# Harold Bamford: $/ should not be an obscure variable to a Perl programmer. A beginner may not know it, but he or she should learn it. The join method is a poor choice for the reasons stated in the article linked by hackingwords above. Here's the relevant quotation from the article:
That needlessly splits the input file into lines (join provides a list context to ) and then joins up those lines again. The original coder of this idiom obviously never read perlvar and learned how to use $/ to allow scalar slurping.
You could do something like:
$data_file="somefile.txt";
open(DAT, $data_file);
#file_data = <DAT>;
close(DAT);
That'll give you the file contents in an array, that you can use for whatever you want, for example, if you wanted each individual line, you could do something like:
foreach $LINE (#file_data)
{
dosomethingwithline($LINE);
}
For a full usage example:
my $result;
$data_file = "somefile.txt";
my $opened = open(DAT, $data_file);
if (!$opened)
{
$result = "Error.";
}
else
{
#lines = <DAT>;
foreach $LINE (#lines)
{
$result .= $LINE;
}
close(DAT);
}
Then you can use $result however you need. Note: This code is untested, but it should give you an idea.
I'd tweak draegtun's answer like this, to make it do exactly what was being asked:
my $buffer;
if ( open my $fh, '<', 'fileName' ) {
$buffer = do { local $/; <$fh> };
close $fh;
} else {
$buffer = 'The file could not be opened.';
}
Just join all lines together into a string:
open(F, $file) or die $!;
my $content = join("", <F>);
close F;
(It was previously suggested to use join "\n" but that will add extra newlines. Each line already has a newline at its end when it's read.)