Operador ternário no PowerShell

214

Pelo que sei, o PowerShell não parece ter uma expressão interna para o chamado operador ternário .

Por exemplo, na linguagem C, que suporta o operador ternário, eu poderia escrever algo como:

<condition> ? <condition-is-true> : <condition-is-false>;

Se isso realmente não existir no PowerShell, qual seria a melhor maneira (isto é, fácil de ler e manter) para obter o mesmo resultado?

mguassa
fonte
5
Dê uma olhada em github.com/nightroman/PowerShellTraps/tree/master/Basic/… . Se é isso que você procura, posso responder.
Roman Kuzmin
2
É um operador condicional ou ternário se . Não é "o operador ternário", pois tudo o que significa é um operador ( qualquer operador) que recebe três argumentos.
Damien_The_Unbeliever
7
@Damien_The_Unbeliever Isso é tecnicamente verdade, mas costuma ser chamado de operador ternário. "Como esse operador geralmente é o único operador ternário existente no idioma, às vezes é simplesmente chamado de" operador ternário ". Em alguns idiomas, esse operador é chamado de" operador condicional ". Operação ternária
mguassa
O Visual Basic não possui um operador ternário verdadeiro, mas considera que o IF e o IFF são funcionalmente equivalentes.
Matt
@ Matt Isso está incorreto. A IIF função sempre avaliará os dois operandos. A Ifdeclaração não irá - veja stackoverflow.com/questions/1220411/... (versões mais recentes VB.NET acrescentou uma expressão ternária: If (x, y, z))
user2864740

Respostas:

319
$result = If ($condition) {"true"} Else {"false"}

Tudo o resto é complexidade incidental e, portanto, deve ser evitado.

Para uso em ou como expressão, não apenas em uma atribuição, envolva-a $()assim:

write-host  $(If ($condition) {"true"} Else {"false"}) 
fbehrens
fonte
3
Isso funciona no lado direito de um igual, mas não exatamente como você esperaria de um operador ternário - eles falham: "a" + If ($ condition) {"true"} Else {"false"} e "a" (If ($ condition) {"true"} Else {"false"}) Isso funciona (ainda não sei por que): "a" + $ (If ($ condition) {"true"} Else {"false "})
Lamarth 30/11/16
12
@ Lamarth - Isso funciona porque o $()wrapper força a avaliação da instrução como uma expressão, retornando, assim, o valor verdadeiro do valor falso, como seria esperado de um operador ternário. É o mais próximo que você chegará do PowerShell AFAIK.
Keith
4
Ao contrário de algumas das outras respostas "não funcionais", isso também garantirá que apenas uma ramificação seja executada.
precisa saber é o seguinte
63

A construção mais próxima do PowerShell que pude criar para emular isso é:

