Passing file handle as function arg in Perl - perl

I would like to be able to have a function that prints to a file but that does not open the file -- instead an already open file handle should be passed to it. That way the file opening and closing only happen once in the calling block of code.
I tried:
sub teeOutput
{
my $str = $_[0];
my $hndl = $_[1];
#print to file
print $hndl $str;
#print to STDOUT
print $str;
}
and then when calling that
open(RPTHANDLE, ">", $rptFilePath) || die("Could not open file ".$rptFilePath);
&teeOutput('blahblah', RPTHANDLE);
&teeOutput('xyz', RPTHANDLE);
close(RPTHANDLE);
but that didn't work.
Any idea how to accomplish this?
Thanks

First, stop using global variables for file handles.
open(my $RPTHANDLE, ">", $rptFilePath)
or die("Could not open file $rptFilePath: $!\n");
Then... Well, there is no "then".
teeOutput($RPTHANDLE, 'blahblah');
teeOutput($RPTHANDLE, 'xyz');
close($RPTHANDLE);
Notes:
I reversed the argument to teeOutput to something saner.
I removed the directive (&) to override teeOutput's prototype. teeOutput doesn't even have one.
(But if you have to deal with globs, use teeOutput(\*STDERR, ...);.)

Related

Writing to a file inside if statement not working in Perl

