Perl appending (s)printf output to string - perl

I'm parsing some data and organizing it, and now I need to capture it inside a variable.
I've never used printf or sprintf before this.
I'm using printf in a manner like this to organize the data:
printf("%-30s %18s %18s\n", "$a", "$b", "$c\n");
Now I have a variable that's storing a string, and I want to append the organized data to the variable $result.
I tried something like
$result.printf("%-30s %18s %18s\n", "$a", "$b", "$c\n");
and it doesn't work. I tried sprintf too.
Any ideas?
Thanks, S

printf outputs the constructed string to the specified handle (or the current default if omitted) and returns a boolean which indicates whether an IO error occurred or not. Not useful. sprintf returns the constructed string, so you want this.
To concatenate two strings (append one to another), one uses the . operator (or join)
$result . sprintf(...)
But you said this doesn't work. Presumably, it's because you also want to store the produced string in $result, which you can do using
$result = $result . sprintf(...);
or the shorter
$result .= sprintf(...);

Don't know what you mean by "tried sprintf too", because there's no reason it would not work if you do it right. Although that syntax you showed does not look much like perl, more like python or ruby?
my $foo = sprintf("%-30s %18s %18s\n", "$a", "$b", "$c\n");
$result .= $foo;

Related

Deferencing hash of hashes in Perl

