Null coalescing in powershell

115

Existe um operador de coalescência nulo no PowerShell?

Eu gostaria de poder fazer estes comandos c # no PowerShell:

var s = myval ?? "new value";
var x = myval == null ? "" : otherval;
Micah
fonte

Respostas:

133

Powershell 7 ou superior

O Powershell 7 apresenta coalescência nula nativa, atribuição condicional nula e operadores ternários no Powershell.

Null Coalescing

$null ?? 100    # Result is 100

"Evaluated" ?? (Expensive-Operation "Not Evaluated")    # Right side here is not evaluated

Atribuição Condicional Nula

$x = $null
$x ??= 100    # $x is now 100
$x ??= 200    # $x remains 100

Operador Ternário

$true  ? "this value returned" : "this expression not evaluated"
$false ? "this expression not evaluated" : "this value returned"

Versões prévias:

Não há necessidade de extensões da comunidade Powershell, você pode usar as instruções if padrão do Powershell como uma expressão:

variable = if (condition) { expr1 } else { expr2 }

Então, para as substituições de sua primeira expressão C # de:

var s = myval ?? "new value";

torna-se um dos seguintes (dependendo da preferência):

$s = if ($myval -eq $null) { "new value" } else { $myval }
$s = if ($myval -ne $null) { $myval } else { "new value" }

ou dependendo do que $ myval possa conter, você pode usar:

$s = if ($myval) { $myval } else { "new value" }

e a segunda expressão C # mapeia de maneira semelhante:

var x = myval == null ? "" : otherval;

torna-se

$x = if ($myval -eq $null) { "" } else { $otherval }

Agora, para ser justo, eles não são muito rápidos e nem de longe tão confortáveis ​​de usar quanto os formulários C #.

Você também pode considerar envolvê-lo em uma função muito simples para tornar as coisas mais legíveis:

function Coalesce($a, $b) { if ($a -ne $null) { $a } else { $b } }

$s = Coalesce $myval "new value"

ou possivelmente como, IfNull:

function IfNull($a, $b, $c) { if ($a -eq $null) { $b } else { $c } }

$s = IfNull $myval "new value" $myval
$x = IfNull $myval "" $otherval

Como você pode ver, uma função muito simples pode fornecer um pouco de liberdade de sintaxe.

ATUALIZAÇÃO: uma opção extra a ser considerada na combinação é uma função IsTrue mais genérica:

function IfTrue($a, $b, $c) { if ($a) { $b } else { $c } }

$x = IfTrue ($myval -eq $null) "" $otherval

Em seguida, combine isso é a capacidade do Powershell de declarar aliases que se parecem um pouco com operadores, você acaba com:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

$ans = ?: ($q -eq "meaning of life") 42 $otherval

É claro que isso não vai agradar a todos, mas pode ser o que você está procurando.

Como observa Thomas, uma outra diferença sutil entre a versão C # e a anterior é que o C # causa um curto-circuito nos argumentos, mas as versões do Powershell envolvendo funções / aliases sempre avaliarão todos os argumentos. Se isso for um problema, use a ifforma de expressão.

StephenD
fonte
6
O único verdadeiro equivalente ao operador coalescente é usar uma instrução if; o problema é que qualquer outra abordagem avalia todos os operandos em vez de curto-circuito. "?? $ myval SomeReallyExpenisveFunction ()" irá chamar a função mesmo se $ myval não for nulo. Suponho que seja possível atrasar a avaliação usando blocos de script, mas esteja ciente de que os blocos de script NÃO são encerramentos e as coisas começam a ficar complicadas.
Thomas S. Trias,
Não funciona no modo estrito - ele joga The variable '$myval' cannot be retrieved because it has not been set..
BrainSlugs83
1
@ BrainSlugs83 O erro que você está vendo no modo estrito não está relacionado às opções de coalescência nulas apresentadas. É apenas o padrão, o Powershell verificando se uma variável é definida primeiro. Se você definir $myval = $nullantes de fazer o teste, o erro deve desaparecer.
StephenD
1
Aviso, peculiaridade do PowerShell, nulo deve sempre ser comparado (desajeitadamente) colocando nulo primeiro, ou seja, $null -ne $a Não é possível encontrar uma referência decente agora, mas é uma prática de longa data.
dudeNumber4
90

