Partager via


Tout ce que vous vouliez savoir sur $null

PowerShell $null semble souvent être simple, mais il a beaucoup de nuances. Examinons de près $null pour que vous sachiez ce qui se passe lorsque vous rencontrez soudainement une valeur $null.

Remarque

La version originale de cet article est parue sur le blog écrit par @KevinMarquette. L’équipe PowerShell remercie Kevin de partager ce contenu avec nous. Consultez son blog à l’adresse PowerShellExplained.com.

Qu’est-ce que NULL ?

Vous pouvez considérer NULL comme une valeur inconnue ou vide. Une variable a la valeur NULL jusqu’à ce que vous lui affectiez une valeur ou un objet. Cela peut être important, car certaines commandes nécessitent une valeur et génèrent des erreurs si la valeur est NULL.

PowerShell $null

$null est une variable automatique dans PowerShell utilisée pour représenter NULL. Vous pouvez l’affecter à des variables, l’utiliser dans les comparaisons et l’utiliser comme espace réservé pour NULL dans une collection.

PowerShell traite $null comme un objet avec une valeur NULL. Cela est différent de ce que vous pouvez attendre si vous venez d’une autre langue.

Exemples de $null

Chaque fois que vous essayez d’utiliser une variable que vous n’avez pas initialisée, la valeur est $null. Il s’agit de l’une des manières les plus typiques par lesquelles les valeurs $null s'insèrent dans votre code.

PS> $null -eq $undefinedVariable
True

Si vous avez mal tapé un nom de variable, PowerShell le voit comme une variable différente et la valeur est $null.

L’autre façon dont vous trouvez $null des valeurs est lorsqu’elles proviennent d’autres commandes qui ne vous donnent aucun résultat.

PS> function Get-Nothing {}
PS> $value = Get-Nothing
PS> $null -eq $value
True

Impact de $null

$null les valeurs affectent votre code différemment en fonction de l’emplacement où ils s’affichent.

Dans des chaînes

Si vous utilisez $null dans une chaîne, il s’agit d’une valeur vide (ou d’une chaîne vide).

PS> $value = $null
PS> Write-Output "'The value is $value'"
'The value is '

C’est l’une des raisons pour lesquelles j’aime placer des crochets autour des variables lors de leur utilisation dans les messages de journal. Il est encore plus important d’identifier les bords de vos valeurs de variable lorsque la valeur se trouve à la fin de la chaîne.

PS> $value = $null
PS> Write-Output "The value is [$value]"
The value is []

Cela permet de repérer facilement les chaînes vides et les valeurs $null.

Dans une équation numérique

Lorsqu’une $null valeur est utilisée dans une équation numérique, vos résultats ne sont pas valides s’ils ne donnent pas d’erreur. Parfois, le $null évalue à 0, et d’autres fois, il rend le résultat entier $null. Voici un exemple de multiplication qui donne 0 ou $null selon l’ordre des valeurs.

PS> $null * 5
PS> $null -eq ( $null * 5 )
True

PS> 5 * $null
0
PS> $null -eq ( 5 * $null )
False

À la place d’une collection

Une collection vous permet d’utiliser un index pour accéder aux valeurs. Si vous essayez d’indexer dans une collection qui est en fait null, vous obtenez cette erreur : Cannot index into a null array.

PS> $value = $null
PS> $value[10]
Cannot index into a null array.
At line:1 char:1
+ $value[10]
+ ~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

Si vous avez une collection mais que vous essayez d’accéder à un élément qui n’est pas dans la collection, vous obtenez un $null résultat.

$array = @( 'one','two','three' )
$null -eq $array[100]
True

À la place d’un objet

Si vous essayez d’accéder à une propriété ou à une sous-propriété d’un objet qui n’a pas la propriété spécifiée, vous obtenez une $null valeur comme vous le feriez pour une variable non définie. Cela n’a pas d’importance si la variable est $null ou un objet réel dans ce cas.

PS> $null -eq $undefined.Some.Fake.Property
True

PS> $date = Get-Date
PS> $null -eq $date.Some.Fake.Property
True