@({'condition is false'},{'condition is true'})[$condition]
mjolinor
fonte
15
({true}, {false})[!$condition]é um pouco melhor (talvez): a) ordem tradicional de partes verdadeiras e falsas; b) $conditionnão precisa ser apenas 0 ou 1 ou $ false, $ true. O operador !converte-o conforme necessário. Ou seja, $conditionpode ser, digamos, 42:! 42 ~ $ false ~ 0 ~ primeira expressão.
Roman Kuzmin
1
Não é exatamente o mesmo, @RomanKuzmin. O exemplo de mjolinor retorna uma string. Mas os mesmos valores de string do exemplo dele plugado em sua expressão retornam um bloco de script. :-(
Michael Sorens
5
Por {true}e {false}quero dizer <true expression>e <false expression>, não blocos de script. Desculpe por não ser preciso.
Roman Kuzmin
1
Obrigado pelo esclarecimento, @ RomanKuzmin - agora vejo o valor em sua sugestão.
Michael Sorens 16/09
12
Isso forçará uma avaliação ansiosa das opções esquerda e direita: isso é muito diferente de uma operação ternária adequada.
precisa saber é o seguinte
24

De acordo com esta postagem do blog do PowerShell , você pode criar um alias para definir um ?:operador:

set-alias ?: Invoke-Ternary -Option AllScope -Description "PSCX filter alias"
filter Invoke-Ternary ([scriptblock]$decider, [scriptblock]$ifTrue, [scriptblock]$ifFalse) 
{
   if (&$decider) { 
      &$ifTrue
   } else { 
      &$ifFalse 
   }
}

Use-o assim:

$total = ($quantity * $price ) * (?:  {$quantity -le 10} {.9} {.75})
Edward Brey
fonte
Não parece haver uma razão para 'decider' ser um scriptblock. Se ?:alguma vez for avaliado, será necessário que os argumentos sejam avaliados. Sem um bloco de script, ele teria um uso semelhante, por exemplo. (?: ($quantity -le 10) {.9} {.75})
usar o seguinte comando
Esse é um recurso interessante, mas pode renderizar seu script como fora do padrão, por exemplo, seu script ou partes dele podem não ser portados, a menos que a definição de alias também seja portada. Você está basicamente criando seu próprio sub-idioma neste momento. Tudo isso pode levar ao aumento do custo de manutenção devido ao aumento da complexidade e à diminuição da legibilidade.
Vance McCorkle
1
@VanceMcCorkle Ponto sólido. A dispersão personalizada vale o seu custo, se útil com frequência. Mas se a situação ocorrer raramente, eu ficaria com uma expressão "se".
Edward Brey
21

Eu também procurei uma resposta melhor e, enquanto a solução na postagem de Edward é "ok", eu vim com uma solução muito mais natural nesta postagem do blog

Curto e grosso:

# ---------------------------------------------------------------------------
# Name:   Invoke-Assignment
# Alias:  =
# Author: Garrett Serack (@FearTheCowboy)
# Desc:   Enables expressions like the C# operators: 
#         Ternary: 
#             <condition> ? <trueresult> : <falseresult> 
#             e.g. 
#                status = (age > 50) ? "old" : "young";
#         Null-Coalescing 
#             <value> ?? <value-if-value-is-null>
#             e.g.
#                name = GetName() ?? "No Name";
#             
# Ternary Usage:  
#         $status == ($age > 50) ? "old" : "young"
#
# Null Coalescing Usage:
#         $name = (get-name) ? "No Name" 
# ---------------------------------------------------------------------------

# returns the evaluated value of the parameter passed in, 
# executing it, if it is a scriptblock   
function eval($item) {
    if( $item -ne $null ) {
        if( $item -is "ScriptBlock" ) {
            return & $item
        }
        return $item
    }
    return $null
}

# an extended assignment function; implements logic for Ternarys and Null-Coalescing expressions
function Invoke-Assignment {
    if( $args ) {
        # ternary
        if ($p = [array]::IndexOf($args,'?' )+1) {
            if (eval($args[0])) {
                return eval($args[$p])
            } 
            return eval($args[([array]::IndexOf($args,':',$p))+1]) 
        }

        # null-coalescing
        if ($p = ([array]::IndexOf($args,'??',$p)+1)) {
            if ($result = eval($args[0])) {
                return $result
            } 
            return eval($args[$p])
        } 

        # neither ternary or null-coalescing, just a value  
        return eval($args[0])
    }
    return $null
}

# alias the function to the equals sign (which doesn't impede the normal use of = )
set-alias = Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment."

O que facilita fazer coisas como (mais exemplos na postagem do blog):

$message == ($age > 50) ? "Old Man" :"Young Dude" 
Garrett Serack
fonte
1
isso é incrível. Eu simplesmente não gosto de usar =como alias, porque ==é usado em C # e em muitos outros idiomas para verificação de igualdade. Ter alguma experiência em C # é bastante comum entre os powerhellers e isso torna o código resultante um pouco confuso. :possivelmente seria um apelido melhor. Usá-lo em conjunto com a atribuição de variável =:pode lembrar alguém da atribuição usada no Pascal, mas não possui nenhum equivalente imediato (que eu saiba) no VB.NET nem no C #.
nohwnd
Você pode definir isso como qualquer apelido que desejar. : ou ~ou o que quer que flutue no seu barco. : D set-alias : Invoke-Assignment -Option AllScope -Description "FearTheCowboy's Invoke-Assignment." # ternário $message =: ($age > 50) ? "Old Man" :"Young Dude" # nulo coalescentes $message =: $foo ?? "foo was empty"`
Garrett Serack
Eu sei, e sabia, estava apenas comentando por que usaria outro apelido.
nohwnd
15

Tente a instrução switch do powershell como uma alternativa, especialmente para atribuição de variáveis ​​- várias linhas, mas legível.

Exemplo,

$WinVer = switch ( Test-Path $Env:windir\SysWOW64 ) {
  $true    { "64-bit" }
  $false   { "32-bit" }
}
"This version of Windows is $WinVer"
nudl
fonte
1
Isso pode ser um pouco mais detalhado, mas há benefícios significativos em muitas das outras respostas. Em particular, não há dependências no código externo, nem nas funções sendo copiadas / coladas em um script ou módulo.
bshacklett
9

Como um operador ternário é geralmente usado ao atribuir valor, ele deve retornar um valor. É assim que pode funcionar:

$var=@("value if false","value if true")[[byte](condition)]

Estúpido, mas trabalhando. Além disso, essa construção pode ser usada para transformar rapidamente um int em outro valor, basta adicionar elementos da matriz e especificar uma expressão que retorne valores não negativos baseados em 0.

Vesper
fonte
6
Isso forçará uma avaliação ansiosa das opções esquerda e direita: isso é muito diferente de uma operação ternária adequada.
precisa saber é o seguinte
6

Como já usei isso muitas vezes e não o vi listado aqui, adicionarei minha peça:

$var = @{$true="this is true";$false="this is false"}[1 -eq 1]

mais feio de todos!

tipo de fonte

sodawillow
fonte
4
Isso forçará uma avaliação ansiosa das opções esquerda e direita: isso é muito diferente de uma operação ternária adequada.
precisa saber é o seguinte
@ user2864740 é porque não há operação ternária adequada no PS. Esta é uma variante sintética.
Viggo Lundén 11/04/19
2
@ ViggoLundén A falta de um operador ternário adequado não reduz a correção do comentário. Essa "variante sintética" tem um comportamento diferente .
user2864740
Você está certo e, depois de reler seu comentário, entendo como ele pode fazer uma diferença real. Obrigado.
precisa saber é o seguinte
5

Recentemente, aprimorei (abra PullRequest) os operadores ternários condicionais e coalescentes nulos no lib 'Pscx'
Pls da PoweShell e procure minha solução.


Ramo de tópico do meu github: UtilityModule_Invoke-Operators

Funções:

Invoke-Ternary
Invoke-TernaryAsPipe
Invoke-NullCoalescing
NullCoalescingAsPipe

Apelido

Set-Alias :?:   Pscx\Invoke-Ternary                     -Description "PSCX alias"
Set-Alias ?:    Pscx\Invoke-TernaryAsPipe               -Description "PSCX alias"
Set-Alias :??   Pscx\Invoke-NullCoalescing              -Description "PSCX alias"
Set-Alias ??    Pscx\Invoke-NullCoalescingAsPipe        -Description "PSCX alias"

Uso

<condition_expression> |?: <true_expression> <false_expression>

<variable_expression> |?? <alternate_expression>

Como expressão, você pode transmitir:
$ null, uma literal, uma variável, uma expressão 'externa' ($ b -eq 4) ou um scriptblock {$ b -eq 4}

Se uma variável na expressão da variável for $ null ou não existir , a expressão alternativa é avaliada como saída.

e eu faço
fonte
4

O PowerShell atualmente não possui um Inline If nativo (ou If ternário ), mas você pode considerar usar o cmdlet personalizado:

IIf <condition> <condition-is-true> <condition-is-false>
Consulte: PowerShell Inline If (IIf)

ferro
fonte
A postagem vinculada mostra como criar uma IIffunção. No entanto, não há IIffunção / cmdlet padrão do PowerShell .
precisa saber é o seguinte
1
@ user2864740, e daí? isso exclui a resposta como uma possível resposta utilizável à pergunta: " qual seria a melhor maneira (isto é, fácil de ler e manter) para obter o mesmo resultado?" Mas, tirando isso, o link se refere à pergunta IIf (publicada em 5 de setembro de 14) que é muito semelhante a esta (duplicada?). O restante das respostas na pergunta vinculada também pode ser visto como valor agregado (e, caso contrário, é principalmente porque são duplicatas).
iRon 11/01/19
Um pouco de explicação ajuda bastante, e é por isso que os links simples são desencorajados, pois não é possível fechar para duplicatas se não houver informações adicionais adicionadas. Considere que uma explicação muito pequena aumentaria significativamente a qualidade de uma resposta de link simples : "O Powershell não suporta um 'ternário' direto (^ mas consulte outras respostas). No entanto, é possível escrever uma função como (^ usando este método, ou veja outras respostas) .. ", mas esse não é meu trabalho a ser adicionado. As respostas podem ser alteradas / atualizadas / etc., conforme aplicável.
precisa saber é o seguinte
@ user2864740, obrigado pelo seu feedback, fiz ajustes na resposta de acordo.
iRon 16/01/19
1

Aqui está uma abordagem alternativa de função personalizada:

function Test-TernaryOperatorCondition {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [bool]$ConditionResult
        ,
        [Parameter(Mandatory = $true, Position = 0)]
        [PSObject]$ValueIfTrue
        ,
        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateSet(':')]
        [char]$Colon
        ,
        [Parameter(Mandatory = $true, Position = 2)]
        [PSObject]$ValueIfFalse
    )
    process {
        if ($ConditionResult) {
            $ValueIfTrue
        }
        else {
            $ValueIfFalse
        }
    }
}
set-alias -Name '???' -Value 'Test-TernaryOperatorCondition'