PowerShell 7 e posterior

O PowerShell 7 apresenta muitos novos recursos e migra do .NET Framework para o .NET Core. Em meados de 2020, ele não substituiu completamente as versões legadas do PowerShell devido à dependência do .NET Core, mas a Microsoft indicou que pretende que a família Core eventualmente substitua a família Framework legada. No momento em que você lê isto, uma versão compatível do PowerShell pode vir pré-instalada em seu sistema; caso contrário, consulte https://github.com/powershell/powershell .

De acordo com a documentação , os seguintes operadores têm suporte imediato no PowerShell 7.0:

  1. Null-coalescing :??
  2. Atribuição de coalescência nula :??=
  3. Ternário :... ? ... : ...

Funcionam como você esperaria para coalescência nula:

$x = $a ?? $b ?? $c ?? 'default value'
$y ??= 'default value'

Uma vez que um operador ternário foi introduzido, o seguinte agora é possível, embora seja desnecessário devido à adição de um operador coalescente nulo:

$x = $a -eq $null ? $b : $a

A partir da versão 7.0, os itens a seguir também estarão disponíveis se o PSNullConditionalOperatorsrecurso opcional estiver ativado , conforme explicado nos documentos ( 1 , 2 ):

  1. Acesso de membro nulo-condicional para membros: ?.
  2. Acesso de membro nulo-condicional para arrays et al: ?[]

Estes têm algumas ressalvas:

  1. Como são experimentais, estão sujeitos a alterações. Eles podem não ser mais considerados experimentais no momento em que você lê isto, e a lista de advertências pode ter mudado.
  2. Variáveis ​​devem ser incluídas ${}se seguidas por um dos operadores experimentais porque pontos de interrogação são permitidos em nomes de variáveis. Não está claro se este será o caso se / quando os recursos passarem do status experimental (consulte a edição nº 11379 ). Por exemplo, ${x}?.Test()usa o novo operador, mas $x?.Test()é executado Test()em uma variável chamada $x?.
  3. Não há ?(operador como você poderia esperar se você estiver vindo do TypeScript. O seguinte não funcionará:$x.Test?()

PowerShell 6 e anterior

As versões do PowerShell anteriores a 7 têm um operador real de coalescência nula ou, pelo menos, um operador capaz de tal comportamento. Esse operador é -ne:

# Format:
# ($a, $b, $c -ne $null)[0]
($null, 'alpha', 1 -ne $null)[0]

# Output:
alpha

É um pouco mais versátil do que um operador de coalescência nulo, pois cria uma matriz de todos os itens não nulos:

$items = $null, 'alpha', 5, 0, '', @(), $null, $true, $false
$instances = $items -ne $null
[string]::Join(', ', ($instances | ForEach-Object -Process { $_.GetType() }))

# Result:
System.String, System.Int32, System.Int32, System.String, System.Object[],
System.Boolean, System.Boolean

-eq funciona de forma semelhante, o que é útil para contar entradas nulas:

($null, 'a', $null -eq $null).Length

# Result:
2

Mas, de qualquer forma, aqui está um caso típico para espelhar o ??operador C # :

'Filename: {0}' -f ($filename, 'Unknown' -ne $null)[0] | Write-Output

Explicação

Esta explicação é baseada em uma sugestão de edição de um usuário anônimo. Obrigado, seja você quem for!

Com base na ordem das operações, funciona na seguinte ordem:

  1. O ,operador cria uma matriz de valores a serem testados.
  2. O -neoperador filtra todos os itens da matriz que correspondem ao valor especificado - neste caso, nulo. O resultado é uma matriz de valores não nulos na mesma ordem da matriz criada na Etapa 1.
  3. [0] é usado para selecionar o primeiro elemento da matriz filtrada.

Simplificando isso:

  1. Crie uma matriz de valores possíveis, na ordem de sua preferência
  2. Exclua todos os valores nulos da matriz
  3. Pegue o primeiro item da matriz resultante

Ressalvas

Ao contrário do operador de coalescência nula do C #, todas as expressões possíveis serão avaliadas, pois a primeira etapa é criar uma matriz.

Zenexer
fonte
Acabei usando uma versão modificada de sua resposta na minha , porque precisava permitir que todas as instâncias no coalesce fossem nulas, sem lançar uma exceção. Uma maneira muito boa de fazer isso, porém, me aprendeu muito. :)
Johny Skovdal
Este método evita curto-circuito. Defina function DidItRun($a) { Write-Host "It ran with $a"; return $a }e corra ((DidItRun $null), (DidItRun 'alpha'), 1 -ne $null)[0]para ver isso.
jpmc26
@ jpmc26 Sim, é por design.
Zenexer
Qualquer tentativa de definir uma função de coalescência provavelmente eliminará o curto-circuito, não é?
Chris F Carroll
8
Ahhhh a precedência do operador do PowerShell me mata! Sempre esqueço que o operador vírgula ,tem uma precedência muito alta. Para quem está confuso sobre como isso funciona, ($null, 'alpha', 1 -ne $null)[0]é o mesmo que (($null, 'alpha', 1) -ne $null)[0]. Na verdade, os únicos dois operadores de "traço" com maior precedência são -splite -join(e traço unário? -4Ou seja, ou -'56').
Plutão
15

