Global variable changed in function not effective - powershell

I just tried this code:
$number = 2
Function Convert-Foo {
$number = 3
}
Convert-Foo
$number
I expected that function Convert-Foo would change $number to 3, but it is still 2.
Why isn't the global variable $number changed to 3 by the function?

No, I'm afraid PowerShell isn't designed that way. You have to think in scopes, for more information on this topic please read the PowerShell help about scopes or type Get-Help about_scopes in your PowerShell ISE/Console.
The short answer is that if you want to change a variable that is in the global scope, you should address the global scope:
$number = 2
Function Convert-Foo {
$global:number = 3
}
Convert-Foo
$number
All variables created inside a Function are not visible outside of the function, unless you explicitly defined them as Script or Global. It's best practice to save the result of a function in another variable, so you can use it in the script scope:
$number = 5
Function Convert-Foo {
# do manipulations in the function
# and return the new value
$number * 10
}
$result = Convert-Foo
# Now you can use the value outside the function:
"The result of the function is '$result'"

Related

Is it possible to match a string $x = "foo" with a variable named $foo and assign a value only if it matches?

What I am trying to ask is easily shown.
Imagine 2 variables named as
my ($foo, $bar) = (0,0);
and
my #a = ("foo","bar","beyond","recognition");
is it possible to string match $a[0] with the variable named $foo, and assign a value to it (say "hi") only if it matches identically?
I am trying to debug this some code (not mine), and I ran into a tough spot. Basically, I have a part of the script where I have a bunch of variables.
my ($p1, $p2, $p3, $p4)= (0,0,0,0); # *Edited*
my #ids = ("p1","p2","p3","p4")
I have a case where I need to pass each of those variables as a hash key to call a certain operation inside a loop.
for (0..3){
my $handle = get_my_stuff(#ids);
my $ret = $p1->do_something(); # <- $p1 is used for the first instance of loop.
...
...
...
}
FOr the first iteration of the loop, I need to use $p1, but for the second iteration of the loop I need to pass (or call)
my $ret = $p2->do_something(); # not $p1
So what I did was ;
my $p;
for (1..4){
my $handle = get_my_stuff(#ids);
no strict 'refs';
my $ret = $p{$_}->do_something();
...
...
...
use strict 'refs';
...
}
But the above operation is not allowed, and I am unable to call my key in such a manner :(. As it turns out, $p1 became a blessed hash as soon after get_my_stuff() was called. And to my biggest surprise, somehow the script in the function (too much and too long to paste here) assign or pass a hash reference to my variables only if they match.
You don't need to try to invent something to deal with variable names. Your idea to use a hash is correct, but your approach is flawed.
It seems your function get_my_stuff takes a list of arguments and transforms them somehow. It then returns a list of objects that correspond to the arguments. Instead of doing that in the loop, do it before you loop through the numbers and build up your hash by assigning each id to an object.
Perl allows you to assign to a hash slice. In that case, the sigil changes to an #. My below implementation uses DateTime with years to show that the objects are different.
use strict;
use warnings;
use feature 'say';
use DateTime;
# for illustration purposes
sub get_my_stuff {
return map { DateTime->new( year => (substr $_, 1) + 2000 ) } #_;
}
my #ids = qw(p1 p2 p3 p4);
my %p;
# create this outside of the loop
#p{#ids} = get_my_stuff(#ids);
foreach my $i ( 1.. 4 ) {
say $p{'p' . $i}->ymd; # "do_something"
}
This will output
2001-01-01
2002-01-01
2003-01-01
2004-01-01

Explain scoping for functions called from PowerShell closures

The following PowerShell code displays unexpected scoping behavior for functions called from closures. Can you explain whether this is "by design", or is a defect?
function runblock($block) {
$x = 4
& $block
}
function printx() {
" in printx: x=" + $x
}
"PSVersion $($PSVersionTable.PSVersion)"
$x = 1
$b = {"In block x=" + $x ; $x = 3 ; printx}
$x = 2
runblock $b
$x = 1
$b = {"In closure x=" + $x ; $x = 3 ; printx}.GetNewClosure()
$x = 2
runblock $b
Output from the above is
PSVersion 3.0
In block x=4
in printx: x=3
In closure x=1
in printx: x=4
Most of the output makes sense to me:
The script block outputs In block x=4 since its parent scope is the runblock function. The printx function outputs x=3 since its parent scope is the script block scope.
The closure outputs In closure x=1 since the value of $x was captured by GetNewClosure call. All as expected.
BUT:
The call to printx from the closure outputs in printx: x=4. So the scope that printx executes within is unaffected by the scope of the closure where $x = 3.
It seems strange to me that a function called from a normal script block does see the variables in the script block scope, but a function called from a closure does not see the variables in the closure.
Consider following code:
function Run {
param($ScriptBlock)
$a = 3
# Picture 4
& $ScriptBlock
}
function Print {
# Picture 6
"Print `$a=$a"
"Print `$b=$b"
}
$a = 1
$b = 1
# Picture 1
$SB = {
# Picture 5
"Closure `$a=$a"
"Closure `$b=$b"
Print
}.GetNewClosure()
# Picture 2
$a = 2
$b = 2
# Picture 3
Run $SB
It prints:
Closure $a=1
Closure $b=1
Print $a=3
Print $b=2
Picture 1: you start from global scope, where you define some variables.
Picture 2: GetNewClosure() create new module and copy variables to it. (red arrow show parent scope relationship)
Picture 3: you change value of variables. Module scope not affected.
Picture 4: function Run called. It create local variable $a. (blue arrow show call direction)
Picture 5: $SB script block called. Script block bound to module session state, so you transit to it.
Picture 6: function Print called. Function bound to global session state, so you return to it.

Concisely passing functions as parameters

This function accepts two values and a function, and calls that function with the two values:
function abc ($a, $func, $b) { &$func $a $b }
Define a function to pass:
function bcd ($x, $y) { $x + $y }
Pass bcd to abc:
abc 10 bcd 20
The result is 30.
It appears that the bcd function object itself isn't being passed, but the string "bcd". Then abc invokes bcd by its name.
Is this considered an acceptable way to pass a function to another function? Most examples I've seen suggest passing a function in a script block which will be invoked in the receiving function. However that approach is more verbose than the above method.
You're correct that it works, as long as the scope doesn't change enough to invalidate the naming. So it's a bit fragile for truly general code; I wouldn't recommend it even in your profile script, for example. (Speaking from some painful experience with insufficiently general profile functions here.)
However, consider this sample, which is even shorter and more robust:
function abc($a, $func, $b) { &$func $a $b }
abc 10 { param($a, $b); $a+$b } 20
(Prints out 30.) You can do whatever you'd normally want with that param block, including validation.
abc 10 {param([parameter(Mandatory=$true)]$a, [parameter(Mandatory=$true)]$b); $a+$b} 20
Alternatively, predefine the function like this:
$bcd = { param($a, $b); $a+$b }
And continue as usual:
abc 10 $bcd 20
Unless there's some reason the passed function call needs to be isolated to it's own scope you can simplify that by just passing a script block and invoking it in the function's local scope:
function abc($a, $func, $b) {.$func}
abc 10 {$a+$b} 20
30

Where does a Perl subroutine get values missing from the actual parameters?

I came across the following Perl subroutine get_billable_pages while chasing a bug. It takes 12 arguments.
sub get_billable_pages {
my ($dbc,
$bill_pages, $page_count, $cover_page_count,
$domain_det_page, $bill_cover_page, $virtual_page_billing,
$job, $bsj, $xqn,
$direction, $attempt,
) = #_;
my $billable_pages = 0;
if ($virtual_page_billing) {
my #row;
### Below is testing on the existence of the 11th and 12th parameters ###
if ( length($direction) && length($attempt) ) {
$dbc->xdb_execute("
SELECT convert(int, value)
FROM job_attribute_detail_atmp_tbl
WHERE job = $job
AND billing_sub_job = $bsj
AND xqn = $xqn
AND direction = '$direction'
AND attempt = $attempt
AND attribute = 1
");
}
else {
$dbc->xdb_execute("
SELECT convert(int, value)
FROM job_attribute_detail_tbl
WHERE job = $job
AND billing_sub_job = $bsj
AND xqn = $xqn
AND attribute = 1
");
}
$cnt = 0;
...;
But is sometimes called with only 10 arguments
$tmp_det = get_billable_pages(
$dbc2,
$row[6], $row[8], $row[7],
$domain_det_page, $bill_cover_page, $virtual_page_billing,
$job1, $bsj1, $row[3],
);
The function does a check on the 11th and 12th arguments.
What are the 11th and 12th arguments when the function is passed only 10 arguments?
Is it a bug to call the function with only 10 arguments because the 11th and 12th arguments end up being random values?
I am thinking this may be the source of the bug because the 12th argument had a funky value when the program failed.
I did not see another definition of the function which takes only 10 arguments.
The values are copied out of the parameter array #_ to the list of scalar variables.
If the array is shorter than the list, then the excess variables are set to undef. If the array is longer than the list, then excess array elements are ignored.
Note that the original array #_ is unmodified by the assignment. No values are created or lost, so it remains the definitive source of the actual parameters passed when the subroutine is called.
ikegami suggested that I should provide some Perl code to demonstrate the assignment of arrays to lists of scalars. Here is that Perl code, based mostly on his edit
use strict;
use warnings;
use Data::Dumper;
my $x = 44; # Make sure that we
my $y = 55; # know if they change
my #params = (8); # Make a dummy parameter array with only one value
($x, $y) = #params; # Copy as if this is were a subroutine
print Dumper $x, $y; # Let's see our parameters
print Dumper \#params; # And how the parameter array looks
output
$VAR1 = 8;
$VAR2 = undef;
$VAR1 = [ 8 ];
So both $x and $y are modified, but if there are insufficient values in the array then undef is used instead. It is as if the source array was extended indefinitely with undef elements.
Now let's look at the logic of the Perl code. undef evaluates as false for the purposes of conditional tests, but you apply the length operator like this
if ( length($direction) && length($attempt) ) { ... }
If you have use warnings in place as you should, Perl would normally produce a Use of uninitialized value warning. However length is unusual in that, if you ask for the length of an undef value (and you are running version 12 or later of Perl 5) it will just return undef instead of warning you.
Regarding "I did not see another definition of the function which takes only 10 arguments", Perl doesn't have function templates like C++ and Java - it is up to the code in the subroutine to look at what it has been passed and behave accordingly.
No, it's not a bug. The remaining arguments are "undef" and you can check for this situation
sub foo {
my ($x, $y) = #_;
print " x is undef\n" unless defined $x;
print " y is undef\n" unless defined $y;
}
foo(1);
prints
y is undef

Why does this Perl variable keep its value

What is the difference between the following two Perl variable declarations?
my $foo = 'bar' if 0;
my $baz;
$baz = 'qux' if 0;
The difference is significant when these appear at the top of a loop. For example:
use warnings;
use strict;
foreach my $n (0,1){
my $foo = 'bar' if 0;
print defined $foo ? "defined\n" : "undefined\n";
$foo = 'bar';
print defined $foo ? "defined\n" : "undefined\n";
}
print "==\n";
foreach my $m (0,1){
my $baz;
$baz = 'qux' if 0;
print defined $baz ? "defined\n" : "undefined\n";
$baz = 'qux';
print defined $baz ? "defined\n" : "undefined\n";
}
results in
undefined
defined
defined
defined
==
undefined
defined
undefined
defined
It seems that if 0 fails, so foo is never reinitialized to undef. In this case, how does it get declared in the first place?
First, note that my $foo = 'bar' if 0; is documented to be undefined behaviour, meaning it's allowed to do anything including crash. But I'll explain what happens anyway.
my $x has three documented effects:
It declares a symbol at compile-time.
It creates an new variable on execution.
It returns the new variable on execution.
In short, it's suppose to be like Java's Scalar x = new Scalar();, except it returns the variable if used in an expression.
But if it actually worked that way, the following would create 100 variables:
for (1..100) {
my $x = rand();
print "$x\n";
}
This would mean two or three memory allocations per loop iteration for the my alone! A very expensive prospect. Instead, Perl only creates one variable and clears it at the end of the scope. So in reality, my $x actually does the following:
It declares a symbol at compile-time.
It creates the variable at compile-time[1].
It puts a directive on the stack that will clear[2] the variable when the scope is exited.
It returns the new variable on execution.
As such, only one variable is ever created[2]. This is much more CPU-efficient than then creating one every time the scope is entered.
Now consider what happens if you execute a my conditionally, or never at all. By doing so, you are preventing it from placing the directive to clear the variable on the stack, so the variable never loses its value. Obviously, that's not meant to happen, so that's why my ... if ...; isn't allowed.
Some take advantage of the implementation as follows:
sub foo {
my $state if 0;
$state = 5 if !defined($state);
print "$state\n";
++$state;
}
foo(); # 5
foo(); # 6
foo(); # 7
But doing so requires ignoring the documentation forbidding it. The above can be achieved safely using
{
my $state = 5;
sub foo {
print "$state\n";
++$state;
}
}
or
use feature qw( state ); # Or: use 5.010;
sub foo {
state $state = 5;
print "$state\n";
++$state;
}
Notes:
"Variable" can mean a couple of things. I'm not sure which definition is accurate here, but it doesn't matter.
If anything but the sub itself holds a reference to the variable (REFCNT>1) or if variable contains an object, the directive replaces the variable with a new one (on scope exit) instead of clearing the existing one. This allows the following to work as it should:
my #a;
for (...) {
my $x = ...;
push #a, \$x;
}
See ikegami's better answer, probably above.
In the first example, you never define $foo inside the loop because of the conditional, so when you use it, you're referencing and then assigning a value to an implicitly declared global variable. Then, the second time through the loop that outside variable is already defined.
In the second example, $baz is defined inside the block each time the block is executed. So the second time through the loop it is a new, not yet defined, local variable.