$prompt = ($defaultValue,$prompt)[[bool]$prompt] - 在 PowerShell 中模拟三元条件

$prompt = ($defaultValue,$prompt)[[bool]$prompt] - emulating a ternary conditional in PowerShell

我正在学习使用 PowerShell 编写脚本,我发现这段代码可以帮助我完成一个项目示例来自 Is there a one-liner for using default values with Read-Host?.

$defaultValue = 'default'

$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"

$prompt = ($defaultValue,$prompt)[[bool]$prompt]

我想我明白 $prompt = ($defaultValue,$prompt) 正在创建一个双元素数组,而 [bool] 部分将 $prompt 数据类型强制为布尔值,但我不明白这第三行代码作为一个整体做了什么。

$prompt 转换为 [bool] 会产生 $true$false 的值,具体取决于变量是否为空($null 或空字符串都变为$false) 或不是(非空字符串变为 $true)。

[bool]''          → $false
[bool]'something' → $true

然后在索引运算符中使用该布尔值,然后将该值隐式转换为整数,其中 $false 变为 0,$true 变为 1,从而选择数组的第一个或第二个元素。

[int]$false → 0
[int]$true  → 1
($defaultValue,$prompt)[0] → $defaultValue
($defaultValue,$prompt)[1] → $prompt

这是一个常见的编程模式:

if (user entered a price)
{
    price = user entered value
} 
else
{
    price = default value
}

因为这很常见,也很啰嗦,所以一些语言有一个特殊的 ternary operator 来更简洁地编写所有代码,并一次性将一个变量分配给 "this value or that value"。例如在 C# 中你可以这样写:

price = (user entered a price) ? (user entered value) : (default value)
# var = IF   [boolean test]    ? THEN  (x)          ELSE  (y)

如果测试为真,? 分配 (x),如果测试为假,则分配 (y)

在Python中写着:

price = (user entered value) if (user entered a price) else (default value)

在 PowerShell 中,它是这样写的:

# you can't have a ternary operator in PowerShell, because reasons. 

是的。不允许使用漂亮的短代码模式。

但是您可以做的是滥用数组索引(@('x', 'y')[0] is 'x'@('x', 'y')[1] is 'y' 和)并编写丑陋且令人困惑的代码高尔夫球线:

$price = ($defaultValue,$userValue)[[bool]$UserEnteredPrice]

# var    (x,y) is an array         $array[ ] is array indexing
         (0,1) are the array indexes of the two values

                                    [bool]$UserEnteredPrice casts the 'test' part to a True/False value
                                    [True/False] used as indexes into an array indexing makes no sense
                                                  so they implicitly cast to integers, and become 0/1

# so if the test is true, the $UserValue is assigned to $price, and if the test fails, the $DefaultValue is assigned to price.

它的行为就像一个三元运算符,除了它令人困惑和丑陋,并且在某些情况下,如果你不小心评估两个数组表达式,无论选择哪个,它都会绊倒你(不像真正的 ?运算符)。


编辑:我真正应该添加的是我更喜欢的 PowerShell 形式 - 您可以直接在 PowerShell 中分配 if 测试的结果并执行:

$price = if ($userValue) { $userValue } else { $DefaultValue }

# -> 

$prompt = if ($prompt) { $prompt } else { $DefaultValue }

为了补充 and :

给出的很好的答案

很棒 如果 PowerShell 有 ternary conditionals and null-coalescing 的运算符,如下(应用于示例中的问题):

# Ternary conditional
# Note: does NOT work in PowerShell as of PSv5.1
$prompt = $prompt ? $prompt : $defaultValue

# Or, more succinctly, with null coalescence:
# Note: does NOT work in PowerShell as of PSv5.1
# (Note: This example assumes that $prompt will be $null in the default
#        case, whereas the code in the question actually assigns the
#        empty string to $prompt if the user just presses Enter.)
$prompt = $prompt ?? $defaultValue

