Null coalescing dans Powershell

115

Existe-t-il un opérateur de fusion nul dans PowerShell?

J'aimerais pouvoir faire ces commandes c # dans PowerShell:

var s = myval ?? "new value";
var x = myval == null ? "" : otherval;
Michée
la source

Réponses:

133

Powershell 7+

Powershell 7 introduit la fusion native null, l'attribution conditionnelle nulle et les opérateurs ternaires dans Powershell.

Coalescence nulle

$null ?? 100    # Result is 100

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

Affectation conditionnelle nulle

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

Opérateur ternaire

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

Versions précédentes:

Pas besoin des extensions de communauté Powershell, vous pouvez utiliser les instructions Powershell if standard comme expression:

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

Donc, aux remplacements de votre première expression C # de:

var s = myval ?? "new value";

devient l'un des suivants (selon la préférence):

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

ou selon ce que $ myval pourrait contenir, vous pourriez utiliser:

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

et la deuxième expression C # est mappée de la même manière:

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

devient

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

Maintenant, pour être honnête, ils ne sont pas très rapides et loin d'être aussi confortables à utiliser que les formulaires C #.

Vous pouvez également envisager de l'envelopper dans une fonction très simple pour rendre les choses plus lisibles:

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

$s = Coalesce $myval "new value"

ou éventuellement, IfNull:

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

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

Comme vous pouvez le voir, une fonction très simple peut vous donner un peu de liberté de syntaxe.

MISE À JOUR: Une option supplémentaire à considérer dans le mix est une fonction IsTrue plus générique:

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

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

Combinez ensuite cela est la capacité de Powershell à déclarer des alias qui ressemblent un peu à des opérateurs, vous vous retrouvez avec:

New-Alias "??" Coalesce

$s = ?? $myval "new value"

New-Alias "?:" IfTrue

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

De toute évidence, ce ne sera pas du goût de tout le monde, mais c'est peut-être ce que vous recherchez.

Comme le note Thomas, une autre différence subtile entre la version C # et la version ci-dessus est que C # effectue un court-circuit des arguments, mais les versions Powershell impliquant des fonctions / alias évalueront toujours tous les arguments. Si cela pose un problème, utilisez le ifformulaire d'expression.

StephenD
la source
6
Le seul vrai équivalent de l'opérateur de fusion utilise une instruction if; le problème est que toute autre approche évalue tous les opérandes au lieu de court-circuiter. "?? $ myval SomeReallyExpenisveFunction ()" appellera la fonction même si $ myval n'est pas nul. Je suppose que l'on pourrait retarder l'évaluation en utilisant des scriptsblocks, mais sachez que les scriptsblocks ne sont PAS des fermetures et que les choses commencent à devenir maladroites.
Thomas S.Trias
Ne fonctionne pas en mode strict - il jette The variable '$myval' cannot be retrieved because it has not been set..
BrainSlugs83
1
@ BrainSlugs83 L'erreur que vous voyez en mode strict n'est pas liée aux options de fusion nulles présentées. C'est juste le standard, Powershell vérifiant qu'une variable est définie en premier. Si vous définissez $myval = $nullavant de faire le test, l'erreur devrait disparaître.
StephenD
1
Attention, bizarrerie PowerShell, null doit toujours être comparé (maladroitement) en plaçant null en premier, c'est-à-dire que vous $null -ne $a ne trouvez pas de référence décente maintenant, mais c'est une pratique de longue date.
dudeNumber4
90

PowerShell 7 et versions ultérieures

PowerShell 7 introduit de nombreuses nouvelles fonctionnalités et migre de .NET Framework vers .NET Core. À la mi-2020, il n'a pas complètement remplacé les versions héritées de PowerShell en raison de la dépendance à .NET Core, mais Microsoft a indiqué qu'il prévoyait que la famille Core remplace à terme la famille Framework héritée. Au moment où vous lirez ceci, une version compatible de PowerShell peut être préinstallée sur votre système; sinon, voir https://github.com/powershell/powershell .

