Muhamad Gafar
Muhamad Gafar

Reputation: 409

(Perl) Is it possible to have interpolated variables when a string is read from a file?

I am working on a script that has some variables which are passed on to a string and then they a printed out. The initial string was only 6 lines I didn't need an external file for it but I now have a new string which can fill over 1000 lines. The new string also has some fields that are to be replaced by variables declared in the script.

The text file has something like:

Hello $name

The code is supposed to have several parts to it.

  1. Declaration of variable
my $name = 'Foo';
  1. Open file and read it into a string.
my $content;
open(my $fh, '<', $filename) or die "cannot open file $filename";
{
    local $/;
    $content = <$fh>;
}
close($fh);
  1. Print string
print $content

Expected outcome:

Hello Foo

I am wondering if it's possible to read "Hello $name" from a file but print it as "Hello Foo" since the variable name is declared as Foo.

Upvotes: 3

Views: 1566

Answers (3)

Eduardo Ver&#237;ssimo
Eduardo Ver&#237;ssimo

Reputation: 491

Simplest way:

my $foo = 'Fred';
my $bar = 'Barney';
my $string = 'Say hello to $foo and $bar';

say eval qq{"$string"}

Upvotes: 1

Dave Cross
Dave Cross

Reputation: 69274

The correct answer to the question (as you've already seen) is to use a proper templating system instead.

But it's worth noting that this is answered in the Perl FAQ.

How can I expand variables in text strings?

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. You might even be able to get the job done with sprintf or printf:

my $string = sprintf 'Say hello to %s and %s', $foo, $bar;

However, for the one-off simple case where I don't want to pull out a full templating system, I'll use a string that has two Perl scalar variables in it. In this example, I want to expand $foo and $bar to their variable's values:

my $foo = 'Fred';
my $bar = 'Barney';
$string = 'Say hello to $foo and $bar';

One way I can do this involves the substitution operator and a double /e flag. The first /e evaluates $1 on the replacement side and turns it into $foo. The second /e starts with $foo and replaces it with its value. $foo, then, turns into 'Fred', and that's finally what's left in the string:

$string =~ s/(\$\w+)/$1/eeg; # 'Say hello to Fred and Barney'

The /e will also silently ignore violations of strict, replacing undefined variable names with the empty string. Since I'm using the /e flag (twice even!), I have all of the same security problems I have with eval in its string form. If there's something odd in $foo, perhaps something like @{[ system "rm -rf /" ]}, then I could get myself in trouble.

To get around the security problem, I could also pull the values from a hash instead of evaluating variable names. Using a single /e, I can check the hash to ensure the value exists, and if it doesn't, I can replace the missing value with a marker, in this case ??? to signal that I missed something:

my $string = 'This has $foo and $bar';

my %Replacements = (
  foo  => 'Fred',
);

# $string =~ s/\$(\w+)/$Replacements{$1}/g;
$string =~ s/\$(\w+)/
  exists $Replacements{$1} ? $Replacements{$1} : '???'
/eg;

print $string;

If you're going to be using Perl, then it's really worth your while to spend an afternoon getting to know the FAQ.

Upvotes: 0

ikegami
ikegami

Reputation: 385917

So you want your file to be a template. Why not use a proper template language like this one?

use Template qw( );

my %vars = (
   name => "Foo",
);

my $tt = Template->new();
$tt->process($qfn, \%vars)
   or die($tt->error());

Template:

Hello [% name %]

The output can be captured instead of printed by using ->process's third arg.

Upvotes: 5

Related Questions