I am declaring the same variable name with # and $:
#ask=(1..9);
$ask="insanity";
print ("Array #ask\n");
print ("Scalar $ask\n");
Without using use strict I am getting output correctly but when I am using use strict it gives me a compilation error.
Do these two variables refer to two different memory locations or is it the same variable?
You've got two variables:
#ask
$ask
You could have %ask (a hash) too if you wanted. Then you'd write:
print $ask, $ask[0], $ask{0};
to reference the scalar, the array and the hash.
Generally, you should avoid this treatment, but the variables are all quite distinct and Perl won't be confused.
The only reason use strict; is complaining is because you don't prefix your variables with my:
#!/usr/bin/env perl
use strict;
use warnings;
my #ask = (1..9);
my $ask = "insanity";
my %ask = ( 0 => 'infinity', infinity => 0 );
print "Array #ask\n";
print "Scalar $ask\n";
print "Hash $ask{0}\n";
with use strict; you need to declare your variables first before using it.
For example:
use strict;
my #ask=(1..9);
my $ask="insanity";
print ("Array #ask\n");
print ("Scalar $ask\n");
#ask and $ask are different variables — as is %ask — and it is not an error to do this. It is however poor style.
Because the sigil changes when you use them, such as when you use $ask[1] to get the second element of #ask, the code becomes harder to read and use strict will also not be able to tell if you've gotten confused. Thus it's a good idea to use names that differ in more than the sigil unless you know what you're doing. So you could use e.g. #asks and $ask.
The error you are getting with strict is not due to variable names. It is because you are not declaring the variables (using one of my, our, local, or state. Nor are you using the vars pragma.
Short answer: Stick a my in front of each variable, and you'll be strict-compliant.
For package variables, you can examine entries in the symbol table. $ask and #ask are separate entities:
#!/usr/bin/env perl
use Devel::Symdump;
use YAML;
#ask=(1..9);
$ask="insanity";
my $st = Devel::Symdump->new('main');
print Dump [ $st->$_ ] for qw(
scalars
arrays
);
Among other things, this code will output:
--
…
- main::ask
…
---
…
- main::ask
…
Being able to use the same name can help when, say, you have an array of fish and you are doing something with each fish in the array:
for my $fish (#fish) {
go($fish);
}
Normally, it is more expressive to use the plural form for arrays and hashes, the singular form for elements of an array, and something based on the singular form for keys in a hash:
#!/usr/bin/env perl
use strict;
use warnings;
my #ships = ('Titanic', 'Costa Concordia');
my %ships = (
'Titanic' => {
maiden_voyage => '10 April 1912',
capacity => 3_327,
},
'Costa Concordia' => {
maiden_voyage => '14 July 2006',
capacity => 4_880,
},
);
for my $ship (#ships) {
print "$ship\n";
}
while (my ($ship_name, $ship_details) = each %ships) {
print "$ship_name capacity: $ship_details->{capacity}\n";
}
Related
I'm trying to learn Perl better, and learn hash slices.
Instead of 3 different if (defined statements, I'm trying to tidy the code to make it more maintainable and readable, but have come across the following conundrum:
#!/usr/bin/env perl
use strict;
use warnings FATAL => 'all';
use feature 'say';
use autodie ':all';
use Carp 'confess';
use DDP; # a.k.a. Data::Printer
use JSON 'decode_json';
my $hashref;
$hashref->{Jane} = decode_json('{"sex":"Female","Mortality_Status":"Alive", "latest_date":"2020-11-26","Hospitalized":"no","Risk_Status":"NA"}');
p $hashref; # pretty print the data
my #needed_terms = qw(age BMI sex);
if (defined #{ $hashref->{Jane} }{#needed_terms}) {
say 'all terms are defined.'; # this is what it says, which is WRONG!!!
} else {
say 'some terms are missing.'; # Jane is missing BMI and age, so the script should print here
}
I've read How to do sum of hash reference slice? and https://perlmonks.org/?node=References+quick+reference to no avail.
This person Jane is missing both age and BMI information, so the if (defined statement should say that some terms are missing, but is instead passing.
I get the same error whether I use #{ $hashref->{Jane} }{#needed_terms} or %{ $hashref->{Jane} }{#needed_terms}
I've also thought that maybe defined is returning how many terms of the slice are defined, but that isn't true.
How can I set if (defined statement on a hash slice properly?
This is a good place to use all from List::Util:
use List::Util qw(all);
if (all { exists $hashref->{Jane}{$_} } #needed_terms) {
say 'all terms are defined.';
} else {
say 'some terms are missing.'; # Jane is missing BMI and age, so the script should print here
}
It loops through all the needed terms and checks to see if each exists as key to the Jane hash.
One of my favorite docs for Perl Data Structures is perldoc perldsc. It is more of a step-by-step tutorial than References quick reference.
I have two file.Variables are declared in first file($one=1;) , in second file variable name is given ($one). I want to substitute this variable name with actual value and print the output.
File1.txt
variables are gieven here
$one=1;
$name="gorge";
$animal="cat";
File2.txt
This number is x=$one/or less then two
his name is $name
It is a $animal/ kind of animal.
Expected output
This number is x=1/or less then two
his name is gorge
It is a cat/ kind of animal.
I tried with this code:
open (data1,"</home/file1");
open (data2,"</home/file2");
while (<data1>){
while (<data2>){
print $_;
}
}
close data2;
close data1;
Thank You.
You need a templating system
One of the most popular ones is Template Toolkit
For example, with this template file
File2.template
This number is x=[% one %]/or less then two
his name is [% name %]
It is a [% animal %]/kind of animal.
And this Perl code
main.pl
use strict;
use warnings 'all';
use Template;
my $tt = Template->new;
my $vars = {
one => 1,
name => 'gorge',
animal => 'cat',
};
$tt->process('File2.template', $vars);
The result is this
output
This number is x=1/or less then two
his name is gorge
It is a cat/kind of animal.
I think you're fishing for something that is a horribly bad idea.
So I'll suggest a different approach, of building regular expressions to replace the text. In doing this though - the use of $one is going to be a bit confusing, because that means a scalar variable in perl, and this is "just" going to be a pattern match.
So if you can change that - you should:
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my %replace = ( 'one' => '1',
'name' => 'gorge',
'animal' => 'cat' );
my $search = join ( '|', keys %replace );
$search = qr/\$($search)/;
print Dumper \%replace;
print $search;
while ( <DATA> ) {
s/$search/$replace{$1}/g;
print;
}
__DATA__
This number is x=$one/or less then two
his name is $name
It is a $animal/ kind of animal.
You can build your replace patterns something like this:
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my %replace = map { m/\$(\w+)=\"?([^;\"]+)/ } <DATA>;
print Dumper \%replace;
__DATA__
$one=1;
$name="gorge";
$animal="cat";
This gives you:
$VAR1 = {
'name' => 'gorge',
'one' => '1',
'animal' => 'cat'
};
If you're going to be any kind of Perl programmer, then you'll need to read the Perl FAQ.
In there, you'll find an answer to your question.
How can I expand variables in a text string?
If you read that answer, you'll end up with code very similar to what Sobrique gave you. However, in order to get to that code, you'll need to first pass the first paragraph in the answer which says:
If you can avoid it, don't, or if you can use a templating system, such as Text::Template or Template Toolkit, do that instead.
That's really good advice. You should follow it.
OK, I've looked hard and have tried a myriad of variations to get this to work. I need some help from the perl experts.
First of all, I know, I know, don't use dynamic variable names! Well, I think I need to do so in this case. I'm fixing an inherited script. It has a set of variables that are formed with prefix + "_" + suffix. This started small, but prefix is now a list of about 20 and suffix of about 50 -- a thousand different variables.
The script in some cases loops over these variables, using eval to retrieve an individual value, etc.. This all works. The problem is, all uses of the different variables throughout the script, whether inside a loop on the prefix and/or suffix or just a hardcoded use, assume that these variables are globalized by using use var qw($varname). The number of use vars hardcoded statements has grown out of control. Moreover, there are thousands of past input profile files that initialize these variables using global syntax, all of which need to continue to work.
I was hoping I could improve maintainability by doing the globalization step in a loop, dynamically creating the variable names one at a time, then globalizing them with use vars or our. Here is some code to show what I'm after. I've shown several options here, but each is actually a separate trial. I have tried other variations and cannot keep them straight anymore.
my #prefixes = ("one", "two", ..., "twenty");
my #suffixes = ("1", "2", ..., "50");
my $globalvars = "";
for my $suffix (#suffixes) {
for my $prefix (#prefixes) {
# option 1 -- remainder of #1 outside of loop
# NOTE: 1.a (no comma); 1.b (include comma between vars)
$globalvars .= "\$$prefix\_$suffix, ";
# option 2
eval "use vars qw(\$$prefix\_$suffix)";
# option 3
my $g = "$prefix\_$suffix";
eval ("use vars qw(\$$g)");
# option 4
eval ("our \$$g");
}
}
# option 1.a
use vars qw($globalvars);
# or option 1.b
my (eval "$globalvars");
:
:
:
# now use them as global variables
if ($one_1 eq ...) {
if ($one_10) {
It seems that the problem with "use vars" (besides the fact that it is discouraged) is that the string inside qw has to be the actual variable name, not a dynamic variable name. The 'eval' options don't work, I think, because the scope only exists within the eval itself.
Is there a way to do this in perl?
string inside qw has to be the actual variable name, not a dynamic variable name
That's how qw works, it quotes words, it does no interpolation. You don't have to use qw with use vars, though.
#! /usr/bin/perl
use warnings;
use strict;
my #global_vars;
BEGIN { #global_vars = qw($x $y) }
use vars #global_vars;
# strict won't complain:
$x = 1;
$y = 2;
BEGIN is needed here, because use vars runs in compile time, and we need #global_vars to be already populated.
There's no need for eval EXPR since there's no need to use qw. qw is just one way to create a list of strings, and one that's not particularly useful to your needs.
my #prefixes; BEGIN { #prefixes = qw( one two ... twenty ); }
my #suffixes; BEGIN { #suffixes = 1..50; }
use vars map { my $p = $_; map { '$'.$p.'_'.$_ } #suffixes } #prefixes;
I have this code:
#!/usr/bin/perl
package Modules::TextStuff;
use strict;
use warnings;
use Exporter;
our #ISA = qw(Exporter);
our #EXPORT = qw(get_text);
my $author;
my $text_tmp1 =<<'ENG';
This is a template text
by $author.
ENG
sub get_text {
my $tmp = shift #_;
$author = shift #_;
print "In sub author= $author lang = $tmp \n";
my $final_str = eval('$text_'.$tmp);
print "$final_str \n";
return $final_str;
}
1;
Test script:
#!/usr/bin/perl
use strict;
use warnings;
use Modules::TextStuff;
my $str = get_text('tmp1','jim');
print $str;
When I run the test script it does not work. I get:
In sub author=jim lang = eng
Variable "$text_tmp1" is not available at (eval 1) line 2. Use of
uninitialized value $final_str in concatenation (.) or string
How can I fix this?
Combining strings to create variables names is usually a bad idea. You could salvage your current program using our $text_tmp1 = ... instead of my $text_tmp1 = ..., but I think you should consider a different approach, like a hash:
my %templates = (
tmp1 => <<ENG,
This is a template text
by \$author.
ENG
tmp2 => <<ESP,
Esta es templata texta de \$author.
ESP
);
sub get_text {
...
my $final_str = eval( $templates{$tmp} );
...
}
The error you asked about is generated when eval EXPR tries to grab the value of a variable that did exist, but no longer exists.
>perl -wE"{ my $x = 123; sub f { eval '$x' } } say '<'.f().'>';"
Variable "$x" is not available at (eval 1) line 2.
Use of uninitialized value in concatenation (.) or string at -e line 1.
<>
Remember, executing a file (such as a script or a module) is done in its own a lexical scope, just like the one the curlies create above.
It can be fixed by keeping the variable alive by not letting it go out of scope
>perl -wE"my $x = 123; sub f { eval '$x' } say '<'.f().'>';"
<123>
But that's not an option for you.
Other options include making the variable a global variable.
>perl -wE"{ our $x = 123; sub f { eval '$x' } } say '<'.f().'>';"
<123>
Or forcing the sub to capture it so it doesn't cease to exist.
>perl -wE"{ my $x = 123; sub f { $x if 0; eval '$x' } } say '<'.f().'>';"
<123>
(The if 0 silences the "void context" warning.)
That said, it looks like you're trying to re-invent the wheel. Don't invent another half-assed templating system.
I'm looking at several things:
First of all, $text_tmp1 is not a package variable. It's lexically scoped since you declared it with my. If you need it as a package variable and for it to be visible in all or your subroutines, you need to declare it with our.
Your module doesn't compile as written. You are trying to source in $author, but it's not defined.
What are you doing with eval? This is wrong on so many levels.
Here's how I would do it:
#! /usr/bin/env perl
package Modules::TextStuff;
use strict;
use warnings;
use Exporter qw(import);
use Carp;
our #EXPORT_OK = qw(get_text);
our %templates; # This is now a package variable
#
# TEMPLATES
#
$templates{tmp1}=<<TEMPLATE; # We'll use `%s` for replacements
This is a template text
by %s.
TEMPLATE
$templates{tmp2}=<<TEMPLATE;
This is another template and we will substitute
in %s in this one too.
TEMPLATE
sub get_text {
my $template = shift;
my $author = shift;
if ( not exists $templates{$template} ) {
croak qq(Invalid template name "$template");
}
return sprintf $templates{$template}, $author;
}
1;
I'll make each of these templates an entry in my %templates hash. No need for eval to calculate out a variable name for the template. Also notice that I can now actually test whether the user passed in a valid template or not with the exists.
Also note that %template is declared with our and not my. This makes it available in the entire package including any subroutines in my package.
I also use #EXPORT_OK instead of #EXPORT. It's considered more polite. You're requesting permission to pollute the user's namespace. It's like knocking on someone's door and asking if you can have a beer rather than barging in and rummaging through their fridge for a beer.
Note how I use sprintf to handle the replaceable parameters. This again removes the need for eval.
I also prefer to use #! /usr/bin/env perl on my program header since it's more compatible with things like Perlbrew. You're using /usr/bin/env to find the executable Perl program that's in the user's path. This way, you don't have to know whether it's /bin/perl, /usr/bin/perl, /usr/local/bin/perl, or $HOME/perl5/perlbrew/perls/perl-5.18.0/bin/perl
To use your module, I would do this:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use Modules::TextStuff qw(get_text);
say get_text('tmp1','jim');
Pretty much the same call you made. This prints out:
This is a template text
by jim.
I'm getting input from an html form. There are a bunch of text inputs, thus a bunch of key-value pairs. You see my current method is excruciatingly tedious when one has more than three pairs.
I'd like to know, is there a more efficient method of turning the hash into a series of scalar variables? I want the key to be the variable name, set to the value of the key.
I'm relatively new to perl, sorry if this is a stupid question.
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use CGI qw(:standard Vars);
print "Content-type: text/html\n\n";
my %form = Vars();
$hourly = $form{hourly};
$hours_w = $form{hours_w};
$rent_m = $form{rent_m};
#...
You can use a hash slice to assign to multiple variables at once:
my ($hourly, $hours_w, $rent_m) = #{$form}{qw(hourly hours_w rent_m)};
Creating variables dynamically would require eval().
Use CGI's OO interface.
my $q = CGI->new();
$q->import_names('Q');
print $Q::hourly; # hourly param, if any
Do not import_names into global namespace (main::) though, or you'll get in trouble sooner or later.
my $cgi;
BEGIN {
$cgi = CGI->new();
}
BEGIN {
# Only create variables we expect for security
# and maintenance reasons.
my #cgi_vars = qw( hourly hours_w rent_m );
for (#cgi_vars) {
no strict 'refs';
${$_} = $cgi->param($_);
}
# Declare the variables so they can be used
# in the rest of the program with strict on.
require vars;
vars->import(map "\$$_", #cgi_vars);
}
What you're trying to do is called symbolic references (see perldoc perlref and search for /Symbolic references/). It is not considered to be best practice.
Try:
for my $key ( keys %form ){
no strict;
$$key = $form{$key};
}