Esta é apenas meia resposta para a primeira metade da pergunta, portanto, um quarto de resposta se quiser, mas há uma alternativa muito mais simples para o operador de coalescência nula, desde que o valor padrão que você deseja usar seja na verdade o valor padrão para o tipo :

string s = myval ?? "";

Pode ser escrito no Powershell como:

([string]myval)

Ou

int d = myval ?? 0;

se traduz em PowerShell:

([int]myval)

Achei a primeira delas útil ao processar um elemento xml que pode não existir e que, se existisse, poderia ter um espaço em branco indesejado ao redor:

$name = ([string]$row.td[0]).Trim()

A conversão em string protege contra o elemento nulo e evita qualquer risco de Trim()falha.

Duncan
fonte
([string]$null)-> ""parece ser um "efeito colateral útil, mas infeliz" :(
user2864740
11

$null, $null, 3 | Select -First 1

retorna

3

Chris F Carroll
fonte
1
Mas o comentário de @thomas-s-trias sobre curto-circuito ainda é válido.
Chris F Carroll,
9

Se você instalar o Powershell Community Extensions Module , poderá usar:

?? é o alias de Invoke-NullCoalescing.

$s = ?? {$myval}  {"New Value"}

?: é o alias de Invoke-Ternary.

$x = ?: {$myval -eq $null} {""} {$otherval}
EBGreen
fonte
Na verdade, não são comandos do PowerShell. Você os juntou com pscx:?: -> Invoke-Ternary
BartekB
... código e resultado reais perdidos ..;) Get-Command -Module pscx -CommandType alias | onde {$ _. Name -match '\ ?.' } | foreach {"{0}: {1}" -f $ _. Nome, $ _. Definição}?:: Invoke-Ternary ?? : Invoke-NullCoalescing
BartekB
Opa ... você está completamente correto. Muitas vezes me esqueço de que ainda tenho esse módulo sendo carregado.
EBGreen
5

Finalmente, o PowerShell 7 tem operadores de atribuição Null Coalescing !

PS > $null ?? "a"
a
PS > "x" ?? "y"
x
PS > $x = $null
PS > $x ??= "abc"
PS > $x
abc
PS > $x ??= "xyz"
PS > $x
abc
Hiroki
fonte
3
@($null,$null,"val1","val2",5) | select -First 1
análise estática
fonte
2
function coalesce {
   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = ($list -ne $null)
   $coalesced[0]
 }
 function coalesce_empty { #COALESCE for empty_strings

   Param ([string[]]$list)
   #$default = $list[-1]
   $coalesced = (($list -ne $null) -ne '')[0]
   $coalesced[0]
 }