Méthode sur une expression avec une valeur nulle

L’appel d’une méthode sur un objet $null provoque un RuntimeException.

PS> $value = $null
PS> $value.ToString()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $value.ToString()
+ ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

Chaque fois que je vois l’expression You cannot call a method on a null-valued expression, la première chose que je fais est de chercher des endroits où j’appelle une méthode sur une variable sans d’abord vérifier la présence de $null.

Recherche de $null

Vous avez peut-être remarqué que je place toujours le $null sur la gauche lors de la vérification $null dans mes exemples. Il s’agit d’une pratique intentionnelle et acceptée comme une bonne pratique PowerShell. Il existe certains scénarios où le placement sur la droite ne vous donne pas le résultat attendu.

Examinez cet exemple suivant et essayez de prédire les résultats :

if ( $value -eq $null )
{
    'The array is $null'
}
if ( $value -ne $null )
{
    'The array is not $null'
}

Si je ne définit $valuepas, le premier évalue $true et notre message est The array is $null. Le piège ici est qu’il est possible de créer un $value qui permet à ces deux d’être $false

$value = @( $null )

Dans ce cas, $value est un tableau qui contient un $null. La fonction -eq vérifie chaque valeur du tableau et retourne la $null correspondante. Cela prend la valeur $false. Renvoie tout ce qui ne correspond pas à -ne et dans ce cas, il n'y a aucun résultat (cela évalue également à $null). Ni l'un ni l'autre n'est $true même si on pourrait penser que l'un d'entre eux devrait l'être.

Non seulement pouvons-nous créer une valeur qui les fait tous deux évaluer à $false, mais il est aussi possible de créer une valeur où ils évaluent tous les deux à $true. Mathias Jessen (@IISResetMe) a un bon post qui plonge dans ce scénario.

PSScriptAnalyzer et VS Code

Le module PSScriptAnalyzer a une règle qui vérifie ce problème appelé PSPossibleIncorrectComparisonWithNull.

PS> Invoke-ScriptAnalyzer ./myscript.ps1

RuleName                              Message
--------                              -------
PSPossibleIncorrectComparisonWithNull $null should be on the left side of equality comparisons.

Étant donné que VS Code utilise également les règles PSScriptAnalyser, il met également en évidence ou identifie cela comme un problème dans votre script.

Vérification « if » simple

Une façon courante pour les utilisateurs de rechercher une valeur non $null consiste à utiliser une instruction simple if() sans comparaison.

if ( $value )
{
    Do-Something
}

Si la valeur est $null, cette valeur est évaluée à $false. C'est facile à lire, mais assurez-vous qu'il recherche exactement ce à quoi vous vous attendez. J’ai lu cette ligne de code comme suit :

Si $value elle a une valeur.

Mais ce n’est pas toute l’histoire. Cette ligne dit en fait :

Si $value n’est pas $null, 0, $false, une chaîne vide, ou un tableau vide.

Voici un exemple plus complet de cette instruction.

if ( $null -ne $value -and
        $value -ne 0 -and
        $value -ne '' -and
        ($value -isnot [array] -or $value.Length -ne 0) -and
        $value -ne $false )
{
    Do-Something
}

Il est parfaitement OK d’utiliser une vérification de base if tant que vous n’oubliez pas que ces autres valeurs comptent comme $false et pas seulement qu’une variable a une valeur.

J’ai rencontré ce problème lors de la refactorisation de code il y a quelques jours. Il avait une vérification de propriété de base comme celle-ci.

if ( $object.Property )
{
    $object.Property = $value
}

Je voulais affecter une valeur à la propriété d’objet uniquement si elle existait. Dans la plupart des cas, l’objet d’origine avait une valeur qui serait évaluée à $true dans l’instruction if. Mais j’ai rencontré un problème où la valeur n’était parfois pas définie. J’ai débogué le code et constaté que l’objet avait la propriété, mais qu’il s’agissait d’une valeur de chaîne vide. Cela a empêché sa mise à jour avec la logique précédente. J’ai donc ajouté une vérification appropriée $null et tout a fonctionné.

