The following powershell script
"one","two","three" | % { "$(($l++)): $_" }
will print
1: one
2: two
3: three
However, after remove the bracket around $l++
"one","two","three" | % { "$($l++): $_" }
it will print
: one
: two
: three
This is because $l++ is a voidable statement. In powershell certain types of
expressions, when used as statements, are not displayed.
Voidable statements include assignments and the increment/decrement operators. When they are used in an expression, they return a value, but when they’re used as a standalone statement, they return no value. It is very well explained in Windows Powershell in Action by Bruce Payette:
The increment and decrement operators were almost not included in PowerShell
because they introduced a problem. In languages such as C and C#, when you use
one of these operators as a statement:
$a++
nothing is displayed. This is because statements in C and C# don’t return values. In
PowerShell, however, all statements return a value. This led to confusion. People
would write scripts like this:
$sum=0
$i=0
while ($i -lt 10) { $sum += $i; $i++ }
$sum
and be surprised to see the numbers 1 through 10 displayed. This was because $a++
returned a value and PowerShell was displaying the results of every statement. This
was so confusing that we almost removed these operators from the language. Then we
hit on the idea of a voidable statement. Basically, this means that certain types of
expressions, when used as statements, are not displayed. Voidable statements include
assignments and the increment/decrement operators. When they are used in an
expression, they return a value, but when they’re used as a standalone statement, they
return no value. Again, this is one of those details that won’t affect how you use PowerShell other than to make it work as you expect. (source: Windows Powershell in Action)
I believe this was a design decision made by the PowerShell team to avoid surprises due to PowerShell outputting return values. Many C/C# folks would expect the following function to only output 1 not #(0,1).
function foo {
$i = 0
$i++
$i
}
So the statement form $i++ doesn't output the value of $i before it is incremented. If you want that behavior, PowerShell allows you to get that behavior by putting the increment (or decrement) statement directly inside an expression e.g.:
function foo {
$i = 0
($i++)
$i
}
This will output #(0,1).
Bruce Payette discusses this in Chapter 5 of Windows PowerShell in Action 2nd Edition. The increment and decrement operators are "voidable statements". Quoting from the book:
Basically, this means that certain types of expressions, when used as
statements, are not displayed. Voidable statements include assignment
statements and the increment/decrement operators. When increment and
decrement are used in an expression, they return a value, but when
they’re used as a standalone statement, they return no value.
That is because $l++ doesn't return anything, :
$l = 0
$l++ #nothing
$l #gives 1
$l++ #nothing
($l++) #gives 2
This is done so that there is no confusion when you are returning to pipeline. Effectively,
$l++ is $l = $l+ 1, so it doesn't return anything.
What you want to see is $l = $l + 1; $l, which is why you have to do ($l++).
I think there are two tasks it needs to do:
Execute $l++ (increment the variable) which happens on the inner parenthesis
Retrieve $l's value which happens in the outer parenthesis.
Here is a illustration of this:
$l = 0
"$($l++ ; $l)"
Outputs: 1 where
$l = 0
"$($l++)"
Doesn't output anything.
In order to receive output I need two statements inside the sub-expression. Otherwise the only thing that happens is that $l is incremented but its value is not retrieved.
Related
I have a script in Perl that is reading a file. At some point, the code utilize the following if statement inside a for loop:
for (my $i = 0; $i<10 ; $i++ ) {
$_ = <INPUT>;
if (!$_) {last;}
...
I am new in Perl, so I would like to know the meaning of !$_. In this example, $_ is a line of my file. So, what content the line should have to the if statement be true.
The if condition, what is inside (), is evaluated in a boolean scalar context to be tested for "truthiness." So if it's undef or '' (empty string) or 0 (or string "0") it's false.
That ! negates what follows it, so if (!$_) is true if $_ is false (undef or '' or 0 or "0"). However, in this case that $_ is assigned from <> operator so it'll always have a linefeed at the end -- unless the source for <> was exhausted in which case <> returns undef.
So, in this case, that if (!$_) tests for whether there is nothing more to read from INPUT, and exits the for loop with last if that is the case.
A few comments on the shown code.
That C-style for loop can also be written as for my $i (0..9), what is considered far nicer and more readable.† See foreach, and really follow links for flow-control key-words
The piece of code
$_=<INPUT>
if (!$_) { last; }
...
reads from INPUT filehandle and exits its loop (see last) once there is end-of-file. (That need not be an actual file but any resource readable via a filehandle.)
This is clumsy, to say the least; a common way of doing it is
while (<INPUT>) {
...
}
† So much so that even hard-core compiled languages now have it. The C++11 introduced the range-based for loop
for (auto var: container) ... // (really, const auto&), or auto&, or auto&&
and the standard reference linked above says
Used as a more readable equivalent to the traditional for loop [...]
Can someone please explain how/why this code:
function DoIt($one, $two)
{
"`$one is $one"
"`$two is $two"
return $one * $two
}
$sum = DoIt 5 4
$sum
works exactly as expected/intended - i.e., it produces the following output:
$one is 5
$two is 4
20
However, this (seemingly almost identical) code:
function DoIt($one, $two)
{
"`$one is $one"
"`$two is $two"
return $one * $two
}
$sum = DoIt 5 4
"`$sum is $sum"
bends my brain and breaks my understanding of reality by producing the following output:
$sum is $one is 5 $two is 4 20
The reason for the odd behavior is that you are polluting the output stream. PowerShell doesn't explicitly have a pure "return" value like a compiled program, and relies on streams to pass data between functions. See about_Return for more information, as well as an example that illustrates this very behavior.
Basically the $sum variable gets all the output of the function, and as #TheMadTechnician says, it outputs differently depending on how it is being used.
The "correct" way in PowerShell to return values is in general not to use return but to use Write-Output to explicitly define which stream you want to output to. The second thing is that you have to use Write-Host when writing messages to the Host console, otherwise it gets returned on the Output stream as well.
function DoIt($one, $two)
{
Write-Host "`$one is $one"
Write-Host "`$two is $two"
Write-Output ($one * $two)
}
$sum = DoIt 5 4
"`$sum is $sum"
$one is 5
$two is 4
$sum is 20
There's good information in the existing answers, but let me try to bring it all together:
function DoIt($one, $two)
{
"`$one is $one"
"`$two is $two"
return $one * $two
}
produces three outputs ("return values") to PowerShell's success [output] stream (see about_Redirection), which are the evaluation results of:
expandable string "`$one is $one"
expandable string "`$two is $two"
expression $one * $two
The expandable strings are implicitly output - due to producing a result that is neither captured, sent to another command, nor redirected .
Similarly, the return in return $one * $two is just syntactic sugar for:
$one * $two # implicit output
return # control flow: exit the scope
Note:
return is unnecessary here and never required.
While Write-Output could be used in lieu of implicit output:
it is only ever helpful if you want output a collection as a single object, via its -NoEnumerate switch.
it is otherwise not only needlessly verbose, it slows things down.
If you want to print status information from a function without polluting the output stream, you have several choices:
Write-Host prints to the host's display (the console, if you're running in a console window); in PSv4-, such output could neither be captured nor suppressed; in PSv5+, Write-Host now writes to the information stream (number 6), which means that 6> can be used to redirect/suppress the output.
Write-Verbose is for opt-in verbose output, which you can activate by setting preference variable $VerbosePreference to 'Continue' or by using the -Verbose switch.
Write-Debug is for opt-in debugging output via $DebugPreference or -Debug, though note that in the latter case a prompt is displayed whenever a Write-Debug statement is encountered.
Because these outputs are captured in a variable - $sum = DoIt 5 4 - and there is more than 1 output, they are implicitly collected in an array (of type [object[]]).
Using implicit output to print this variable's value to the display - $sum - enumerates the array elements, which prints them each on their own line:
$one is 5
$two is 4
20
By contrast, using implicit output with expandable string "`$sum is $sum" results in a single output string in which the reference to variable $sum, which contains an array, is expanded as follows:
each element of the array is stringified
loosely speaking, this is like calling .ToString() on each element, but it's important to note that PowerShell opts for a culture-invariant string representation, if available - see this answer.
the results are joined with the value of (the rarely used automatic variable) $OFS as the separator to form a single string; $OFS defaults to a single space.
Thus, if you join array elements $one is 5, $two is 4, and 20 with a single space between them, you get:
$one is 5 $two is 4 20
To put it differently: the above is the equivalent of executing '$one is 5', '$two is 4', '20' -join ' '
This is due to how you are outputting an array, and has nothing to do with the function. You can replicate the same behavior with the following:
$sum='$one is 5','$two is 4',20
"`$sum is $sum"
Putting the variable in double quotes performs string expansion, which performs the .ToString() method on each item in the array, and then joins them with a space to convert the array into a string.
After playing around more (using the info I learned from #HAL9256), I discovered the problem isn't actually with using return vs Write-Output but rather with just how command-less strings are handled in functions. For example, this too works perfectly:
function DoIt($one, $two)
{
Write-Host "`$one is $one"
Write-Host "`$two is $two"
return ($one * $two)
}
$sum = DoIt 5 4
Write-Host "`$sum is $sum"
that is, it produces the following expected output:
$one is 5
$two is 4
$sum is 20
(And, apparently, the mathematical computation without explicit parentheses works just fine within a return command - unlike within a Write-Output command.)
In other words, I guess a static string in a function is treated as if you're building an array to be returned (as #TheMadTechnician was referring) as opposed to shorthand for a Write-Host command. Live and learn - "explicit is better than implicit". :-)
Granted, I still don't understand why the final output wasn't exactly the same (right or wrong) between the two code blocks in my original question ... but, hey, my brain is tired enough for one day. :-P
thanks again guys!!!
Why does
$i=1
for ($i -le 5; $i++)
{Write-Host $i}
result in an infinite loop?
I never tried to write something like this in C# or any other programming language, so I don't know how it will behave there, but why would the for cycle not just grab the "i" variable compare it to 5, add 1 to it and compare it again, it's like the for cycle is blind or some form of a machine, instead of a reasonable, logical human being.
Why infinite and not just grab predefined i?
Answers like "because that's how PowerShell functions" are useless, I want to know why it works like that.
I know it becomes infinite, because it's missing the first parameter, I want to know why, though, like the answer the the philosophical "why would the for cycle not look for the variable of the same name outside of its cycle, but must be specifically included in it?"
If you read the Windows PowerShell language specifications, you'll see this grammar for the for statement:
for-statement:
for new-linesopt (
new-linesopt for-initializeropt statement-terminator
new-linesopt for-conditionopt statement-terminator
new-linesopt for-iteratoropt
new-linesopt ) statement-block
for new-linesopt (
new-linesopt for-initializeropt statement-terminator
new-linesopt for-conditionopt
new-linesopt ) statement-block
for new-linesopt (
new-linesopt for-initializeropt
new-linesopt ) statement-block
for-initializer:
pipeline
for-condition:
pipeline
for-iterator:
pipeline
Meaning that in your example, the first statement is the INITIALIZER.
If you rewrite your loop this way:
$i=1
for (;$i -le 5; $i++)
{Write-Host $i}
it will work as you expect. Note the additional ";".
If you omit the ";", in the grammar above $i++ corresponds to the for-condition which constantly evaluates to $true, so the loop never ends.
In your edit, you say you know it becomes infinite, but you want to know WHY.
The answer, is that PowerShell does, automatically, type conversion.
You start with an int type with a value of 1, which is incremented. When it reaches [int]::MaxValue and is incremented by 1, the type int cannot hold this value anymore. Then, PowerShell converts its type, automatically, to double. For instance, try this:
$i=[int]::MaxValue -2
++$i
$i.GetType()
++$i
$i.GetType()
++$i
$i.GetType()
++$i
$i.GetType()
Look at the output, and see PowerShell converts the type from int to a double.
The maximum value of a double incremented by 1 is equals to itself, thus, the loop will never end.
for(1){
print 1;
}
do {
print 1;
}
Is it true?
Or is there any special case these two doesn't equal?
One difference is that for(1) sets $_ to the value of 1, as well:
for(1){
print $_; # prints 1
}
Also, do returns the value of the last command in the sequence:
my $x = do { 1 }; # $x = 1
my $y = for(1){ 1 }; # invalid
You might really be looking for just plain curlies.
{
print 1;
}
It has the following benefits:
Creates a lexical scope (like for (1) and do {}).
You can use next, last and redo in them (like for (1)).
It doesn't mask $_ (like do {}).
But
It can only used where a statement is expected (like for (1), but unlike do {}).
Therefore, { ... } makes more sense than for (1) { ... }, and do { ... } is useful when you want to return a value.
About the same.
You can next, last and redo a for loop, but a do is not a loop--including as part of a do-while "loop". So in a non-trivial block, you couldn't be sure. However, this will work:
do {{
...
}};
Also do will not automatically set $_ to each member of the list, the way a bare for loop will.
No. They have different compilation properties and have different effects. They are similar in only one dimension, that being that the code they introduce will not be looped over -- something they have in common with other constructs, including bare blocks and (sub {...})->().
Here's an obvious difference: for (LIST) BLOCK is a loop, whereas do BLOCK is an expression. This means that
for (1) {
say "Blurgh"
} unless 1;
doesn't compile, whereas
do {
say "Blurgh"
} unless 1;
does.
$df{key} =10 ; return ; if $result == 10 ;
gives me an error. How can I achieve this?
The post-statement form of if only works with single statements. You will have to enclose multiple statements in a block after the if condition, which itself needs to be enclosed in parentheses:
if ( $result == 10 ) {
$df{key} = 10;
return;
}
In this case, it is possible to combine the two statements with a post-statement conditional. The idea here is to combine the two statements in one by performing a Boolean evaluation.
However, this is not a good idea in general as it may short-circuit and fail to do what you expect, like when $df{key} = 0:
$df{key} = 10 and return if $result == 10;
From perlsyn:
In Perl, a sequence of statements that defines a scope is called a block
... generally, a block is delimited by curly brackets, also known as braces. We will call this syntactic construct a BLOCK.
The following compound statements may be used to control flow:
if (EXPR) BLOCK
if (EXPR) BLOCK else BLOCK
if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK
You can group the statements into a do BLOCK and use a conditional
statement modifier on that compound statement.
do { $df{key} = 10; return } if $result == 10;
Unlike the and construct posted by Zaid, this is not ambiguous. You
should, however, think twice before using a conditional statement
modifier. Especially mixing if/unless statements with
if/unless statement modifiers reduces readability of your code.
The main case where in my opinion the statement modifiers make sense
are uncomplicated error paths, i.e.:
croak "foo not specified" unless exists $args{foo};
The comma operator allows one to chain together multiple statements into an expression, after which you can include the conditional:
$df{key} = 10, return if $result == 10;
I use this construct quite often when checking for error conditions:
for my $foo (something...)
{
warn("invalid thing"), next unless $foo =~ /pattern/;
# ...
}