How explicitly resolve variables in a perl string? - string

In my perl script I want to have both versions of $config directory:
my $config='$home/client/config';
and
my $config_resolved="$home/client/config";
But I want to get $config_resolved from $config, i.e. something like this:
my $config_resolved=resolve_vars($config);
How can I do such thing in perl?

From the Perl FAQ (which every Perl programmer should read at least once):
How can I expand variables in text strings?
(contributed by brian d foy)
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;

I use eval for this.
So, you must replace all scalars (their names) with their values.
$config = 'stringone';
$boo = '$config/any/string';
$boo =~ s/(\$\w+)/eval($1)/eg;
print $boo;

Because you are using my to declare it as private variable, you might as well use a /ee modifier. This can find variables declared to be in local scope:
$boo =~ s/(\$\w+)/$1/eeg;

This is most tidily and safely done by the double-eval modifier on s///.
In the program below, the first /e evaluates the string $1 to get $home, while the second evaluates $home to get the variable's value HOME.
use strict;
my $home = 'HOME';
my $config = '$home/client/config';
my $config_resolved = resolve_vars($config);
print $config_resolved, "\n";
sub resolve_vars {
(my $str = shift) =~ s/(\$\w+)/$1/eeg;
return $str;
}
output
HOME/client/config

Related

Perl: How to pass in an input without being replaced by the substitution s/// Operator?

For example,
$test = "abc";
$test =~ s/b//g;
normally test will be ac. How do I manipulate the string "abc" so that it will go through this test and still return abc.
I am trying to do a command injection so I can't change $test =~ s/b//g; but can change the input.
Hopefully this makes sense.
You want to use the /r modifier. it's a new'ish feature so older versions will not run it
$test = "abc";
my $str= $test =~ s/b//gr;
http://perldoc.perl.org/perlre.html#Modifiers
There is no string you can pass to s/b//g that will result in a string that contains a b.
However, if you have access to $test and not just it's value, you could set its pos such that the substitution operator won't find anything.
$ perl -e'
my $test = "abc";
pos($test) = length($test);
$test =~ s/b//g;
CORE::say($test);
'
ac

remove a space from a perl variable