if ( $null -ne $object.Property )
{
    $object.Property = $value
}

Ce sont des petits bogues comme ceux-ci qui sont difficiles à repérer et me font vérifier de manière agressive les valeurs pour $null.

$null. Compter

Si vous essayez d’accéder à une propriété d'une valeur $null, cette propriété est également $null. La Count propriété est l’exception à cette règle.

PS> $value = $null
PS> $value.Count
0

Lorsque vous avez une $null valeur, alors la Count valeur est 0. Cette propriété spéciale est ajoutée par PowerShell.

[PSCustomObject] Compter

Presque tous les objets dans PowerShell ont cette Count propriété. Une exception importante est la [pscustomobject] version 5.1 de Windows PowerShell (qui est corrigée dans PowerShell 6.0). Il n'a pas de propriété Count, alors vous obtenez une valeur $null si vous essayez de l'utiliser. Je le mentionne ici pour que vous n’essayiez pas d’utiliser Count au lieu d’une $null vérification.

L’exécution de cet exemple sur Windows PowerShell 5.1 et PowerShell 6.0 vous donne des résultats différents.

$value = [pscustomobject]@{Name='MyObject'}
if ( $value.Count -eq 1 )
{
    "We have a value"
}

Null énumérable

Il y a un type spécial de $null qui agit différemment des autres. Je vais l’appeler le null énumérable, mais c’est vraiment un System.Management.Automation.Internal.AutomationNull. Cette valeur null énumérable est celle que vous obtenez en raison d’une fonction ou d’un bloc de script qui ne retourne rien (résultat void).

PS> function Get-Nothing {}
PS> $nothing = Get-Nothing
PS> $null -eq $nothing
True

Si vous le comparez à $null, vous obtenez une valeur $null. Lorsqu’elle est utilisée dans une évaluation où une valeur est requise, la valeur est toujours $null. Mais si vous la placez dans un tableau, elle est traitée comme un tableau vide.

PS> $containEmpty = @( @() )
PS> $containNothing = @($nothing)
PS> $containNull = @($null)

PS> $containEmpty.Count
0
PS> $containNothing.Count
0
PS> $containNull.Count
1

Vous pouvez avoir un tableau qui contient une $null valeur et sa Count valeur est 1. Mais si vous placez un tableau vide à l’intérieur d’un tableau, il n’est pas compté comme un élément. Le nombre est 0.

Si vous traitez la valeur Null énumérable comme une collection, elle est vide.

Si vous passez une valeur null énumérable à un paramètre de fonction qui n’est pas fortement typé, PowerShell convertit la valeur null énumérable en valeur $null par défaut. Cela signifie qu’à l’intérieur de la fonction, la valeur est traitée comme $null au lieu d’être du type System.Management.Automation.Internal.AutomationNull.

Canalisation

L’emplacement principal où vous voyez la différence est lors de l’utilisation du pipeline. Vous pouvez rediriger une $null valeur, mais pas une valeur null énumérable.

PS> $null | ForEach-Object{ Write-Output 'NULL Value' }
'NULL Value'
PS> $nothing | ForEach-Object{ Write-Output 'No Value' }

Selon votre code, vous devez tenir compte de l’élément $null dans votre logique.

Recherchez d’abord la valeur $null

  • Filtrer les valeurs Null sur le pipeline (... | where {$null -ne $_} | ...)
  • Gérer-la dans la fonction pipeline

foreach

L’une de mes fonctionnalités préférées de foreach est qu’il n’effectue pas d’énumération sur une collection $null.

foreach ( $node in $null )
{
    #skipped
}

Cela m'évite d'avoir à $null vérifier la collection avant de l’énumérer. Si vous avez une collection de $null valeurs, la valeur $node peut toujours être $null.

La foreach procédure a commencé à fonctionner de cette façon avec PowerShell 3.0. Si vous utilisez une version antérieure, ce n’est pas le cas. Il s’agit de l’une des modifications importantes à prendre en compte lors du rétro-portage du code pour la compatibilité avec la version 2.0.

Types de valeur

