In Perl, how can I check if a file is locked? - perl

How exactly would I check to see if a file is locked exclusively? I have this function but it is returning 1 no matter what I do:
sub is_file_locked
{
my $theFile;
my $theRC;
($theFile) = #_;
$theRC = open(HANDLE, $theFile);
$theRC = flock(HANDLE, LOCK_EX|LOCK_NB);
close(HANDLE);
return !$theRC;
}

You have opened $theFile in read mode and LOCK_EX isn't meant to be used that way.
Note that the fcntl(2) emulation of flock(3) requires that
FILEHANDLE be open with read intent to use LOCK_SH and requires
that it be open with write intent to use LOCK_EX.

First off, you should check if open succeeded.
Also, you should check if you can get a shared lock. flock with LOCK_EX would (I think) fail, if there is a shared lock on the file.
However, the file can become locked between the check and the return, creating a race condition, so such a function is of dubious value.
#!/usr/bin/perl
use strict; use warnings;
use Fcntl qw( :flock );
print is_locked_ex($0)
? "$0 : locked exclusively\n"
: "$0 : not locked exclusively\n";
my $test_file = 'test.txt';
open my $fh, '>', $test_file
or die "Cannot open '$test_file' for writing: $!";
if ( flock $fh, LOCK_EX|LOCK_NB ) {
print is_locked_ex($test_file)
? "$test_file : locked exclusively\n"
: "$test_file : not locked exclusively\n";
}
close $fh or die "Cannot close '$test_file': $!";
sub is_locked_ex {
my ($path) = #_;
die "Not a plain file: '$path'" unless -f $path;
return 1 unless open my $fh, '<', $path;
my $ret = not flock $fh, LOCK_SH | LOCK_NB;
close $fh
or die "Cannot close '$path': $!";
return $ret;
}

The final solution:
flock($fh, LOCK_EX) or die "Cannot lock file - $!\n";
if ( is_file_locked($gTestQueuePath) ){ print "locked";} else { print "not locked";}
#1 = locked 0 = not locked
sub is_file_locked
{
my $theFile;
my $theRC;
($theFile) = #_;
$theRC = open(my $HANDLE, ">>", $theFile);
$theRC = flock($HANDLE, LOCK_EX|LOCK_NB);
close($HANDLE);
return !$theRC;
}
close $fh or die "Cannot close";

Related

How to get a POD section into a string?

How to get a POD section into a variable:
pod2usage(-verbose => 99, -sections => "DESCRIPTION"); # Goes on STDOUT
=head1 DESCRIPTION
A description
=cut
I just want to assign DESCRIPTION to a variable instead displaying it on STDOUT.
I am currently investigating this way. It is complicated and it doesn't work:
sub pod2scalar {
use File::Temp 'tempfile';
my ($fh, $filename) = tempfile(UNLINK => 1);
open OLDOUT, '>&STDOUT';
{
local *STDOUT;
open STDOUT, ">", $filename or warn "Can't open $filename: $!";
#pod2usage(#_); # Doesn't work... I don't know why...
print STDOUT "This is captured in \$str";
close STDOUT;
}
open STDOUT, '>&OLDOUT' or die "Can't restore stdout: $!";
close OLDOUT or die "Can't close OLDOUT: $!";
open $fh, "<", $filename or warn "Can't open $filename: $!";
my $str = do { local $/, <$fh> };
close $fh;
$str;
}
You can open a scalar variable for output by passing a reference to open in place of a file name
Then you can provide the file handle as the value of the -output option of pod2usage to get the data sent to your scalar variable
You will also want to set an -exitval of 'NOEXIT' so that you get a chance to use what you have captured
It would look like this
use Pod::Usage 'pod2usage';
sub pod2scalar {
open my $fh, '>', \my $text;
pod2usage(#_, -output => $fh, -exitval => 'NOEXIT');
$text;
}
There is an excellent perl library Capture::Tiny that simplifies saving stdout/stderr.
By default pod2usage exits the program, so you must specify -exitval => "noexit".
Here is a full working example:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Pod::Usage;
use Capture::Tiny ':all';
=head1 DESCRIPTION
A description
=cut
my $stdout = capture_merged {
pod2usage(-verbose => 99, -sections => "DESCRIPTION", -exitval => "noexit");
};
say "## Captured";
say $stdout;
__END__
This will output:
## Captured
Description:
A description

is it possible to copy a file handle to another variable?

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)

Can't call method "network" without a package or object reference at blib/lib/NetAddr/IP.pm

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;

In perl how can I print to a file whose filehandle is resolved at the runtime?

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'".

How can I automatically release resources RAII-style in Perl?

Say I have a resource (e.g. a filehandle or network socket) which has to be freed:
open my $fh, "<", "filename" or die "Couldn't open filename: $!";
process($fh);
close $fh or die "Couldn't close filename: $!";
Suppose that process might die. Then the code block exits early, and $fh doesn't get closed.
I could explicitly check for errors:
open my $fh, "<", "filename" or die "Couldn't open filename: $!";
eval {process($fh)};
my $saved_error = $#;
close $fh or die "Couldn't close filename: $!";
die $saved_error if $saved_error;
but this kind of code is notoriously difficult to get right, and only gets more complicated when you add more resources.
In C++ I would use RAII to create an object which owns the resource, and whose destructor would free it. That way, I don't have to remember to free the resource, and resource cleanup happens correctly as soon as the RAII object goes out of scope - even if an exception is thrown. Unfortunately in Perl a DESTROY method is unsuitable for this purpose as there are no guarantees for when it will be called.
Is there a Perlish way to ensure resources are automatically freed like this even in the presence of exceptions? Or is explicit error checking the only option?
I think that's what Scope::Guard was designed to help with.
#!/usr/bin/perl
use strict; use warnings;
use Scope::Guard;
my $filename = 'file.test';
open my $fh, '>', $filename
or die "Couldn't open '$filename': $!";
{
my $sg = Scope::Guard->new(
sub {
close $fh or die "Could not close";
warn "file closed properly\n";
}
);
process($fh);
}
sub process { die "cannot process\n" }
However, as #Philip notes in the comments, Scope::Guard utilizes the DESTROY method which creates some uncertainty as to when the scope exit code will be run. Modules such as Hook::Scope and Sub::ScopeFinalizer look fine as well although I have never used them.
I do like Try::Tiny for its clean interface and sheer simplicity and it will help you handle exceptions the correct way:
#!/usr/bin/perl
use strict; use warnings;
use Try::Tiny;
my $filename = 'file.test';
open my $fh, '>', $filename
or die "Couldn't open '$filename': $!";
try {
process($fh);
}
catch {
warn $_;
}
finally {
close $fh
and warn "file closed properly\n";
};
sub process { die "cannot process\n" }
My module Scope::OnExit is intended for exactly that.
The nice thing about lexical filehandles is that they'll get closed (and freed) when they go out of scope. So you can just do something like this:
{
# bare block creates new scope
open my $fh, "<", "filename" or die "Couldn't open filename: $!";
eval { process($fh) };
# handle exceptions here
close $fh or die "Couldn't close filename: $!";
}
# $fh is now out of scope and goes away automagically.