I am having a lot of trouble doing a simple search and replace. I tried the solution offered in
How do I remove white space in a Perl string?
but was unable to print this.
Here is my sample code:
#!/usr/bin/perl
use strict;
my $hello = "hello world";
print "$hello\n"; #this should print out >> hello world
#now i am trying to print out helloworld (space removed)
my $hello_nospaces = $hello =~ s/\s//g;
#my $hello_nospaces = $hello =~ s/hello world/helloworld/g;
#my $hello_nospaces = $hello =~ s/\s+//g;
print "$hello_nospaces\n"
#am getting a blank response when i run this.
I tried a couple of different ways, but I was unable to do this.
My end result is to automate some aspects of moving files around in a linux environment, but sometimes the files have spaces in the name, so I want to remove the space from the variable.
You're almost there; you're just confused about operator precedence. The code you want to use is:
(my $hello_nospaces = $hello) =~ s/\s//g;
First, this assigns the value of the variable $hello to the variable $hello_nospaces. Then it performs the substitution operation on $hello_nospaces, as if you said
my $hello_nospaces = $hello;
$hello_nospaces =~ s/\s//g;
Because the bind operator =~ has higher precedence than the assignment operator =, the way you wrote it
my $hello_nospaces = $hello =~ s/\s//g;
first performs the substitution on $hello and then assigns the result of the substitution operation (which is 1 in this case) to the variable $hello_nospaces.
As of 5.14, Perl provides a non-destructive s/// option:
Non-destructive substitution
The substitution (s///) and transliteration (y///) operators now support an /r option that copies the input variable, carries out the substitution on the copy, and returns the result. The original remains unmodified.
my $old = "cat";
my $new = $old =~ s/cat/dog/r;
# $old is "cat" and $new is "dog"
This is particularly useful with map. See perlop for more examples.
So:
my $hello_nospaces = $hello =~ s/\s//gr;
should do what you want.
You just need to add parentheses so Perl's parser can understand what you want it to do.
my $hello = "hello world";
print "$hello\n";
to
(my $hello_nospaces = $hello) =~ s/\s//g;
print "$hello_nospaces\n";
## prints
## hello world
## helloworld
Split this line:
my $hello_nospaces = $hello =~ s/\s//g;
Into those two:
my $hello_nospaces = $hello;
$hello_nospaces =~ s/\s//g;
From the official Perl Regex Tutorial:
If there is a match, s/// returns the number of substitutions made; otherwise it returns false.

Shell Script to parse/retrieve a string found after another string/match

The shell script will be passed a string of arguments. The position of the key/value I am looking to parse out may change over time, i.e. it may come before or after another key at any time so parsing between two keys wouldn't be an option.
I am looking to parse the domain key out of a string like this:
maxpark 0 maxsub n domain sample.foo maxlst n max_defer_fail_percentage user oli force no_cache_update 0 maxpop n maxaddon 0 locale en contactemail
The key would be "domain" the value would be "sample.foo". The domain key could have more than one '.' in it so I would need to grab the entire domain key.
I am not the best with regular expressions but I imagine using 'sed' is what I'm going to need to do.
I am accessing this full string using $*, if I could simply reference the key by accessing $DOMAIN that would be great, but since my only option is to access based on position, $3, and the position could change, that isn't an option
Solved the problem using PERL.
#!/usr/bin/perl -w
use strict;
my %OPTS = #ARGV;
open(FILE, "</var/named/$OPTS{'domain'}.db") || die "File not found";
my #lines = <FILE>;
close(FILE);
my #newlines;
foreach(#lines) {
$_ =~ s/$LOCAL_IP/$PUBLIC_IP/g;
push(#newlines,$_);
}
open(FILE, ">/var/named/$OPTS{'domain'}.db") || die "File not found";
print FILE #newlines;
close(FILE);
If you do have perl, just use this one-liner from your shell script.
domain=$( echo $* | perl -ne '/domain\s([^\s]+)\s/ and print "$1"' )
Or if you'd rather just do it with sed:
domain=$( echo $* | sed 's/.*\<domain \([^ ]\+\).*/\1/' )

How do I delay expansion of variables in PowerShell strings?

Whatever you want to call it, I'm trying to figure out a way to take the contents of an existing string and evaluate them as a double-quoted string. For example, if I create the following strings:
$string = 'The $animal says "meow"'
$animal = 'cat'
Then, Write-Host $string would produce The $animal says "meow". How can I have $string re-evaluated, to output (or assign to a new variable) The cat says "meow"?
How annoying...the limitations on comments makes it very difficult (if it's even possible) to include code with backticks. Here's an unmangled version of the last two comments I made in response to zdan below:
----------
Actually, after thinking about it, I realized that it's not reasonable to expect The $animal says "meow" to be interpolated without escaping the double quotes, because if it were a double-quoted string to begin with, the evaluation would break if the double quotes weren't escaped. So I suppose the answer would be that it's a two step process:
$newstring = $string -replace '"', '`"'
iex "`"$string`""
One final comment for posterity: I experimented with ways of getting that all on one line, and almost anything that you'd think works breaks once you feed it to iex, but this one works:
iex ('"' + ($string -replace '"', '`"') + '"')
Probably the simplest way is
$ExecutionContext.InvokeCommand.ExpandString($var)
You could use Invoke-Expression to have your string reparsed - something like this:
$string = 'The $animal says `"meow`"'
$animal = 'cat'
Invoke-Expression "Write-Host `"$string`""
Note how you have to escape the double quotes (using a backtick) inside your string to avoid confusing the parser. This includes any double quotes in the original string.
Also note that the first command should be a command, if you need to use the resulting string, just pipe the output using write-output and assign that to a variable you can use later:
$result = Invoke-Expression "write-output `"$string`""
As noted in your comments, if you can't modify the creation of the string to escape the double quotes, you will have to do this yourself. You can also wrap this in a function to make it look a little clearer:
function Invoke-String($str) {
$escapedString = $str -replace '"', '`"'
Invoke-Expression "Write-Output `"$escapedString`""
}
So now it would look like this:
# ~> $string = 'The $animal says "meow"'
# ~> $animal = 'cat'
# ~> Invoke-String $string
The cat says "meow"
You can use the -f operator. This is the same as calling [String]::Format as far as I can determine.
PS C:\> $string = 'The {0} says "meow"'
PS C:\> $animal = 'cat'
PS C:\> Write-Host ($string -f $animal)
The cat says "meow"
This avoids the pitfalls associated with quote stripping (faced by ExpandString and Invoke-Expression) and arbitrary code execution (faced by Invoke-Expression).
I've tested that it is supported in version 2 and up; I am not completely certain it's present in PowerShell 1.
Edit: It turns out that string interpolation behavior is different depending on the version of PowerShell. I wrote a better version of the xs (Expand-String) cmdlet with unit tests to deal with that behavior over here on GitHub.
This solution is inspired by this answer about shortening calls to object methods while retaining context. You can put the following function in a utility module somewhere, and it still works when you call it from another module:
function xs
{
[CmdletBinding()]
param
(
# The string containing variables that will be expanded.
[parameter(ValueFromPipeline=$true,
Position=0,
Mandatory=$true)]
[string]
$String
)
process
{
$escapedString = $String -replace '"','`"'
$code = "`$ExecutionContext.InvokeCommand.ExpandString(`"$escapedString`")"
[scriptblock]::create($code)
}
}
Then when you need to do delayed variable expansion, you use it like this:
$MyString = 'The $animal says $sound.'
...
$animal = 'fox'
...
$sound = 'simper'
&($MyString | xs)
&(xs $MyString)
PS> The fox says simper.
PS> The fox says simper.
$animal and $sound aren't expanded until the last two lines. This allows you to set up a $MyString up front and delay expansion until the variables have the values you want.
Invoke-Expression "`"$string`""

Shell Programming: Access Element of List

It is my understanding that when writing a Unix shell program you can iterate through a string like a list with a for loop. Does this mean you can access elements of the string by their index as well?
For example:
foo="fruit vegetable bread"
How could I access the first word of this sentence? I've tried using brackets like the C-based languages to no avail, and solutions I've read online require regular expressions, which I would like to avoid for now.
Pass $foo as argument to a function. Than you can use $1, $2 and so on to access the corresponding word in the function.
function try {
echo $1
}
a="one two three"
try $a
EDIT: another better version is:
a="one two three"
b=( $a )
echo ${b[0]}
EDIT(2): have a look at this thread.
Using arrays is the best solution.
Here's a tricky way using indirect variables
get() { local idx=${!#}; echo "${!idx}"; }
foo="one two three"
get $foo 1 # one
get $foo 2 # two
get $foo 3 # three
Notes:
$# is the number of parameters given to the function (4 in all these cases)
${!#} is the value of the last parameter
${!idx} is the value of the idx'th parameter
You must not quote $foo so the shell can split the string into words.
With a bit of error checking:
get() {
local idx=${!#}
if (( $idx < 1 || $idx >= $# )); then
echo "index out of bounds" >&2
return 1
fi
echo "${!idx}"
}
Please don't actually use this function. Use an array.

Resources