Seeing value of Perl variable created at runtime - perl

My code is below:
use strict;
my $store = 'Media Markt';
my $sentence = "I visited [store]";
# Replace characters "[" and "]"
$sentence =~ s/\[/\$/g;
$sentence =~ s/\]//g;
print $sentence;
I see following at screen:
I visited $store
Is it possible to see following? I want to see value of $store:
I visited Media Markt

You seem to be thinking of using a string, 'store', in order to build a variable name, $store. This gets to the subject of symbolic references, and you do not want to go there.
One way to do what you want is to build a hash that relates such strings to corresponding variables. Then capture the bracketed strings in the sentence and replace them by their hash values
use warnings;
use strict;
my $store = 'Media Markt';
my $time = 'morning';
my %repl = ( store => $store, time => $time );
my $sentence = "I visited [store] in the [time]";
$sentence =~ s/\[ ([^]]+) \]/$repl{$1}/gex;
print "$sentence\n";
This prints the line I visited Media Markt in the morning
The regex captures anything between [ ], by using the negated character class [^]] (any char other than ]), matched one-or-more times (+). Then it replaces that with its value in the hash, using /e to evaluate the replacement side as an expression. Since brackets are matched as well they end up being removed. The /x allows spaces inside, for readibilty.
For each string found in brackets there must be a key-value pair in the hash or you'll get a warning. To account for this, we can provide an alternative
$sentence =~ s{\[ ([^]+) \]}{$repl{$1}//"[$1]"}gex;
The defined-or operator (//) puts back "[$1]" if $repl{$1} returns undef (no key $1 in the hash, or it has undef value). Thus strings which have no hash pairs are unchanged. I changed the delimiters to s{}{} so that // can be used inside.
This does not allow nesting (like [store [name]]), does not handle multiline strings, and has other limitations. But it should work for reasonable cases.

As I told you on the Perl Programmers Facebook group, this is very similar to one of the answers 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;
And the actual (but really not recommended - for the reasons explained in the FAQ above) answer to your question is:
$sentence =~ s/\[(\w+)]/'$' . $1/ee;

Related

(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.
Declaration of variable
my $name = 'Foo';
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);
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.
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.
Simplest way:
my $foo = 'Fred';
my $bar = 'Barney';
my $string = 'Say hello to $foo and $bar';
say eval qq{"$string"}
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.

How can I prevent Perl from interpreting double-backslash as single-backslash character?

How can I print a string (single-quoted) containing double-backslash \\ characters as is without making Perl somehow interpolating it to single-slash \? I don't want to alter the string by adding more escape characters also.
my $string1 = 'a\\\b';
print $string1; #prints 'a\b'
my $string1 = 'a\\\\b';
#I know I can alter the string to escape each backslash
#but I want to keep string as is.
print $string1; #prints 'a\\b'
#I can also use single-quoted here document
#but unfortunately this would make my code syntactically look horrible.
my $string1 = <<'EOF';
a\\b
EOF
print $string1; #prints a\\b, with newline that could be removed with chomp
The only quoting construct in Perl that doesn't interpret backslashes at all is the single-quoted here document:
my $string1 = <<'EOF';
a\\\b
EOF
print $string1; # Prints a\\\b, with newline
Because here-docs are line-based, it's unavoidable that you will get a newline at the end of your string, but you can remove it with chomp.
Other techniques are simply to live with it and backslash your strings correctly (for small amounts of data), or to put them in a __DATA__ section or an external file (for large amounts of data).
If you are mildly crazy, and like the idea of using experimental software that mucks about with perl's internals to improve the aesthetics of your code, you can use the Syntax::Keyword::RawQuote module, on CPAN since this morning.
use syntax 'raw_quote';
my $string1 = r'a\\\b';
print $string1; # prints 'a\\\b'
Thanks to #melpomene for the inspiration.
Since the backslash interpolation happens in string literals, perhaps you could declare your literals using some other arbitrary symbol, then substitute them for something else later.
my $string = 'a!!!b';
$string =~ s{!}{\\}g;
print $string; #prints 'a\\\b'
Of course it doesn't have to be !, any symbol that does not conflict with a normal character in the string will do. You said you need to make a number of strings, so you could put the substitution in a function
sub bs {
$_[0] =~ s{!}{\\}gr
}
my $string = 'a!!!b';
print bs($string); #prints 'a\\\b'
P.S.
That function uses the non-destructive substitution modifier /r introduced in v5.14. If you are using an older version, then the function would need to be written like this
sub bs {
$_[0] =~ s{!}{\\}g;
return $_[0];
}
Or if you like something more readable
sub bs {
my $str = shift;
$str =~ s{!}{\\}g;
return $str;
}

Perl tr operator is transliterating based on the variable's name not its value

I'm using Perl 5.16.2 to try to count the number of occurrences of a particular delimiter in the $_ string. The delimiter is passed to my Perl program via the #ARGV array. I verify that it is correct within the program. My instruction to count the number of delimiters in the string is:
$dlm_count = tr/$dlm//;
If I hardcode the delimiter, e.g. $dlm_count = tr/,//; the count comes out correctly. But when I use the variable $dlm, the count is wrong. I modified the instruction to say
$dlm_count = tr/$dlm/\t/;
and realized from how the tabs were inserted in the string that the operation was substituting every instance of any of the four characters "$", "d", "l", or "m" to \t — i.e. any of the four characters that made up my variable name $dlm.
Here is a sample program that illustrates the problem:
$_ = "abcdefghij,klm,nopqrstuvwxyz";
my $dlm = ",";
my $dlm_count = tr/$dlm/\t/;
print "The count is $dlm_count\n";
print "The modified string is $_\n";
There are only two commas in the $_ string, but this program prints the following:
The count is 3
The modified string is abc efghij,k ,nopqrstuvwxyz
Why is the $dlm token being treated as a literal string of four characters instead of as a variable name?
You cannot use tr that way, it doesn't interpolate variables. It runs strictly character by character replacement. So this
$string =~ tr/a$v/123/
is going to replace every a with 1, every $ with 2, and every v with 3. It is not a regex but a transliteration. From perlop
Because the transliteration table is built at compile time, neither the SEARCHLIST nor the REPLACEMENTLIST are subjected to double quote interpolation. That means that if you want to use variables, you must use an eval():
eval "tr/$oldlist/$newlist/";
die $# if $#;
eval "tr/$oldlist/$newlist/, 1" or die $#;
The above example from docs hints how to count. For $dlms in $string
$dlm_count = eval "\$string =~ tr/$dlm//";
The $string is escaped so to not be interpolated before it gets to eval. In your case
$dlm_count = eval "tr/$dlm//";
You can also use tools other than tr (or regex). For example, with string being in $_
my $dlm_count = grep { /$dlm/ } split //;
When split breaks $_ by the pattern that is empty string (//) it returns the list of all characters in it. Then the grep block tests each against $dlm so returning the list of as many $dlm characters as there were in $_. Since this is assigned to a scalar, $dlm_count is set to the length of that list, which is the count of all $dlm.
In the section of the docs on perlop 'Quote Like Operators', it states:
Because the transliteration table is built at compile time, neither
the SEARCHLIST nor the REPLACEMENTLIST are subjected to double quote
interpolation. That means that if you want to use variables, you must
use an eval():
As documented and as you discovered, tr/// doesn't interpolate. The simple solution is to use s/// instead.
my $dlm = ",";
$_ = "abcdefghij,klm,nopqrstuvwxyz";
my $dlm_count = s/\Q$dlm/\t/g;
If the transliteration is being performed in a loop, the following might speed things up noticeably:
my $dlm = ",";
my $tr = eval "sub { tr/\Q$dlm\E/\\t/ }";
for (...) {
my $dlm_count = $tr->();
...
}
Although several answers have hinted at the eval() idiom for tr///, none have the form that covers cases where the string has tr syntax characters in it, e.g.- (hyphen):
$_ = "abcdefghij,klm,nopqrstuvwxyz";
my $dlm = ",";
my $dlm_count = eval sprintf "tr/%s/%s/", map quotemeta, $dlm, "\t";
But as others have noted, there are lots of ways to count characters in Perl that avoid eval(), here's another:
my $dlm_count = () = m/$dlm/go;

How to interpret replacement?

$test='abc="def"';
$replacement='$1="ghj"';
$test =~ s/(.+)="(.+)"/"$replacement/;
print $test;
It prints:
$1=ghj
How can I treat $replacement to be interpreted?
You add the /e modifier to your regex. You need to modify your replacement string too, so that it is evaluated correctly. Double evaluation is needed to interpolate the variable.
my $test='abc="def"';
my $replacement='"$1=ghj"';
$test =~ s/(.+)="(.+)"/$replacement/ee;
print $test;
Output:
abc=ghj
It should be noted that this is somewhat unsafe, especially if others can affect the value of your replacement. Then they can execute arbitrary code on your system.
There are approximately 3 answers to this question.
Your replacement "string" is actually code to be evaluated at match time to generate the replacement string. That is, it is better represented as a function:
my $test = 'abc="def"';
my $replacement = sub { $1 . '="ghj"' };
$test =~ s/(.+)="(.+)"/$replacement->()/e;
print $test;
If you don't need the full power of arbitrary Perl expressions (or if your replacement string comes from an external source), you can also treat it as a template to be filled in with the match results. There is a module that encapsulates this in the form of a JavaScript-like replace function, Data::Munge:
use Data::Munge qw(replace);
my $test = 'abc="def"';
my $replacement = '$1="ghj"';
$test = replace $test, qr/(.+)="(.+)"/, $replacement;
print $test;
Finally, you can represent Perl code as a string to be eval'd. This is not only inefficient but also fraught with quoting issues (you have to make sure everything in $replacement is syntactically valid Perl) and security holes (if $replacement is generated at runtime, especially if it comes from an external source). My least favorite approach:
my $test = 'abc="def"';
my $replacement = '$1 . "=\\"ghj\\""';
$test =~ s/(.+)="(.+)"/eval $replacement/e;
print $test;
(The s//eval $foo/e part can also be written as s//$foo/ee. I don't like to do that because eval is evil and shouldn't be more hidden than it already is.)

How to convert single quoted string to double quoted string so it gets interpreted?

For example I have,
my $str = '\t';
print "My String is ".$str;
I want the output to interpret the tab character and return something like:
"My String is \t"
I am actually getting the value of the string from the database, and it returns it as a single quoted string.
String::Interpolate does exactly that
$ perl -MString::Interpolate=interpolate -E 'say "My String is [". interpolate(shift) . "]"' '\t'
My String is [ ]
'\t' and "\t" are string literals, pieces of Perl code that produces strings ("\","t" and the tab character respectively). The database doesn't return Perl code, so describing the problem in terms of single-quoted literals and double-quoted literals makes no sense. You have a string, period.
The string is formed of the characters "\" and "t". You want to convert that sequence of characters into the tab character. That's a simple substitution.
s/\\t/\t/g
I presume you don't want to deal with just \t. You can create a table of the sequences.
my %escapes = (
"t" => "\t",
"n" => "\n",
"\" => "\\",
);
my $escapes_pat = join('', map quotemeta, keys(%escapes));
$escapes_pat = qr/[$escapes_pat]/;
s/\\($escapes_pat)/$escapes{$1}/g;
You can follow the technique in perlfaq4's answer to 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;
Well, I just tried below workaround it worked. Please have a look
my $str1 = "1234\n\t5678";
print $str1;
#it prints
#1234
# 5678
$str1 =~ s/\t/\\t/g;
$str1 =~ s/\n/\\n/g;
print $str1;
#it prints exactly the same
#1234\n\t5678