Techniquement, seuls les types de référence peuvent être $null. Mais PowerShell est très généreux et permet aux variables d’être n’importe quel type. Si vous décidez de taper fortement un type valeur, il ne peut pas être $null. PowerShell convertit $null en une valeur par défaut pour de nombreux types.

PS> [int]$number = $null
PS> $number
0

PS> [bool]$boolean = $null
PS> $boolean
False

PS> [string]$string = $null
PS> $string -eq ''
True

Il existe certains types qui n’ont pas de conversion valide à partir de $null. Ces types génèrent une Cannot convert null to type erreur.

PS> [datetime]$date = $null
Cannot convert null to type "System.DateTime".
At line:1 char:1
+ [datetime]$date = $null
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

Paramètres de fonction

L’utilisation de valeurs fortement typées dans les paramètres de fonction est très courante. Nous apprenons généralement à définir les types de nos paramètres même si nous ne définissons généralement pas les types d’autres variables dans nos scripts. Vous avez peut-être déjà des variables fortement typées dans vos fonctions et vous ne le réalisez même pas.

function Do-Something
{
    param(
        [string] $Value
    )
}

Dès que vous définissez le type du paramètre comme un string, la valeur ne peut jamais être $null. Il est courant de vérifier si une valeur est $null de voir si l’utilisateur a fourni une valeur ou non.

if ( $null -ne $Value ){...}

$Value est une chaîne '' vide lorsqu’aucune valeur n’est fournie. Utilisez plutôt la variable $PSBoundParameters.Value automatique.

if ( $null -ne $PSBoundParameters.Value ){...}

$PSBoundParameters contient uniquement les paramètres spécifiés lors de l’appel de la fonction. Vous pouvez également utiliser la méthode ContainsKey pour rechercher la propriété.

if ( $PSBoundParameters.ContainsKey('Value') ){...}

IsNotNullOrEmpty

Si la valeur est une chaîne, vous pouvez utiliser une fonction de chaîne statique pour vérifier si la valeur est $null ou une chaîne vide en même temps.

if ( -not [string]::IsNullOrEmpty( $value ) ){...}

J'utilise souvent cela lorsque je sais que la valeur doit être une chaîne.

Quand je vérifie les valeurs $null

Je suis un scripteur défensif. Chaque fois que j’appelle une fonction et que je l’assigne à une variable, je la vérifie pour $null.

$userList = Get-ADUser kevmar
if ($null -ne $userList){...}

Je préfère largement utiliser if ou foreach plutôt que d'utiliser try/catch. Ne me trompe pas, j’utilise try/catch encore beaucoup. Mais si je peux tester une condition d’erreur ou un ensemble vide de résultats, je peux autoriser la gestion de mes exceptions pour les exceptions vraies.

J’ai également tendance à vérifier $null avant d’indexer des valeurs ou d'appeler des méthodes sur un objet. Ces deux actions échouent pour un $null objet afin que je trouve qu’il est important de les valider en premier. J’ai déjà abordé ces scénarios plus tôt dans ce billet.

Aucun scénario de résultats

Il est important de savoir que différentes fonctions et commandes gèrent différemment le scénario sans aucun résultat. De nombreuses commandes PowerShell retournent la valeur null énumérable et une erreur dans le flux d’erreurs. Mais d’autres lèvent des exceptions ou vous donnent un objet d’état. Il est toujours à vous de savoir comment les commandes que vous utilisez traitent des scénarios d’absence de résultats et d’erreur.

Initialisation sur $null

Une habitude que j’ai récupérée est d’initialiser toutes mes variables avant de les utiliser. Vous devez effectuer cette opération dans d’autres langues. En haut de ma fonction ou lorsque j’entre dans une foreach boucle, je définis toutes les valeurs que j’utilise.

Voici un scénario que je veux que vous examiniez de près. C’est un exemple de bogue que j’ai dû chasser avant.

function Do-Something
{
    foreach ( $node in 1..6 )
    {
        try
        {
            $result = Get-Something -Id $node
        }
        catch
        {
            Write-Verbose "[$result] not valid"
        }

        if ( $null -ne $result )
        {
            Update-Something $result
        }
    }
}