Shawn
fonte
2

Freqüentemente, acho que também preciso tratar string vazia como nula ao usar coalescer. Acabei escrevendo uma função para isso, que usa a solução de Zenexer para coalescer para a coalescência nula simples e, em seguida, usei a de Keith Hill para verificação de nulo ou vazio e adicionei isso como um sinalizador para que minha função pudesse fazer as duas coisas.

Uma das vantagens dessa função é que ela também lida com ter todos os elementos nulos (ou vazios), sem lançar uma exceção. Ele também pode ser usado para muitas variáveis ​​de entrada arbitrárias, graças à forma como o PowerShell lida com entradas de array.

function Coalesce([string[]] $StringsToLookThrough, [switch]$EmptyStringAsNull) {
  if ($EmptyStringAsNull.IsPresent) {
    return ($StringsToLookThrough | Where-Object { $_ } | Select-Object -first 1)
  } else {
    return (($StringsToLookThrough -ne $null) | Select-Object -first 1)
  }  
}

Isso produz os seguintes resultados de teste:

Null coallesce tests:
1 (w/o flag)  - empty/null/'end'                 : 
1 (with flag) - empty/null/'end'                 : end
2 (w/o flag)  - empty/null                       : 
2 (with flag) - empty/null                       : 
3 (w/o flag)  - empty/null/$false/'end'          : 
3 (with flag) - empty/null/$false/'end'          : False
4 (w/o flag)  - empty/null/"$false"/'end'        : 
4 (with flag) - empty/null/"$false"/'end'        : False
5 (w/o flag)  - empty/'false'/null/"$false"/'end': 
5 (with flag) - empty/'false'/null/"$false"/'end': false

Código de teste:

Write-Host "Null coalesce tests:"
Write-Host "1 (w/o flag)  - empty/null/'end'                 :" (Coalesce '', $null, 'end')
Write-Host "1 (with flag) - empty/null/'end'                 :" (Coalesce '', $null, 'end' -EmptyStringAsNull)
Write-Host "2 (w/o flag)  - empty/null                       :" (Coalesce('', $null))
Write-Host "2 (with flag) - empty/null                       :" (Coalesce('', $null) -EmptyStringAsNull)
Write-Host "3 (w/o flag)  - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end')
Write-Host "3 (with flag) - empty/null/`$false/'end'          :" (Coalesce '', $null, $false, 'end' -EmptyStringAsNull)
Write-Host "4 (w/o flag)  - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end')
Write-Host "4 (with flag) - empty/null/`"`$false`"/'end'        :" (Coalesce '', $null, "$false", 'end' -EmptyStringAsNull)
Write-Host "5 (w/o flag)  - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end')
Write-Host "5 (with flag) - empty/'false'/null/`"`$false`"/'end':" (Coalesce '', 'false', $null, "$false", 'end' -EmptyStringAsNull)
Johny Skovdal
fonte
1

O mais próximo que posso chegar é: $Val = $MyVal |?? "Default Value"

Implementei o operador de coalescência nulo para o acima assim:

function NullCoalesc {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$Default
    )

    if ($Value) { $Value } else { $Default }
}

Set-Alias -Name "??" -Value NullCoalesc

O operador ternário condicional pode ser implementado de maneira semelhante.

function ConditionalTernary {
    param (
        [Parameter(ValueFromPipeline=$true)]$Value,
        [Parameter(Position=0)]$First,
        [Parameter(Position=1)]$Second
    )

    if ($Value) { $First } else { $Second }
}

Set-Alias -Name "?:" -Value ConditionalTernary

E usado como: $Val = $MyVal |?: $MyVal "Default Value"

Elon Mallin
fonte