4

Here's a little piece of code that outputs 1 2 3 ... with 1 second interval.

while ($true) {
  sleep -s 1
  "$(($i++))"
}

How is it possible?

manidos
  • 3,244
  • 4
  • 29
  • 65
  • 5
    powershell can do `variable squeezing` so that the variable is both assigned/updated AND displayed in one step. that seems to be what is happening in the code you posted. the outer `"$()"` is unneeded, tho. you can replace that line with `($i++)` and get the same result. – Lee_Dailey Jun 16 '19 at 10:22
  • [About Assignment Operators](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_assignment_operators?view=powershell-6#long-description): `++` _Increases the value of a variable, assignable property, or array element by 1._ – JosefZ Jun 16 '19 at 10:27
  • @Lee_Dailey, thank you, makes much more sense now – manidos Jun 16 '19 at 10:38
  • @Matt no it's not unneeded. You need it to process what is inside the brakets first. Otherwise only `$i` will be interpreted and `++` will be part of the string. – T-Me Jun 17 '19 at 07:19
  • @T-Me He means drop the double quotes and the outer dollar sign. – js2010 Mar 08 '23 at 15:54

2 Answers2

14

There are good pointers in the comments, but let me dig a little deeper:

Explanation of $i++:

  • $i++ uses ++, the increment operator, to increment the value of variable $i by 1, as may be familiar from languages such as C# and C/C++. As expected, a complementary decrement operator, --, exists too).

    • Since the ++ is positioned after the variable (postfix form), incrementing happens after the variable's value has been used in a statement.
      Placing it before the variable - ++$i (prefix form) would perform incrementing first.
      If an increment / decrement operation in used in isolation, that distinction is irrelevant.

    • $i is assumed to contain an instance of a numeric type, otherwise an error occurs; if variable $i has not been initialized, its value is effectively $null, which PowerShell coerces to an [int]-typed 0. Thus, $i++ evaluates to 0 in the context of its statement and is incremented to 1 afterwards.

  • An increment / decrement expression such as $i++ is treated like an assignment - you can think of it as $i = $i + 1 - and assignments in PowerShell produce no output (they do not return anything; they only update the variable's value).

Explanation of (...) around $i++:

  • By enclosing an assignment in parentheses ((...)), the grouping operator, you turn it into an expression, which means that the value of the assignment is passed through, so that it can participate in a larger expression; e.g.:

    • $i = 0 ... no output - just assigns value 0 to variable $i.
    • ($i = 1) ... outputs 1: due to (...), the assigned value is also output.
    • (++$i) ... pre-increment: increments the value of $i to 2 and outputs that value.
    • ($i++) ... post-decrement: outputs 2, the current value, then increments the value to 3.
  • Note: There are two closely related bugs related to this behavior, with respect to assigning to type-constrained variables:

Explanation of $(...) around ($i++):

  • $(...), the subexpression operator, is needed for embedding the output from one or even multiple statements in contexts where statements aren't directly supported. Notably, you can use it to embed command output in an expandable string ("..."), i.e., to perform string interpolation.

    • Note that $(...) is only needed for embedding expressions (e.g., something enclosed in (...), property access ($foo.bar), indexing, ($foo[0]) and method calls ($foo.Baz())) and commands (e.g., Get-Date), not for mere variable references such as in "Honey, I'm $HOME". See this answer for more information about expandable strings in PowerShell.
  • While there is no strict need for an expandable string in your simple example - just ($i++) would produce output that looks the same[1] - the $(...) is useful for making the value of ($i++) part of a larger string; e.g., "Iteration #$(($i++))" to print "Iteration #0", "Iteration #1", ...


[1] ($i++) is a number, whereas "$(($i++)" is a string, where the to-string conversion of the number happened as part of the string interpolation. While that typically results in the same console output, it can actually differ for non-integral numbers such as 1.2, because direct output applies culture-sensitive stringification, whereas string interpolation is culture-invariant. Thus, with a culture in effect that uses , as the decimal mark -e.g, fr-FR, 1.2 prints - culture-appropriately - as 1,2 to the console, whereas "$(1.2)" always prints as 1.2

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

In powershell, assignments are also expressions. But the output of expressions isn't normally shown. Because anything output inside a function would be returned by it.

PS C:\users\js> $a = ($b = 1)
PS C:\users\js> $a
1
PS C:\users\js> $b
1    

Btw, $( ) isn't just for inside strings. You can put multiple statements inside it separated by semicolons, and use keywords like foreach and if, and then put it anywhere you can put an expression (pipeline).

PS C:\users\js> $(if ($true) { echo hi }; echo there) | measure


Count    : 2
Average  :
Sum      :
Maximum  :
Minimum  :
Property :
mklement0
  • 382,024
  • 64
  • 607
  • 775
js2010
  • 23,033
  • 6
  • 64
  • 66
  • Assignment's _aren't_ expressions (expressions in the sense of having a _value_), but can be _turned into_ ones - by way of _enclosing them in parentheses_. – mklement0 Jun 18 '19 at 11:10
  • @mklement0 An assignment doesn't normally output its value. But I can find examples where it acts as an expression without extra parentheses. `$a = $b = 1;` `if ($a = 1) { 'yes' }` – js2010 Jun 18 '19 at 14:20
  • Yes, and not outputting its value makes it _not_ an expression. Your example is a _single assignment statement_ (even though it involves multiple variables), not an expression. While your example does blur the line, it's more helpful to think of assignment as a non-expression that you must turn into an expression with `(...)` if you want it to output the assigned value. – mklement0 Jun 18 '19 at 14:25