L’attente ici est que Get-Something retourne un résultat ou une valeur Null énumérable. En cas d'erreur, nous l'enregistrons. Ensuite, nous vérifions que nous avons obtenu un résultat valide avant de le traiter.

Le bogue masqué dans ce code est lorsque lève Get-Something une exception et n’affecte pas de valeur à $result. Cela échoue avant l'affectation, donc nous n'affectons même pas $null à la variable $result. $result contient toujours la $result valide issue des itérations précédentes. Update-Something pour s’exécuter plusieurs fois sur le même objet dans cet exemple.

J'ai défini $result à $null juste à l'intérieur de la boucle foreach avant de l'utiliser pour atténuer ce problème.

foreach ( $node in 1..6 )
{
    $result = $null
    try
    {
        ...

Problèmes d’étendue

Cela permet également d’atténuer les problèmes d’étendue. Dans cet exemple, nous affectons des valeurs à $result encore et encore dans une boucle. Toutefois, étant donné que PowerShell permet aux valeurs de variables de l'extérieur de la fonction de pénétrer dans la portée de la fonction actuelle, les initialiser à l'intérieur de votre fonction réduit les bogues pouvant être introduits de cette manière.

Une variable non initialisée dans votre fonction n’est pas $null si elle est définie sur une valeur dans une étendue parente. L’étendue parente peut être une autre fonction qui appelle votre fonction et utilise les mêmes noms de variables.

Si je prends ce même Do-something exemple et supprimez la boucle, je finirais par quelque chose qui ressemble à cet exemple :

function Invoke-Something
{
    $result = 'ParentScope'
    Do-Something
}

function Do-Something
{
    try
    {
        $result = Get-Something -Id $node
    }
    catch
    {
        Write-Verbose "[$result] not valid"
    }

    if ( $null -ne $result )
    {
        Update-Something $result
    }
}

Si l’appel à Get-Something lève une exception, ma vérification des valeurs $null trouve $result dans Invoke-Something. L’initialisation de la valeur dans votre fonction atténue ce problème.

Nommer les variables est difficile, et il est courant qu'un programmeur réutilise les mêmes noms de variables dans différentes fonctions. Je sais que j’utilise $node,$result,$data tout le temps. Il serait donc très facile pour les valeurs provenant de différentes étendues d’apparaître dans des endroits où elles ne devraient pas être.

Rediriger la sortie vers $null

J’ai parlé de $null valeurs pour cet article entier, mais la rubrique n’est pas complète si je n’ai pas mentionné de redirection de sortie vers $null. Il existe des moments où vous avez des commandes qui génèrent des informations ou des objets que vous souhaitez supprimer. Rediriger la sortie vers $null accomplit cela.

Out-Null

La commande Out-Null est la méthode intégrée pour rediriger les données du pipeline vers $null.

New-Item -Type Directory -Path $path | Out-Null

Affecter à $null

Vous pouvez affecter les résultats d'une commande à $null pour avoir le même effet que Out-Null.

$null = New-Item -Type Directory -Path $path

Étant donné qu’il $null s’agit d’une valeur constante, vous ne pouvez jamais la remplacer. Je n'aime pas l'apparence que ça a dans mon code, mais ça fonctionne souvent plus vite que Out-Null.

Rediriger vers $null

Vous pouvez également utiliser l’opérateur de redirection pour envoyer la sortie vers $null.

New-Item -Type Directory -Path $path > $null

Si vous utilisez des exécutables de ligne de commande qui génèrent les sorties sur les différents flux. Vous pouvez rediriger tous les flux de sortie vers $null de cette manière :

git status *> $null

Résumé

J'ai couvert beaucoup de points sur celui-ci et je sais que cet article est plus fragmenté que la plupart de mes analyses approfondies. En effet, $null les valeurs peuvent apparaître dans de nombreux endroits différents dans PowerShell et toutes les nuances sont spécifiques à l’endroit où vous le trouvez. J’espère que vous vous éloignez de cela avec une meilleure compréhension et une prise de $null conscience des scénarios les plus obscurs que vous pourriez rencontrer.