Sorry for this long post, the code should be easy to understand for veterans of Perl. I'm new to Perl and I'm trying to figure out this bit of code:
my %regression;
print "Reading regression dir: $opt_dir\n";
foreach my $f ( glob("$opt_dir/*.regress") ) {
my $name = ( fileparse( $f, '\.regress' ) )[0];
$regression{$name}{file} = $f;
say "file $regression{$name}{file}";
say "regression name $regression{$name}";
say "regression name ${regression}{$name}";
&read_regress_file( $f, $regression{$name} );
}
sub read_regress_file {
say "args #_";
my $file = shift;
my $href = shift;
say "href $href";
open FILE, $file or die "Cannot open $file: $!\n";
while ( <FILE> ) {
next if /^\s*\#/ or /^\s*$/;
chomp;
my #tokens = split "=";
my $key = shift #tokens;
$$href{$key} = join( "=", #tokens );
}
close FILE;
}
The say lines are things I added to debug.
My confusion is the last part of the subroutine read_regress_file. It looks like href is a reference from the line my $href = shift;. However, I'm trying to figure out how the hash that was passed got referenced in the first place.
%regression is a hash with keys of $name. The .regress files the code reads are simple files contains variables and their values in the form of:
var1=value
var2=value
...
So it looks like the line
my $name = (fileparse($f,'\.regress'))[0];
is creating the keys as scalars and the line
$regression{$name}{file} = $f;
actually makes $name into a hash.
In my debugging lines
say "regression name $regression{$name}";
prints the reference, for instance
regression name HASH(0x7cd198)
but
say "regression name ${regression}{$name}";
prints a name, like
regression name {filename}
with the file name inside the braces.
However, using
say "regression name $$regression{$name}";
prints nothing.
From my understanding, it looks like regression is an actual hash, but the references are the nested hashes, name.
Why does my deference test line using braces work, but the other form of dereferencing ($$) not work?
Also, why is the name still surrounded by braces when it prints? Shouldn't I be dereferencing $name instead?
I'm sorry if this is difficult to read. I'm confused which hash is actually referenced, and how to deference them if the reference is the nested hash.
This is a tough one. You've found some very awkward code that displays what may well be a bug in Perl, and you're getting confused over dereferencing Perl data structures. Standard Perl installations include the full set of documentation, and I suggest you take a look at perldoc perlreftut which is also available online at perldoc.com
The most obvious thing is that you are writing very old-fashioned Perl. Using an ampersand & to call a Perl subroutine hasn't been considered good practice since v5.8 was released fourteen years ago
I don't think there's much need to go beyond your clearly experimentatal lines at the start of the first for loop. Once you have understood this the rest should follow
say "file $regression{$name}{file}";
say "regression name $regression{$name}";
say "regression name ${regression}{$name}";
First of all, expanding data structure references within a string is unreliable. Perl tries to do what you mean, but it's very easy to write something ambiguous without realising it. It is often much better to use printf so that you can specify the embedded value separately. For instance
printf "file %s\n", $regression{$name}{file};
That said, you have a problem. $regression{$name} accesses the element of hash %regression whose key is equal to $name. That value is a reference to another hash, so the line
say "regression name $regression{$name}";
prints something like
regression name HASH(0x29348b0)
which you really don't want to see
Your first try $regression{$name}{file} accesses the element of the secondary hash that has the key file. That works fine
But ${regression}{$name} should be the same as $regression{$name}. Outside a string it is, but inside it's like ${regression} and {$name} are treated separately
There are really too many issues here for me to start guessing where you're stuck, especially without being able to talk about specifics. But it may help if I rewrite the initial code like this
my %regression;
print "Reading regression dir: $opt_dir\n";
foreach my $f ( glob("$opt_dir/*.pl") ) {
my ($name, $path, $suffix) = fileparse($f, '\.regress');
$regression{$name}{file} = $f;
my $file_details = $regression{$name};
say "file $file_details->{file}";
read_regress_file($f, $file_details);
}
I've copied the hash reference to $file_details and passed it to the subroutine like that. Can you see that each element of %regression is keyed by the name of the file, and that each value is a reference to another hash that contains the values filled in by read_regress_file?
I hope this helps. This isn't really a forum for teaching language basics so I don't think I can do much better
What I understand is that this:
$regression{$name}
represents a hashref, which looks like this:
{ file => '...something...'}
So, in order to dereference the hashref returned by $regression{$name}, you have to do something like:
%{ $regression{$name} }
In order to get the full hash.
In order to get the file property of the hash, do this:
$regression{$name}->{file}
Hope this helps.

A better variable naming system?

A newbie to programming. The task is to extract a particular data from a string and I chose to write the code as follows -
while ($line =<IN>) {
chomp $line;
#tmp=(split /\t/, $line);
next if ($tmp[0] !~ /ch/);
#tgt1=#tmp[8..11];
#tgt2=#tmp[12..14];
#tgt3=#tmp[15..17];
#tgt4=#tmp[18..21];
foreach (1..4) {
print #tgt($_), "\n";
}
I thought #tgt($_) would be interpreted as #tgt1, #tgt2, #tgt3, #tgt4 but I still get the error message that #tgt is a global symbol (#tgt1, #tgt2, #tgt3, #tgt4` have been declared).
Q1. Did I misunderstand foreach loop?
Q2. Why couldn't perl see #tgt($_) as #tgt1, #tgt2 ..etc?
Q2. From the experience this is probably a bad way to name variables. What would be a preferred way to name variables that have similar features?
Q2. Why couldn't perl see #tgt($_) as #tgt1, #tgt2 ..etc?
Q2. From the experience this is probably a bad way to name variables. What would be a preferred way to name variables that have similar features?
I'll asnswer both together.
#tgt($_) does NOT mean what you hope it means
First off, it's an invalid syntax (you can't use () after an array name, perl interpeter will produce a compile error).
What you're trying to do is access distinct variables by accessing a variable via an expression resulting in its name (aka symbolic references). This IS possible to do; but is typically a bad idea and poor-style Perl (as in, you CAN but you SHOULD NOT do it, without a very very good reason).
To access element $_ the way you tried, you use #{"tgt$_"} syntax. But I repeat - Do Not Do That, even if you can.
A correct idiomatic solution: use an array of arrayrefs, with your 1-4 (or rather 0-3) indexing the outer array:
# Old bad code: #tgt1=#tmp[8..11];
# New correct code:
$tgt[0]=[ #tmp[8..11] ]; # [] creates an array reference from a list.
# etc... repeat 4 times - you can even do it in a smart loop later.
What this does is, it stores a reference to an array slice into a zeroth element of a single #tgt array.
At the end, #tgt array has 4 elements , each an array reference to an array containing one of the slices.
Q1. Did I misunderstand foreach loop?
Your foreach loop (as opposed to its contents - see above) was correct, with one style caveat - again, while you CAN use a default $_ variable, you should almost never use it, instead always use named variables for readability.
You print the abovementioned array of arrayrefs as follows (ask separately if any of the syntax is unclear - this is a mid-level data structure handling, not for beginners):
foreach my $index (0..3) {
print join(",", #{ $tgt[$index]}) . "\n";
}

perl: printing object properties

I'm playing a bit with the Net::Amazon::EC2 libraries, and can't find out a simple way to print object properties:
This works:
my $snaps = $ec2->describe_snapshots();
foreach my $snap ( #$snaps ) {
print $snap->snapshot_id . " " . $snap->volume_id . "\n";
}
But if I try:
print "$snap->snapshot_id $snap->volume_id \n";
I get
Net::Amazon::EC2::Snapshot=HASH(0x4c1be90)->snapshot_id
Is there a simple way to print the value of the property inside a print?
$snap->volume_id is not a property, it is a method call. While you could interpolate a method call inside a string, it is exceedingly ugly.
To get all the properties of an object you can use the module Data::Dumper, included with core perl:
use Data::Dumper;
print Dumper($object);
Not in the way you want to do it. In fact, what you're doing with $snap->snapshot_id is calling a method (as in sub). Perl cannot do that inside a double-quoted string. It will interpolate your variable $snap. That becomes something like HASH(0x1234567) because that is what it is: a blessed reference of a hash.
The interpolation only works with scalars (and arrays, but I'll omit that). You can go:
print "$foo $bar"; # scalar
print "$hash->{key}"; # scalar inside a hashref
print "$hash->{key}->{moreKeys}->[0]"; # scalar in an array ref in a hashref...
There is one way to do it, though: You can reference and dereference it inside the quoted string, like I do here:
use DateTime;
my $dt = DateTime->now();
print "${\$dt->epoch }"; # both these
print "#{[$dt->epoch]}"; # examples work
But that looks rather ugly, so I would not recommend it. Use your first approach instead!
If you're still interested in how it works, you might also want to look at these Perl FAQs:
What's wrong with always quoting "$vars"?
How do I expand function calls in a string?
From perlref:
Here's a trick for interpolating a subroutine call into a string:
print "My sub returned #{[mysub(1,2,3)]} that time.\n";
The way it works is that when the #{...} is seen in the double-quoted
string, it's evaluated as a block. The block creates a reference to an
anonymous array containing the results of the call to mysub(1,2,3) .
So the whole block returns a reference to an array, which is then
dereferenced by #{...} and stuck into the double-quoted string. This
chicanery is also useful for arbitrary expressions:
print "That yields #{[$n + 5]} widgets\n";
Similarly, an expression that returns a reference to a scalar can be
dereferenced via ${...} . Thus, the above expression may be written
as:
print "That yields ${\($n + 5)} widgets\n";
Stick with the first sample you showed. It looks cleaner and is easier to read.
I'm answering this because it took me a long time to find this and I feel like other people may benefit as well.
For nicer printing of objects use Data::Printer and p():
use DateTime;
use Data::Printer;
my $dt = DateTime->from_epoch( epoch => time );
p($dt);
The PERL translator has limited depth perception within quotes. Removing them should solve the problem. Or just load the real values into a simple variable that you can print within the quotes. Might need to do that if you have objects which contain pointers to other objects:
SwissArmyChainSaw =/= PureMagic:
print("xxx".$this->{whatever}."rest of string\n");
The problem is that $snap is being interpolated inside the string, but $snap is a reference. As perldoc perlref tells us: "Using a reference as a string produces both its referent's type, including any package blessing as described in perlobj, as well as the numeric address expressed in hex."
In other words, within a string, you can't dereference $snap. Your first try was the correct way to do it.
I agree with most comment, stick to concatenation for easy reading. You can use
say
instead of print to spare of using the "\n".

Why does this subroutine work if I type out its arguments literally, but not if I give the arguments in the form of a variable?

I am using a perl package (Biomart), that includes a subroutine called addFilter(). That subroutine needs a couple of arguments, including one that needs to be of the format "nr:nr:nr"
If I use the subroutine as follows, it works fine:
$query->addFilter("chromosomal_region", ["1:1108138:1108138","1:1110294:1110294"]);
However, if I use it like this, it does not work:
my $string = '"1:1108138:1108138","1:1110294:1110294","1:1125105:1125105"';
$query->addFilter("chromosomal_region", ['$string']);
Since there are tens of thousands of those arguments that I construct in a for loop, I really need the second way to work... What could be causing this? I hope someone can help me out, many thanks in advance!
Because you seem to be trying to write in a language that's not Perl. '"this","that","another"' isn't an array, it's a string. And '$string' doesn't interpolate or include $string in any way because it uses single quotes. It just produces a string that starts with a dollar sign and ends with "string".
Something more like what you intend would be:
my #things = ("1:1108138:1108138","1:1110294:1110294","1:1125105:1125105");
$query->addFilter("chromosomal_region", \#things);
-or-
$query->addFilter("chromosomal_region", [ #things ] );
And to build it up dynamically, you can simply do push #things, $value in a loop or whatever you need.
'$string' is literally "$string"; the variable isn't replaced with its contents. Lose the single quotes.
Of course, it's unlikely passing a reference to an array consisting of a single comma-separated string with quotes embedded in it is going to do the same thing as passing a reference to an array of strings.
Try something like:
my $ref = ["1:1108138:1108138","1:1110294:1110294"];
$query->addFilter("chromosomal_region", $ref);
I agree with hobbs...if you want to take many inputs like that, you can use a for loop and an array like this (provided you are taking inputs from STDIN):
for ($line = <STDIN> && $line ne "end\n")
{
chomp($line);
push #values,$line;
}
It takes data and puts in values array. You have to indicate the end of data by "end".
And for your error, what others said was right. Perl's variable interpolation works only for variables in double quotes.

Print the return of a method

I know in php I can do something like this
echo "{$this->method}";
and I swear there was a way to do it in perl
Update:
What I am trying to do is print a scalar that the method returns. I was kind of hoping of doing within the string like in php, just because I'm lazy :P.
Are you just trying to evaluate an arbitrary expression inside a double quoted string? Then maybe you're thinking of
print "#{[$this->method]}";
There is also a trick to call the method in scalar context, but the syntax is a little less clean.
print "${\($this->method)}";
Well, if $this->method outputs a string or a number (like PHP, Perl can automatically convert numbers to strings when required), then you can do print $this->method . "\n";.
If $this->method outputs a data structure (eg an array reference or a hash reference), you can use Data::Dumper to look at the structure of the data. Basically, print Dumper($foo) is the Perl equivalent of PHP's var_dump($foo).
What are you trying to do, exactly?
If $this->method is returning a string, you can do this:
print $this->method . "\n";
without quotes. That will print your string. Sometimes, that can lead to a clumsy looking statement:
print "And we have " . $this->method . " and " . $that->method . " and " . $there->method . "\n";
In that case you can use a little programming trick of:
print "And we have #{[$this->method]} and #{[that->method]} and #{[$their->method]}\n";
Surrounding a function with #{[]} prints out the function's value. Someone explained this to me once, but I can't remember why it works.