Selon la documentation , les opérateurs suivants sont pris en charge par défaut dans PowerShell 7.0:

  1. Null-coalescing :??
  2. Affectation à fusion nulle :??=
  3. Ternaire :... ? ... : ...

Celles-ci fonctionnent comme vous vous en doutez pour une fusion nulle:

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

Depuis qu'un opérateur ternaire a été introduit, ce qui suit est désormais possible, bien que ce ne soit pas nécessaire étant donné l'ajout d'un opérateur de fusion nul:

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

À partir de la version 7.0, les éléments suivants sont également disponibles si la PSNullConditionalOperatorsfonctionnalité facultative est activée , comme expliqué dans la documentation ( 1 , 2 ):

  1. Accès membre conditionnel nul pour les membres: ?.
  2. Accès aux membres conditionnel nul pour les tableaux et al: ?[]

Ceux-ci ont quelques mises en garde:

  1. Comme ils sont expérimentaux, ils sont sujets à changement. Ils peuvent ne plus être considérés comme expérimentaux au moment où vous lisez ceci, et la liste des mises en garde peut avoir changé.
  2. Les variables doivent être placées entre ${}si elles sont suivies par l'un des opérateurs expérimentaux car les points d'interrogation sont autorisés dans les noms de variables. On ne sait pas si ce sera le cas si / quand les fonctionnalités passeront du statut expérimental (voir le numéro 11379 ). Par exemple, ${x}?.Test()utilise l'opérateur new, mais $x?.Test()s'exécute Test()sur une variable nommée$x? .
  3. Il n'y a pas d' ?(opérateur comme on pourrait s'y attendre si vous venez de TypeScript. Ce qui suit ne fonctionnera pas:$x.Test?()

PowerShell 6 et versions antérieures

Les versions de PowerShell antérieures à 7 ont un opérateur de fusion nul réel, ou au moins un opérateur capable d'un tel comportement. Cet opérateur est -ne:

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

# Output:
alpha

C'est un peu plus polyvalent qu'un opérateur de fusion nul, car il crée un tableau de tous les éléments non nuls:

$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 fonctionne de la même manière, ce qui est utile pour compter les entrées nulles:

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

# Result:
2

Mais de toute façon, voici un cas typique pour refléter l' ??opérateur de C # :

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

Explication

Cette explication est basée sur une suggestion de modification d'un utilisateur anonyme. Merci, qui que vous soyez!

En fonction de l'ordre des opérations, cela fonctionne dans l'ordre suivant:

  1. L' ,opérateur crée un tableau de valeurs à tester.
  2. L' -neopérateur filtre tous les éléments du tableau qui correspondent à la valeur spécifiée - dans ce cas, null. Le résultat est un tableau de valeurs non nulles dans le même ordre que le tableau créé à l'étape 1.
  3. [0] est utilisé pour sélectionner le premier élément du tableau filtré.

Simplifier cela:

  1. Créez un tableau de valeurs possibles, dans l'ordre souhaité
  2. Exclure toutes les valeurs nulles du tableau
  3. Prenez le premier élément du tableau résultant

Mises en garde

Contrairement à l'opérateur de fusion nul de C #, chaque expression possible sera évaluée, puisque la première étape consiste à créer un tableau.

Zenexer
la source
J'ai fini par utiliser une version modifiée de votre réponse dans la mienne , car je devais autoriser toutes les instances de la fusion à être nulles, sans lever d'exception. Très belle façon de le faire cependant, m'a beaucoup appris. :)
Johny Skovdal
Cette méthode évite les courts-circuits. Définissez function DidItRun($a) { Write-Host "It ran with $a"; return $a }et exécutez ((DidItRun $null), (DidItRun 'alpha'), 1 -ne $null)[0]pour voir cela.
jpmc26
@ jpmc26 Oui, c'est par conception.
Zenexer
Toute tentative de définir une fonction de fusion est également susceptible d'éliminer les courts-circuits, n'est-ce pas?
Chris F Carroll
8
La priorité des opérateurs de PowerShell me tue! J'oublie toujours que l'opérateur virgule ,a une priorité si élevée. Pour quiconque ne ($null, 'alpha', 1 -ne $null)[0]sait pas comment cela fonctionne, c'est le même que (($null, 'alpha', 1) -ne $null)[0]. En fait, les deux seuls opérateurs «tiret» avec une priorité plus élevée sont -splitet -join(et un tiret unaire? C'est-à-dire. -4Ou -'56').
Pluton
15

