I have a very straitforward question i couldn't find an answer for it.
Say we have a section of code that performs a common task (e.g., a sub), and the output of that code should be directed to a specific file handle based on some criteria.
Is it possible to copy the target file handle to a local variable? if yes how?
e.g.,
my $key;
my $tempFh;
my $targetFh1 = open (...);
my $targetFh2 = open (...);
if ($key eq "1")
{
$tempFh = $targetFh1;
}
else
{
$tempFh = $targetFh2;
}
#perform the common activity
print $tempFh "common activity\n";
Yes.
The only issue is the syntax of open.
my $targetFh1 = open (...);
my $targetFh2 = open (...);
should be
open(my $targetFh1, ...) or die $!;
open(my $targetFh2, ...) or die $!;
The rest is fine.
my $fh;
if ($key eq '1') {
$fh = $targetFh1;
} else {
$fh = $targetFh2;
}
print $fh "common activity\n";
(The word temp is completely meaningless, so I removed it.)
Another syntax you could use is
my $fh = $key eq '1' ? $targetFh1 : $targetFh2;
print $fh "common activity\n";
Or even
print { $key eq '1' ? $targetFh1 : $targetFh2 } "common activity\n";
But unless the print is in a loop and $key can change from loop pass to loop pass, there's no reason to open both files like that. You could simply use
my $fh;
if ($key eq '1') {
open($fh, ...) or die $!;
} else {
open($fh, ...) or die $!;
}
print $fh "common activity\n";
Yes.
Use 3 arg open with a lexical filehandle:
open ( my $output_fh, ">", $output_filename ) or die $!;
Assign the filehandle:
my $temp_fh_ref = $output_fh;
print {$temp_fh_ref} "Some text";
Works exactly like you'd expect. (Just bear in mind that if you close one, you'll close both)
Related
I'm trying to write a Perl script to take a list of IPv4 aggregates and another list of addresses and using NetAddr::IP to take each IP and compare to the list of aggregates to see if it belongs to any of the aggregates. I need to find which ones are not part of any of the list of aggregates I have.
I finally got past all of the Perl errors and now I'm getting some kind of error with the NetAddr::IP module it appears. Can anyone assist?
Here is the error I'm getting:
Can't call method "network" without a package or object reference at blib/lib/NetAddr/IP.pm (autosplit into blib/lib/auto/NetAddr/IP/compactref.al) line 1075.
And here is the code I'm using:
#!/usr/bin/perl
use strict;
use NetAddr::IP;
my $fh = ();
my $sfile = "/home/dkenne201/ex-addresses.txt";
my $afile = "/home/dkenne201/aggs.txt";
my #space;
my #ips;
my $ip;
open($fh, "<", $sfile)
or die "Failed to open file: $!\n";
while(<$fh>) {
chomp;
push #space, $_;
}
close $fh;
open($fh, "<", $afile)
or die "Failed to open file: $!\n";
while(<$fh>) {
chomp;
push #ips, $_;
}
close $fh;
for my $netblock (NetAddr::IP::compact #space)
{
for $ip (map { new NetAddr::IP->new($_) } #ips)
{
if ($ip->within($netblock)) {
print "$ip found within $netblock\n";
}
else {
print "$ip not found within $netblock\n";
}
}
}
Here is an example of the format in the text files that contain the data as well.
Aggs example:
1.1.0.0/16
2.2.0.0/18
Addresses example:
1.1.1.1
192.168.2.3
5.2.3.4
You're calling new twice.
for $ip (map { new NetAddr::IP->new($_) } #ips)
Your code can be cleaned up to the following:
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
use NetAddr::IP;
my $sfile = "/home/dkenne201/ex-addresses.txt";
my $afile = "/home/dkenne201/aggs.txt";
my #netblocks = do {
open my $fh, "<", $sfile;
my #space = <$fh>;
chomp #space;
map {NetAddr::IP->new($_)} #space;
};
open my $fh, "<", $afile;
while (<$fh>) {
chomp;
my $ip = NetAddr::IP->new($_);
if (my ($netblock) = grep {$ip->within($_)} #netblocks) {
print "$_ found within $netblock\n";
} else {
print "$_ not found\n";
}
}
close $fh;
Outputs:
1.1.1.1 found within 1.1.0.0/16
192.168.2.3 not found
5.2.3.4 not found
Thanks to Miller for the improved version of my code. I am just re-posting with the $afile and $sfile variables swapped so we are looking for ips within aggs and not aggs within ips (which broke the earlier code in my original post as well). Works perfectly for me with the below code.
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
use NetAddr::IP;
my $sfile = "ex-addresses.txt";
my $afile = "aggs.txt";
my #netblocks = do {
open my $fh, "<", $afile;
my #space = <$fh>;
chomp #space;
map {NetAddr::IP->new($_)} #space;
};
open my $fh, "<", $sfile;
while (<$fh>) {
chomp;
my $ip = NetAddr::IP->new($_);
if (my ($netblock) = grep {$ip->within($_)} #netblocks) {
print "$_ found within $netblock\n";
} else {
print "$_ not found\n";
}
}
close $fh;
I want to make a tool to classify each line in input file to several files
but it seems have some problem in naming a filehandler so I can't go ahead , how do I solve?
here is my program
ARGV[0] is the input file
ARGV[1] is the number of classes
#!/usr/bin/perl
use POSIX;
use warnings;
# open input file
open(Raw,"<","./$ARGV[0]") or die "Can't open $ARGV[0] \n";
# create a directory class to store class files
system("mkdir","Class");
# create files for store class informations
for($i=1;$i<=$ARGV[1];$i++)
{
# it seems something wrong in here
open("Class$i",">","./Class/$i.class") or die "Can't create $i.class \n";
}
# read each line and random decide which class to store
while( eof(Raw) != 1)
{
$Line = readline(*Raw);
$Random_num = ceil(rand $ARGV[1]);
for($k=1;$k<=$ARGV[1];$k++)
{
if($Random_num == $k)
{
# Store to the file
print "Class$k" $Line;
last;
}
}
}
for($h=1;$h<=$ARGV[1];$h++)
{
close "Class$h";
}
close Raw;
thanks
Later I use the advice provided by Bill Ruppert
I put the name of filehandler into array , but it seems appear a syntax bug , but I can't correct it
I label the syntax bug with ######## A syntax error but it looks quite OK ########
here is my code
#!/usr/bin/perl
use POSIX;
use warnings;
use Data::Dumper;
# open input file
open(Raw,"<","./$ARGV[0]") or die "Can't open $ARGV[0] \n";
# create a directory class to store class files
system("mkdir","Class");
# put the name of hilehandler into array
for($i=0;$i<$ARGV[1];$i++)
{
push(#Name,("Class".$i));
}
# create files of classes
for($i=0;$i<=$#Name;$i++)
{
$I = ($i+1);
open($Name[$i],">","./Class/$I.class") or die "Can't create $I.class \n";
}
# read each line and random decide which class to store
while( eof(Raw) != 1)
{
$Line = readline(*Raw);
$Random_num = ceil(rand $ARGV[1]);
for($k=0;$k<=$#Name;$k++)
{
if($Random_num == ($k+1))
{
print $Name[$k] $Line; ######## A syntax error but it looks quite OK ########
last;
}
}
}
for($h=0;$h<=$#Name;$h++)
{
close $Name[$h];
}
close Raw;
thanks
To quote the Perl documentation on the print function:
If you're storing handles in an array or hash, or in general whenever you're using any expression more complex than a bareword handle or a plain, unsubscripted scalar variable to retrieve it, you will have to use a block returning the filehandle value instead, in which case the LIST may not be omitted:
print { $files[$i] } "stuff\n";
print { $OK ? STDOUT : STDERR } "stuff\n";
Thus, print $Name[$k] $Line; needs to be changed to print { $Name[$k] } $Line;.
How about this one:
#! /usr/bin/perl -w
use strict;
use POSIX;
my $input_file = shift;
my $file_count = shift;
my %hash;
open(INPUT, "<$input_file") || die "Can't open file $input_file";
while(my $line = <INPUT>) {
my $num = ceil(rand($file_count));
$hash{$num} .= $line
}
foreach my $i (1..$file_count) {
open(OUTPUT, ">$i.txt") || die "Can't open file $i.txt";
print OUTPUT $hash{$i};
close OUTPUT;
}
close INPUT;
I want to do the same thing as
open MYFILE, ">", "data.txt";
print MYFILE "Bob\n";
but instead in class variable like
sub _init_tmp_db
{
my ($self) = #_;
open $$self{tmp_db_fh}, ">", "data.txt";
print $$self{tmp_db_fh} "Bob\n";
}
It gave me this error : 'String found where operator expected near "Bob\n"'
what should I do?
From the print manpage:
If you're storing handles in an array or hash, or in general whenever
you're using any expression more complex than a bareword handle or a
plain, unsubscripted scalar variable to retrieve it, you will have to
use a block returning the filehandle value instead.
You should be using:
print { $$self{tmp_db_fh} } "Bob\n";
This code won't work under use strict. To fix it just use a my variable:
open my $fh, ">", "data.txt" or die $!;
$$self{tmp_db_fh} = $fh;
print { $$self{tmp_db_fh} } "Bob\n";
You should the IO::File module instead.
use IO::File;
my $file = IO::File->new;
$file->open("> data.txt");
print_something($file);
sub print_something {
my ($file) = #_;
$file->print("hello world\n");
}
Or in your example function:
use IO::File;
# ...
sub _init_tmp_db
{
my ($self) = #_;
$self{tmp_db_fh} = IO::File->new;
$self{tmp_db_fh}->open(">", "data.txt");
$self{tmp_db_fh}->print"Bob\n";
}
(note, you can still non -> based calls too, but I wrote the above
using the more traditional ->open() type calls.)
Filehandles can only be scalars.
But $$self{tmp_db_fh} is either an open filehandle (to data.txt) then this would work:
sub _init_tmp_db
{
my ($self) = #_;
my $filehandle = $$self{tmp_db_fh} ;
print $filehandle "Bob\n";
}
or you open the filehandle inside _init_tmp_db
sub _init_tmp_db
{
my ($self) = #_;
open my $filehandle , ">", "data.txt" or die "Cannot open data.txt" ;
print $filehandle "Bob\n";
}
But providing a string in $$self{tmp_db_fh} (like 'FILEHANDLE') won't work.
This is easily solved by creating a variable for a file handle:
sub _init_tmp_db {
my $self = shift;
my $fh;
open $fh, ">", "data.txt"
$self->{temp_db_fh} = $fh;
# Sometime later...
$fh = $self-{temp_db_hf};
print $fh "Bob\n";
}
This is an issue because the way the print syntax is parsed and the early sloppiness of the syntax. The print statement has really two separate formats: Format #1 is that the you're simply passing it stuff to print. Format #2 says that the first item may be a file handle, and the rest is the stuff you want to print to the file handle. If print can't easily determine that the first parameter is a file handle, it fails.
If you look at other languages, they'll use a parameter for passing the file handle, and maybe the stuff to print. Or in object oriented languages, they'll overload >> for the file handle parameter. They'll look something like this:
print "This is my statement", file=file_handle;
or
print "This is my statement" >> file_handle;
You might be able to munge the syntax to get away from using a variable. However, it doesn't make the program more efficient or more readable, and may simply make the program harder to maintain. So, just use a variable for the file handle.
You said class in your title. I assume that you are interested in writing a fully fledge object oriented package to do this. Here's a quick example. Notice in the write subroutine method I retrieve the file handle into a variable and use the variable in the print statement.
#! /usr/bin/env perl
#
use strict;
use warnings;
#######################################################
# MAIN PROGRAM
#
my $file = File->new;
$file->open("OUTPUT") or
die "Can't open 'OUTPUT' for writing\n";
$file->write("This is a test");
#
#######################################################
package File;
use Carp;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub open {
my $self = shift;
my $file = shift;
my $fh;
if (defined $file) {
$self->{FILE} = $file;
open ($fh, ">", $file) and $self->_fh($fh);
}
return $self->_fh;
}
sub _fh {
my $self = shift;
my $fh = shift;
if (defined $fh) {
$self->{FH} = $fh;
}
return $self->{FH};
}
sub write {
my $self = shift;
my $note = shift;
my $fh = $self->_fh;
print $fh $note . "\n";
return
}
I wonder if any module exist that can automate file numbering process.
If i try open "foo.bar" and it exists i open "foo_1.bar" without race condition.
What if two apps try open some file. Open fail or they get filehandles with diferent number?
Very thx for help.
I don't know of a canned module to do this off the top of my head, but the basic idea if you want a sequential file name is:
use Fcntl;
use Errno;
$seq = "";
until (defined ($fh = sysopen("foo".$seq.".bar", O_WRONLY|O_CREAT|O_EXCL, 0600))) {
last if $! != EEXIST;
$seq eq '' && $seq = '_0';
$seq =~ s/(\d+)/$1 + 1/e;
}
# if !defined $fh then $! contains the error, otherwise "foo".$seq.".bar" is created
Opens unique file name for writing. Return array ref to IO::File ref and writing name.
If fail return undef. Work with warnings and strict.
use Fcntl;
use Errno;
use IO::File;
sub open_unique {
my $file = shift || '';
unless ($file =~ /^(.*?)(\.[^\.]+)$/) {
print "Bad file name: '$file'\n";
return;
}
my $io;
my $seq = '';
my $base = $1;
my $ext = $2;
until (defined ($io = IO::File->new($base.$seq.$ext
,O_WRONLY|O_CREAT|O_EXCL))) {
last unless $!{EEXIST};
$seq = '_0' if $seq eq '';
$seq =~ s/(\d+)/$1 + 1/e;
}
return [$io,$base.$seq.$ext] if defined $io;
}
You might want to look at File::Temp.
Something like:
($fh, $filename) = tempfile('foo_XXXX', SUFFIX => '.bar');
print $fh "Some data\n";
close($fh) or die;
I have the following code:
use strict;
my $org_file_hash = {
'S6' => '/path/to/file/filename.txt_S6',
'S8' => '/path/to/file/filename.txt_S8',
'S2' => '/path/to/file/filename.txt_S2',
'S0' => '/path/to/file/filename.txt_S0',
'S00' => '/path/to/file/filename.txt_S00'
};
my $filehandles;
for(keys %{$org_file_hash})
{
my $key=$_;
open(my $key,">",$org_file_hash->{$key}) || die "Cannot open ".$org_file_hash->{$key}." for writing: $!";
push(#{$filehandles},$key);
}
In the latter part of the code, I get $org as "S2".
my $org="S2";
Based on $org I will decide the file I need to print to and in this case it is /path/to/file/filename.txt_S2.
To achieve this, I am doing following, but it does not work:
my $org="S2";
print {$org} "hello world\n";
I get the following error:
Can't use string ("S2") as a symbol ref while "strict refs" in use at new_t.pl line 22.
Please help.
Use $filehandles as a hash (or hashref) instead of an arrayref, as such:
my $filehandles = {};
for my $key (keys %{$org_file_hash})
{
# my $key=$_; # redundant
open( my $fh, '>', $org_file_hash->{$key} )
or die "Cannot open ".$org_file_hash->{$key}." for writing: $!";
$filehandles->{$key} = $fh;
}
# later...
my $org = 'S2';
print { $filehandles->{$org} } "Hello, world.\n";
At the end, don't forget to iterate over keys %{$filehandles} and close your opened files, too.
Use a hash:
my $filehandles = {};
for my $key (keys %{$org_file_hash}) {
open my $fh, ">", $org_file_hash->{$key} or die $!;
$filehandles->{$key} = $fh;
}
my $org="S2";
print {$filehandles->{$org}} "hello world\n";
BTW, if you use the open my $fh, ... form of open, $fh should be undefined. Otherwise, its value is used as the name of the real filehandle wanted. This is considered a symbolic reference, so the script won't compile under "use strict 'refs'".