Exemplo

1 -eq 1 |??? 'match' : 'nomatch'
1 -eq 2 |??? 'match' : 'nomatch'

Diferenças explicadas

  • Por que são 3 pontos de interrogação em vez de 1?
    • O ?personagem já é um apelido para Where-Object.
    • ?? é usado em outros idiomas como um operador coalescente nulo, e eu queria evitar confusão.
  • Por que precisamos do pipe antes do comando?
    • Como estou utilizando o pipeline para avaliar isso, ainda precisamos desse personagem para canalizar a condição em nossa função
  • O que acontece se eu passar em uma matriz?
    • Nós obtemos um resultado para cada valor; ie -2..2 |??? 'match' : 'nomatch': dá: match, match, nomatch, match, match(ou seja, como qualquer int diferente de zero é avaliado como true; enquanto zero é avaliado como false).
    • Se você não quiser isso, converta a matriz em um bool; ([bool](-2..2)) |??? 'match' : 'nomatch'(ou simplesmente [bool](-2..2) |??? 'match' : 'nomatch':)
JohnLBevan
fonte
1

Se você está apenas procurando uma maneira sintaticamente simples de atribuir / retornar uma string ou numérico com base em uma condição booleana, você pode usar o operador de multiplicação como este:

"Condition is "+("true"*$condition)+("false"*!$condition)
(12.34*$condition)+(56.78*!$condition)

Se você só está interessado no resultado quando algo é verdadeiro, pode simplesmente omitir completamente a parte falsa (ou vice-versa), por exemplo, um sistema de pontuação simples:

$isTall = $true
$isDark = $false
$isHandsome = $true

$score = (2*$isTall)+(4*$isDark)+(10*$isHandsome)
"Score = $score"
# or
# "Score = $((2*$isTall)+(4*$isDark)+(10*$isHandsome))"

Observe que o valor booleano não deve ser o termo inicial na multiplicação, ou seja, $ condition * "true" etc. não funcionará.

Nimbell
fonte
1

A partir do PowerShell versão 7, o operador ternário é incorporado ao PowerShell.

1 -gt 2 ? "Yes" : "No"
Trevor Sullivan
fonte