I've looked around here a bit and found similar questions but not exactly. If there is one, I apologize and please point me to it.
I have the following code. I'm trying to create a csv file of simply an ID pulled from a filename and the filename itself. This is the ENTIRE script.
use strict;
use warnings;
use File::Find;
find( \&findAllFiles, '.');
exit;
sub findAllFiles {
my #fp1;
my #fp2;
my $patId;
my $filename;
my $testvar = "hello again";
$filename = $File::Find::name;
if ($filename =~ /\.pdf$/) {
open (my $fh, '>', 'filenames.csv') or die "Failed to open - $!\n";
print $fh "starting...$testvar\n" or die "Failed to print to file - $!\n";
#fp1 = split('/', $filename);
#fp2 = split('_', $fp1[-1]);
$patId = $fp2[-1];
$patId =~ s/\.pdf$//;
print "Adding $patId, file = $filename\n";
print $fh "$patId,$filename\n" or die "File print error: $!";
close $fh or warn "close failed! - $!";
}
return;
}
The line that prints to the screen, prints perfectly.
If I take the file open/close and the first print statement out of the if block, it prints that line into the file, but not the data inside the block.
I've tried every combo I can think of and it doesn't work. I've alternated between '>' and '>>' since it clearly needs the append since it's looping over filenames, but neither works inside the if block.
Even this code above doesn't throw the die errors! It just ignores those lines! I'm figuring there's something obvious I'm missing.
Quoting File::Find::find's documentation:
Additionally, for each directory found, it will chdir() into that directory
It means that when you open inside findAllFiles, you are potentially opening a file filenames.csv inside a subdirectory of your initial directory. You can run something like find . -name filenames.csv from your terminal, and you'll see plenty of filenames.csv. You can change this behavior by passing no_chdir option to find:
find( { wanted => \&findAllFiles, no_chdir => 1}, '.');
(and additionally changing > for >> in your open)
However, personally, I'd avoid repeatedly opening and closing filenames.csv when you could open it just once before calling find. If you don't want to have your filehandle globally defined, you can always pass it as an argument to findAllFiles:
{
open my $fh, '>', 'filenames.csv' or die "Failed to open 'filenames.csv': $!";
find(sub { findAllFiles($fh) }, '.')
}
sub findAllFiles {
my ($fh) = #_;
...
filenames.csv will be created in the directory where the pdf is found, since find() changes directories as it searches. If that's not what you want, use an absolute path to open it (or open it before calling find, which seems like a better idea).

Cannot decode! Invalid Base58 Character(s)!

I am trying to run
base58perl.pl
in my terminal using the following command:
perl base58perl.pl
but I get the following error:
Cannot decode! Invalid Base58 Character(s)!
Here's the code:
my $fileSrc = 'base58.txt';
open my $fhSrc, $fileSrc or die "Could not open $fileSrc: $!";
my $fileDest = 'hex.txt';
open( my $fhDest, '>>', $fileDest) or die "Could not open file $fileDest: $!";
while ( my $base58_encoded_address = <$fhSrc >) {
my $binary_address = decodebase58tohex($base58_encoded_address);
say $fhDest $binary_address;
}
close $fhSrc;
close $fhDest;
The content of base58.txt is a list of BTC address in base58 form.
I also have tried
chmod a+x base58perl.pl
perl base58perl.pl
base58.txt contents:
1E5PBfSaFawBy1RjBHkS6FDtCwXkYSsVTo
1DCgptTS2uY2occbVdW1qcVT72T75RXbyg
1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb
I still get the same error.
That error message comes from the unbase58 function in the code you have linked.
die "Cannot Decode! Invalid Base58 Character(s)!\n" unless $bitcoin_address =~ /^[1-9A-HJ-NP-Za-km-z]*$/;
That line checks if the input contains only characters of the character group [1-9A-HJ-NP-Za-km-z]. Since your input does, it must dislike something else.
My guess is that it disliked the newline characters at the end of your lines. You need to chomp them off before passing the value to decodebase58tohex.
while( my $base58_encoded_address = <$fhSrc>) {
chomp $base58_encoded_address;
my $binary_address = decodebase58tohex($base58_encoded_address);
say $fhDest $binary_address;
}
You probably need to remove whitespace. You appear to be passing only chunks of the string to the decode function at a time, which could also be a problem. Read the whole file into a var, remove any whitespace, then decode.
my $base58_encoded_address = do { local $/; <$fhSrc> };
$base58_encoded_address =~ s/\s+//g;
my $binary_address = decodebase58tohex($base58_encoded_address);
say $fhDest $binary_address;
my $fileSrc = 'base58.txt';
open my $fhSrc, $fileSrc or die "Could not open $fileSrc: $!";
my $fileDest = 'hex.txt';
open( my $fhDest, '>>', $fileDest) or die "Could not open file $fileDest: $!";
my #tmp = <$fhSrc>;
chomp #tmp;
for my $line (#tmp) {
print "decoding '$line'\n";
my $binary_address = decodebase58tohex($line);
say $fhDest $binary_address;
}
close $fhSrc;
close $fhDest;
As someone else mentioned I think your dealing with whitespaces.
chomp will take care of that for you.
The next thing to do is print the string you are trying to decode in quotes which will confirm your only decoding what you want to.
The script is now working properly, the problem was the base58.txt the file was created using notepad. I created a new file using a different text editor.

Open filehandle or assign stdout

I'm working in a program where the user can pass a -o file option, and output should be then directed to that file. Otherwise, it should go to stdout.
To retrieve the option I'm using the module getopt long, and that's not the problem. The problem is that I want to create a file handle with that file or assign stdout to it if the option was not set.
if ($opt) {
open OUTPUT, ">", $file;
} else {
open OUTPUT, # ???
}
That's because this way, later in my code I can just:
print OUTPUT "...";
Without worrying if OUTPUT is stdout or a file the user specified. Is this possible? If I'm doing a bad design here, please let me know.
This would be a good example on how to use select.
use strict;
use warnings;
use autodie;
my $fh;
if ($opt) {
open $fh, '>', $file;
select $fh;
}
print "This goes to the file if $opt is defined, otherwise to STDOUT."
Look at the open documentation. The easiest is to reopen STDOUT itself and not use a filehandle in your code.
if ($opt) {
open(STDOUT, ">", $file);
}
...
print "this goes to $file or STDOUT\n";
(Add some error checking of course.)
A constant item such as OUTPUT cannot be assigned. Using a variable such as $output works better. For example:
my ($output, $display_filename);
if ($opt)
{
if ($opt eq '-')
{
$display_filename = 'stdout';
$output = *STDOUT;
}
else
{
$display_filename = $opt;
open($output, '>', $opt) or
die("Cannot open $opt for writing: $!\n");
}
}
That way the program can print to standard output and/or to an output file:
print $output "This might go to a file\n";
print "Data written to $display_filename\n" if ($verbose);

Why doesn't my Perl blessed filehandle doesn't return true with `can('print')`'?

For some reason, I can't get filehandles working with Expect.pm's log_file method. I originally got help on How can I pass a filehandle to Perl Expect's log_file function?, where it was suggested that I use an IO::Handle filehandle to pass to the method. This seems to be a different issue, so I thought I'd start a new question.
This is the offending section of Expect.pm:
if (ref($file) ne 'CODE') {
croak "Given logfile doesn't have a 'print' method"
if not $fh->can("print");
$fh->autoflush(1); # so logfile is up to date
}
So, then, I tried this sample code:
use IO::Handle;
open $fh, ">>", "file.out" or die "Can't open file";
$fh->print("Hello, world");
if ($fh->can("print"))
{
print "Yes\n";
}
else
{
print "No\n";
}
When I run this, I get two (to my mind) conflicting items. A file with a single line that says 'Hello, world', and output of 'No'. To my mind, the $fh->can line should return true. Am I wrong here?
Odd, it looks like you need to create a real IO::File object to get the can method to work. Try
use IO::File;
my $fh = IO::File->new("file.out", ">>")
or die "Couldn't open file: $!";
IO::Handle doesn't overload the open() function, so you're not actually getting an IO::Handle object in $fh. I don't know why the $fh->print("Hello, world") line works (probably because you're calling the print() function, and when you do things like $foo->function it's equivalent to function $foo, so you're essentially printing to the filehandle like you'd normally expect).
If you change your code to something like:
use strict;
use IO::Handle;
open my $fh, ">>", "file.out" or die "Can't open file";
my $iofh = new IO::Handle;
$iofh->fdopen( $fh, "w" );
$iofh->print("Hello, world");
if ($iofh->can("print"))
{
print "Yes\n";
}
else
{
print "No\n";
}
...then your code will do as you expect. At least, it does for me!

How do I process the response as a file without using the :content_file option?

Example code:
my $ua = LWP::UserAgent->new;
my $response = $ua->get('http://example.com/file.zip');
if ($response->is_success) {
# get the filehandle for $response->content
# and process the data
}
else { die $response->status_line }
I need to open the content as a file without prior saving it to the disk. How would you do this?
You can open a fake filehandle that points to a scalar. If the file argument is a scalar reference, Perl will treat the contents of the scalar as file data rather than a filename.
open my $fh, '<', $response->content_ref;
while( <$fh> ) {
# pretend it's a file
}
Not quite a file, but here is a relevant SO question: What is the easiest way in pure Perl to stream from another HTTP resource?