Re: Open Filehandle once
by Corion (Patriarch) on Aug 24, 2017 at 11:55 UTC
|
To open a filehandle once the easiest way would be to have one central function for opening a file that caches the filehandle:
my %filehandles;
sub open_file {
my( $filename ) = @_;
if( ! $filehandles{ $filename }) {
open $filehandles{ $filename }, '>>', $filename
or die "Couldn't append to '$filename': $!";
};
return $filehandles{ $filename }
}
...
my $fh = open_file( 'path1' );
print $fh "Hello\n";
my $fh2 = open_file( 'path1' );
print $fh2 "world!\n";
As for your programming style, you are launching external processes several times to retrieve information. This can lead to inconsistent errors if a service comes up or goes down between your two queries. Better retrieve the information one into an array. Also, don't use an external grep command when Perl has a built-in grep:
my @services = `systemctl list-unit-files`;
if( my @smb = grep { /smb\.service/ } @services ) {
my $fh = open_file( 'path1' );
print DATEI "chkconfig smb: OK\n";
print DATEI "Output:_\n " . join( "", @smb) . \n\n;
};
Update: The return statement in the function was wrong. Spotted by soonix++ | [reply] [d/l] [select] |
|
|
| [reply] |
|
|
| [reply] |
Re: Open Filehandle once
by Laurent_R (Canon) on Aug 24, 2017 at 12:34 UTC
|
Hi Dunkellbusch,
judging from the code you presented (and assuming that you really have only two files where to write to), the simplest might be to open two files (using simply two different filehandles) at the beginning of your script and to print to the appropriate filehandle when needed.
If you have more than two files, then you might want to construct an array or a hash of file handles.
For this to be easily made, it would be much better to use lexical filehandles, with a syntax such as:
open my $DATEI, ">>", "path1" or die "failed to open path1 $!"; # if
+you open it only once, then ">" might be better than ">>" but I don't
+ know the wider context
In fact, in general, you should use lexical filehandles and the three-argument syntax for open shown just above.
With such lexical filehandles, it becomes fairly easy to store them in an array or a hash.
| [reply] [d/l] [select] |
Re: Open Filehandle once (updated)
by thanos1983 (Parson) on Aug 24, 2017 at 14:58 UTC
|
Hello Dunkellbusch,
Welcome to the Monastery. The monks have already provided you with solutions to your problem, but I would like to add one more. You can use the module File::Slurp/append_file, so you can minimize your code to simply one call in every time you need it, no closing file handles etc.
But to be honest I would combine the module with the nice solution of fellow monk Corion, executable sample bellow below (change grep from /enable/ to /smb\.service/):
#!/usr/bin/perl
use strict;
use warnings;
use File::Slurp;
my $filePath = 'test_1.txt';
my @services = `systemctl list-unit-files`;
if( my @smb = grep { /enabled/ } @services ) {
map { s/\s+\z// } @smb; # remover white space
append_file( $filePath, "chkconfig smb: OK\n");
append_file( $filePath, map { "$_\n" } @smb);
# append_file( $filePath, "\n"); # if you want to append multiple
+times
}
Update: Based on comments try to avoid File::Slurp, for simple implementations (read, write, etc) you can use File::Slurper, unfortunately there the author has not added the append proposal. I was looking online and I found the IO::All. Never used it before but I read that it is really really good.
Sample of updated solution, based on commends:
#!/usr/bin/perl
use strict;
use warnings;
use IO::All -utf8; # Turn on utf8 for all io
my $path = 'file.txt';
my @services = `systemctl list-unit-files`;
if( my @smb = grep { /enabled/ } @services ) {
s{ ^\s* | \s*$ }{}gx for @smb;
io($path)->appendln(@smb);
}
Output:
Hope this helps, BR.
Seeking for Perl wisdom...on the process of learning...not there...yet!
| [reply] [d/l] [select] |
|
|
| [reply] |
|
|
| [reply] [d/l] [select] |
|
|
Hello thanos1983,
Please do not recommend File::Slurp. It's broken and wrong.
Also please do not recommend using map in void context. This is considered bad practice by many programmers since you are only making the call for its side-effects whilst throwing away its output.
The way forward always starts with a minimal test.
| [reply] |
|
|
"Also please do not recommend using map in void context."
In general, I agree with this statement.
"This is considered bad practice by many programmers since you are only making the call for its side-effects whilst throwing away its output."
However, I disagree with this reason.
The issue with "throwing away its output" was resolved,
about 14 years ago[perlhist], in Perl 5.8.1.
From "perl581delta: Miscellaneous Enhancements":
"map in void context is no longer expensive. map is now context aware, and will not construct a list if called in void context."
The reason I would give for not using map in void context is based on efficiency.
I've had reason to benchmark map vs. for on a number of occasions over the years:
I've always found for to be measurably faster than map;
and, unsurprisingly, map EXPR is faster than map BLOCK.
Here's a very quick and dirty benchmark I just wrote to show this:
#!/usr/bin/env perl
use strict;
use warnings;
use Benchmark 'cmpthese';
my @x = 0 .. 99;
cmpthese 1e6 => {
map_expr => sub { my @y = @x; map ++$_, @y; return },
map_block => sub { my @y = @x; map { ++$_ } @y; return },
for => sub { my @y = @x; ++$_ for @y; return },
};
I ran this five times. All results were much the same. This one was roughly in the middle:
Rate map_block map_expr for
map_block 75019/s -- -44% -47%
map_expr 134953/s 80% -- -5%
for 141844/s 89% 5% --
Benchmark run using:
$ perl -v | head -2 | tail -1
This is perl 5, version 26, subversion 0 (v5.26.0) built for darwin-th
+read-multi-2level
| [reply] [d/l] [select] |
|
|
|
|
|
|
|
|
| [reply] [d/l] [select] |
|
|
use Path::Tiny (); # otherwise 'path' will be imported
...
Path::Tiny::path($filePath)->append(map { "$_\n" } 'chkconfig smb: OK'
+, @smb))
| [reply] [d/l] |
Re: Open Filehandle once
by shmem (Chancellor) on Aug 25, 2017 at 09:33 UTC
|
You don't need to use a bareword for a filehandle. You can use a lexical variable as a filehandle, and store its value in a hash table keyed with the associated file path. In a writing sub, you check whether the path exists in the hash, and open it if it doesn't:
my %fh; # key: path, value: filehandle
if ($cond1) {
my $text = "text1\n";
$text .= somefunc(); # whatever
append($path1, $text);
} elsif ($cond2) {
my $text = "some other text\n";
append($path2, $text);
} elsif (...) {
...
} else {
...
}
# when done:
close $fh{$_} for keys %fh;
sub append {
my ($path, $text) = @_;
unless ( exists $fh{$path} ) {
open my $fh, '>>', $path or die "Can't append to $path: $!\n";
$fh{$path} = $fh;
}
print {$fh{$path}} $text;
}
That way, you only open files as needed. You could also use another hash for the paths, keyed with some meaningful strings:
my %paths = (
LOG => '/path/to/log.txt',
ERROR => '/path/to/errorlog.txt',
INFO => '/path/to/info.txt',
);
my %fh; # key: token, value: filehandle
...
if (...) {
$text = ...
append('LOG',$text);
} else {
...
}
sub append {
my ($token, $text) = @_;
my$ fh
unless (exists $fh{$token} ) {
open my $fh, '>>', $paths{$token} or die $!;
$fh{$token} = $fh;
}
print {$fh{$token}} $text;
}
so you need not pass the path every time to the append() subroutine, only the key upon which it is stored in your path hash.
perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
| [reply] [d/l] [select] |
Re: Open Filehandle once
by zarath (Beadle) on Aug 25, 2017 at 14:01 UTC
|
Hello,
I'm pretty new to Perl too, but I have just finished a code that contains some elements that might be useful to you, so I may aswell share it.
#!perl
use strict; # because we should
use warnings; # because we should
use autodie; # die if problem reading or writing a file
my $input = 'C:/MIG6/Uploadfiles/Input/AllRAW.txt'; # where to get dat
+a
my $dir = 'C:/MIG6/Uploadfiles/Output'; # where to put the new files
open FILEin, $input;
binmode(FILEin);
while (my $line = <FILEin>){
if ($line =~ /^"EE",/){
my $search = index($line,'54250213200');
$search += 16;
my $dgo = substr($line,$search,13);
$search += 16;
my $ean = substr($line,$search,18);
my $outfile = $dir.'/e07_'.$dgo.'_err_b2c_MIG6testing_'.$ean.'
+.txt';
open FILEout, '>'.$outfile;
print FILEout $line;
} else {
print FILEout $line;
}
}
close FILEout;
close FILEin;
exit 0; # all done, let's have some cake!
Every time a line is found in FILEin that starts with the string "EE", a new file is created and that line is written to the new file, the following lines are also written to that same file until another line that starts with "EE", is found again, which triggers the creation of a new file and so on.
I don't use close anywhere inside the while-block and it works, so I guess I'm doing something right :-)
Not sure if the 'close FILEout;' is of any use outside the while-block, it just looks cleaner to me.
If the code itself brings nothing useful to the table, then only consider the tip of using 'use autodie;', so you can forget about typing 'or die $!;' every time. | [reply] [d/l] |
Re: Open Filehandle once
by Dunkellbusch (Initiate) on Aug 29, 2017 at 12:03 UTC
|
| [reply] |
| A reply falls below the community's threshold of quality. You may see it by logging in. |