不幸的是,这些富有表现力的结构(仍然)不是 PowerShell 的一部分,而且似乎 PowerShell 团队已经进入了一个长期的失望期,在撰写本文时已经持续了将近十年:

At Microsoft, “to ship is to choose”.  One of the things we were very disappointed in not being able to ship in V1.0 is a ternary operator.

来自 PowerShell Team blog post 2006 年 12 月 29 日。

同一个博客 post 以 函数 的形式提供权宜之计,(不完美地) 模拟 这些运算符。

然而,尝试 "fix" 一种语言总是棘手的事情,所以让我们希望有一天能够正确实施。

以下是来自博客 post 的函数的改编版本以及关联的别名定义,使用它们可以实现以下解决方案:

# Ternary conditional - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?: $prompt $prompt $defaultValue

# Or, more succinctly, with null coalescence - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?? $prompt $defaultValue

源代码:

请注意,实际功能很短;正是基于评论的帮助使此列表冗长。

Set-Alias ?: Invoke-Ternary -Option AllScope
<#
.SYNOPSIS
Emulation of a ternary conditional operator.

.DESCRIPTION
An emulation of the still-missing-from-the-PS-language ternary conditional,
such as the C-style <predicate> ? <if-true> : <if-false>

Because a function is used for emulation, however, the function name must
come first in the invocation.

If you define a succinct alias, e.g., set-alias ?: Invoke-Ternary,
concise in-line conditionals become possible.

To specify something other than a literal or a variable reference, pass a
script block for any of the tree operands.
A predicate script block is of necessity always evaluated, but a script block
passed to the true or false branch is only evaluated on demand. 

.EXAMPLE
> Invoke-Ternary { 2 -lt 3 } 'yes' 'no'

Evaluates the predicate script block, which outputs $true, and therefore 
selects and outputs the true-case expression, string 'yes'. 

.EXAMPLE
> Invoke-Ternary $false { $global:foo = 'bar' } { Get-Date }

Outputs the result of executing Get-Date.
Note that the true-case script block is NOT evaluated in this case.

.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-Ternary
{
  [CmdletBinding()]
  param($Predicate, $Then, $Otherwise = $null)

  if ($(if ($Predicate -is [scriptblock]) { & $Predicate } else { $Predicate })) {
     if ($Then -is [ScriptBlock]) { & $Then } else { $Then }
  } else {
     if ($Otherwise -is [ScriptBlock]) { & $Otherwise } else { $Otherwise }
  }
}


Set-Alias ?? Invoke-NullCoalescence -Option AllScope
<#
.SYNOPSIS
Emulation of a null-coalescence operator.

.DESCRIPTION
An emulation of a null-coalescence operator such as the following:
<expr> ?? <alternative-expr-if-expr-is-null>

Because a function is used for emulation, however, the function name must
come first in the invocation.

If you define a succinct alias, e.g., set-alias ?? Invoke-NullCoalescence,
concise in-line null-coalescing becomes possible.

To specify something other than a literal or a variable reference, pass a
script block for any of the two operands.
A first-operand script block is of necessity always evaluated, but a
second-operand script block is only evaluated on demand.

Note that only a true $null value in the first operand causes the second 
operand to be returned. 

.EXAMPLE
> Invoke-NullCoalescence $null '(empty)'

Since the first operand is $null, the second operand, string '(empty)', is
output.

.EXAMPLE
> Invoke-NullCoalescence '' { $global:foo = 'bar' }

Outputs the first operand, the empty string, because it is not $null.
Note that the second-operand script block is NOT evaluated in this case.

.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-NullCoalescence
{
  [CmdletBinding()]
  param($Value, $Alternative)

  if ($Value -is [scriptblock]) { $Value = & $Value }

  if ($null -ne $Value) {
     $Value
  } else {
     if ($Alternative -is [ScriptBlock]) { & $Alternative } else { $Alternative }
  }
}