Il ne s'agit que d'une demi-réponse à la première moitié de la question, donc un quart de réponse si vous voulez, mais il existe une alternative beaucoup plus simple à l'opérateur de fusion nul à condition que la valeur par défaut que vous souhaitez utiliser soit en fait la valeur par défaut du type :

string s = myval ?? "";

Peut être écrit dans Powershell comme:

([string]myval)

Ou

int d = myval ?? 0;

se traduit par Powershell:

([int]myval)

J'ai trouvé le premier d'entre eux utile lors du traitement d'un élément xml qui pourrait ne pas exister et qui, s'il existait, pourrait avoir des espaces non désirés autour de lui:

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

La conversion en chaîne protège contre la nullité de l'élément et évite tout risque d' Trim()échec.

Duncan
la source
([string]$null)-> ""semble être un "effet secondaire utile, mais malheureux" :(
user2864740
11

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

Retour

3

Chris F Carroll
la source
1
Mais le commentaire de @ thomas-s-trias sur le court-circuit est toujours valable.
Chris F Carroll
9

Si vous installez le module d'extensions de communauté Powershell, vous pouvez utiliser:

?? est l'alias d'Invoke-NullCoalescing.

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

?: est l'alias d'Invoke-Ternary.

$x = ?: {$myval -eq $null} {""} {$otherval}
EBGreen
la source
En fait, ce ne sont pas des commandes PowerShell. Vous les avez réunis avec pscx:?: -> Invoke-Ternary
BartekB
... a manqué le code et le résultat réels ..;) Get-Command -Module pscx -CommandType alias | où {$ _. Nom -match '\ ?.' } | foreach {"{0}: {1}" -f $ _. Nom, $ _. Définition}?:: Invoke-Ternary ?? : Invoke-NullCoalescing
BartekB
Oups ... vous avez tout à fait raison. J'oublie souvent que j'ai même ce module en cours de chargement.
EBGreen
5

Enfin, PowerShell 7 a des opérateurs d'affectation 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
la source
3
@($null,$null,"val1","val2",5) | select -First 1
staticrysis
la source
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
la source
2

Souvent, je trouve que je dois également traiter une chaîne vide comme nulle lors de l'utilisation de coalesce. J'ai fini par écrire une fonction pour cela, qui utilise la solution de Zenexer pour la fusion pour la simple fusion nulle, puis j'ai utilisé Keith Hill pour la vérification nulle ou vide, et j'ai ajouté cela comme indicateur afin que ma fonction puisse faire les deux.

L'un des avantages de cette fonction est qu'elle gère également le fait d'avoir tous les éléments nuls (ou vides), sans lever d'exception. Il peut également être utilisé pour de nombreuses variables d'entrée arbitraires, grâce à la façon dont PowerShell gère les entrées de tableau.

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)
  }  
}

Cela produit les résultats de test suivants:

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

Code de test:

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
la source
1

Le plus proche que je puisse obtenir est: $Val = $MyVal |?? "Default Value"

J'ai implémenté l' opérateur de fusion nul pour ce qui précède comme ceci:

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

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

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

L' opérateur ternaire conditionnel pourrait être implémenté de manière similaire.

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

Et utilisé comme: $Val = $MyVal |?: $MyVal "Default Value"

Elon Mallin
la source