Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
PowerShell $null
часто кажется простым, но существует множество нюансов. Давайте рассмотрим $null
, чтобы узнать, что происходит при неожиданном возникновении значения $null
.
Примечание.
Оригинальная версия этой статьи появилась в блоге, написанном @KevinMarquette. Команда PowerShell благодарит Кевина за предоставление этого содержимого нам. Пожалуйста, ознакомьтесь с его блогом на PowerShellExplained.com.
Что такое NULL?
Значение NULL можно рассматривать как неизвестное или пустое значение. Переменная имеет значение NULL, пока не назначьте ему значение или объект. Это может быть важно, так как существуют некоторые команды, требующие значения и создающие ошибки, если значение равно NULL.
$null PowerShell
$null
— это автоматическая переменная в PowerShell, используемая для представления NULL. Вы можете назначить его переменным, использовать его в сравнениях и использовать его в качестве держателя места для NULL в коллекции.
PowerShell обрабатывает $null
как объект со значением NULL. Это отличается от того, что вы можете ожидать, если вы исходите из другого языка.
Примеры $null
В любой момент, когда вы пытаетесь использовать переменную, которую вы не инициализировали, значение равно $null
. Это один из наиболее распространенных способов, как значения $null
проникают в ваш код.
PS> $null -eq $undefinedVariable
True
Если вы случайно наберете имя переменной с ошибкой, то PowerShell воспринимает ее как другую переменную, и присваивает ей значение $null
.
Другой способ получения значений $null
заключается в том, что они поступают из других команд, которые не дают никаких результатов.
PS> function Get-Nothing {}
PS> $value = Get-Nothing
PS> $null -eq $value
True
Влияние $null
$null
значения влияют на код по-разному в зависимости от того, где они отображаются.
В строках
Если вы используете $null
в строке, это пустое значение (или пустая строка).
PS> $value = $null
PS> Write-Output "'The value is $value'"
'The value is '
Это одна из причин, по которым мне нравится размещать скобки вокруг переменных при их использовании в сообщениях журнала. Еще более важно определить края значений переменной, когда значение находится в конце строки.
PS> $value = $null
PS> Write-Output "The value is [$value]"
The value is []
Это упрощает обнаружение пустых строк и значений $null
.
В числовом уравнении
Если значение $null
используется в числовом уравнении, и при этом не возникает ошибка, то результаты недействительны. Иногда $null
принимает значение 0
, а в других случаях делает весь результат $null
.
Ниже приведен пример умножения, который дает 0 или $null
в зависимости от порядка значений.
PS> $null * 5
PS> $null -eq ( $null * 5 )
True
PS> 5 * $null
0
PS> $null -eq ( 5 * $null )
False
Вместо коллекции
Коллекция позволяет использовать индекс для доступа к значениям. Если вы пытаетесь осуществить индексирование в коллекцию, которая фактически null
, то вы получите эту ошибку: 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
Если у вас есть коллекция, но вы пытаетесь получить доступ к элементу, который не находится в коллекции, вы получите $null
результат.
$array = @( 'one','two','three' )
$null -eq $array[100]
True
Вместо объекта
Если вы пытаетесь получить доступ к свойству или дочернему свойству объекта, который не имеет указанного свойства, вы получите значение $null
, например, для неопределенной переменной. Не имеет значения, является ли переменная $null
или фактическим объектом в данном случае.
PS> $null -eq $undefined.Some.Fake.Property
True
PS> $date = Get-Date
PS> $null -eq $date.Some.Fake.Property
True
Метод для выражения с значением NULL
Вызов метода для объекта $null
вызывает 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
Всякий раз, когда я вижу фразу You cannot call a method on a null-valued expression
, то первое, что я ищу, — это места, где я вызываю метод для переменной, не проверяя её на $null
.
Проверка значения $null
Возможно, вы заметили, что я всегда размещаю $null
слева при проверке $null
в моих примерах. Это намеренно и принято в качестве рекомендации PowerShell. Есть некоторые сценарии, где размещение его справа не дает ожидаемый результат.
Просмотрите следующий пример и попробуйте спрогнозировать результаты:
if ( $value -eq $null )
{
'The array is $null'
}
if ( $value -ne $null )
{
'The array is not $null'
}
Если я не определю $value
, первое значение вычисляется как $true
, и наше сообщение становится The array is $null
. Ловушка здесь заключается в том, что можно создать $value
, которое позволяет обоим из них быть $false
.
$value = @( $null )
В этом случае $value
представляет собой массив, содержащий $null
.
-eq
проверяет каждое значение в массиве и возвращает соответствующий $null
. Это выражается как $false
.
-ne
возвращает все, что не соответствует $null
, и в этом случае нет результатов (это также оценивается как $false
). Ни один из них не $true
, хотя кажется, что один из них должен быть.
Мы можем не только создать значение, которое заставляет их обоих принять $false
, но и создать значение, где оба они принимают $true
. Матиас Джессен (@IISResetMe) опубликовал хороший пост, который подробно рассматривает этот сценарий.
PSScriptAnalyzer и VS Code
Модуль PSScriptAnalyzer содержит правило, которое проверяет наличие этой проблемы с именем PSPossibleIncorrectComparisonWithNull
.
PS> Invoke-ScriptAnalyzer ./myscript.ps1
RuleName Message
-------- -------
PSPossibleIncorrectComparisonWithNull $null should be on the left side of equality comparisons.
Так как VS Code также использует правила PSScriptAnalyser, он также выделяет или идентифицирует это как проблему в скрипте.
Простая проверка с условием "if"
Распространенный способ проверки значения, отличного от $null, заключается в использовании простой инструкции if()
без сравнения.
if ( $value )
{
Do-Something
}
Если значение равно $null
, это преобразуется в $false
. Это легко читать, но будьте осторожны: убедитесь, что он ищет именно то, чего вы ожидаете. Я прочитал эту строку кода следующим образом:
Если
$value
имеет значение.
Но это не вся история. Эта строка на самом деле говорит:
Если
$value
не является$null
и0
и$false
и пустой строкой, и пустым массивом.
Ниже приведен более полный пример этой инструкции.
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
}
Совершенно нормально использовать базовую проверку if
при условии, что вы помните, что эти другие значения считаются как $false
, а не просто то, что переменная имеет значение.
Я столкнулась с этой проблемой при рефакторинге некоторого кода несколько дней назад. У него была базовая проверка свойства, например.
if ( $object.Property )
{
$object.Property = $value
}
Я хотел назначить значение свойству объекта только в том случае, если оно существует. В большинстве случаев исходный объект имел значение, которое будет оцениваться как $true
в инструкции if
. Но я столкнулся с проблемой, когда значение иногда не устанавливалось. Я отладил код и обнаружил, что объект имел свойство, но это было пустое строковое значение. Это не позволило ему обновиться по предыдущей логике. Я добавил правильную проверку $null
, и все заработало.
if ( $null -ne $object.Property )
{
$object.Property = $value
}
Это такие мелкие ошибки, которые трудно обнаружить и из-за которых я начинаю внимательно проверять значения $null
.
$null.Количество
Если вы пытаетесь получить доступ к свойству значения $null
, это свойство также является $null
. Свойство Count
является исключением из этого правила.
PS> $value = $null
PS> $value.Count
0
Когда у вас есть значение $null
, тогда Count
равно 0
. Это специальное свойство добавляется PowerShellом.
[PSCustomObject] Количество
Почти все объекты в PowerShell имеют это свойство Count
. Одним из важных исключений является [pscustomobject]
в Windows PowerShell 5.1 (это исправлено в PowerShell 6.0). У него нет свойства Count
, поэтому вы получите $null
значение, если вы пытаетесь использовать его. Я указываю на это здесь, чтобы вы не пытались использовать Count
вместо проверки $null
.
Выполнение этого примера в Windows PowerShell 5.1 и PowerShell 6.0 дает различные результаты.
$value = [pscustomobject]@{Name='MyObject'}
if ( $value.Count -eq 1 )
{
"We have a value"
}
Перечисление 'null'
Существует один особый тип $null
, который действует не так, как другие. Я собираюсь назвать объект перечисляемым null, но это действительно System.Management.Automation.Internal.AutomationNull.
Это перечисленное значение NULL является результатом функции или блока скрипта, возвращающего ничего (пустой результат).
PS> function Get-Nothing {}
PS> $nothing = Get-Nothing
PS> $null -eq $nothing
True
Если сравнить его с $null
, вы получите значение $null
. При использовании в оценке, когда требуется значение, оно всегда равно $null
. Но если поместить его в массив, он рассматривается так же, как пустой массив.
PS> $containEmpty = @( @() )
PS> $containNothing = @($nothing)
PS> $containNull = @($null)
PS> $containEmpty.Count
0
PS> $containNothing.Count
0
PS> $containNull.Count
1
Массив может содержать одно значение $null
, и его Count
является 1
. Но если поместить пустой массив внутри массива, он не считается элементом. Счёт 0
.
Если вы обрабатываете перечисляемое значение null как коллекцию, то она будет пустой.
Если вы передаете перечисляемое null в параметр функции, который не является строго типизированным, PowerShell по умолчанию преобразует перечисляемое null в значение $null
. Это означает, что внутри функции значение обрабатывается как $null
вместо типа System.Management.Automation.Internal.AutomationNull.
Трубопровод
Основное место, где вы видите разницу, заключается в использовании конвейера. Можно передать значение $null
, но не перечисляемое значение null.
PS> $null | ForEach-Object{ Write-Output 'NULL Value' }
'NULL Value'
PS> $nothing | ForEach-Object{ Write-Output 'No Value' }
В зависимости от вашего кода, следует учесть $null
в своей логике.
Либо сначала проверьте наличие $null
- Удаление null на конвейере (
... | where {$null -ne $_} | ...
) - Обработайте его в функции конвейера
foreach
Одна из моих любимых функций foreach
заключается в том, что она не перебирает коллекцию $null
.
foreach ( $node in $null )
{
#skipped
}
Это спасает меня от необходимости $null
проверить коллекцию перед перечислением. Если у вас есть коллекция значений $null
, $node
по-прежнему может быть $null
.
foreach
начал работать таким образом с PowerShell 3.0. Если вы используете более старую версию, то это не актуально. Это одно из важных изменений, которые следует учитывать при обратном переносе кода для совместимости 2.0.
Типы значений
Теоретически только ссылочные типы могут быть $null
. Но PowerShell очень щедрый и позволяет переменным быть любым типом. Если вы решите жестко определить тип значения, его нельзя будет $null
.
PowerShell преобразует $null
в значение по умолчанию для многих типов.
PS> [int]$number = $null
PS> $number
0
PS> [bool]$boolean = $null
PS> $boolean
False
PS> [string]$string = $null
PS> $string -eq ''
True
Существуют некоторые типы, которые не имеют корректного преобразования из $null
. Эти типы создают ошибку Cannot convert null to type
.
PS> [datetime]$date = $null
Cannot convert null to type "System.DateTime".
At line:1 char:1
+ [datetime]$date = $null
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
Параметры функции
Использование строго типизированных значений в параметрах функции очень распространено. Как правило, мы научимся определять типы наших параметров, даже если мы, как правило, не определяем типы других переменных в наших скриптах. Возможно, у вас уже есть некоторые строго типизированные переменные в ваших функциях, и вы даже этого не осознаете.
function Do-Something
{
param(
[string] $Value
)
}
После установки типа параметра в качестве string
значение никогда не может быть $null
. Принято проверять, равно ли значение $null
, чтобы понять, предоставил ли пользователь данные.
if ( $null -ne $Value ){...}
$Value
является пустой строкой ''
, если значение не указано. Вместо этого используйте автоматическую переменную $PSBoundParameters.Value
.
if ( $null -ne $PSBoundParameters.Value ){...}
$PSBoundParameters
содержит только параметры, указанные при вызове функции.
Для проверки свойства можно также использовать метод ContainsKey
.
if ( $PSBoundParameters.ContainsKey('Value') ){...}
НеЯвляетсяПустымИлиНулевым
Если значение является строкой, можно использовать статическую строковую функцию для проверки того, является ли значение $null
или пустой строкой одновременно.
if ( -not [string]::IsNullOrEmpty( $value ) ){...}
Я часто использую это, когда знаю, что тип значения должен быть строкой.
Когда я $null проверяю
Я пишу скрипты для защиты. Каждый раз, когда я вызываю функцию и назначаю ее переменной, я проверяю ее на $null
.
$userList = Get-ADUser kevmar
if ($null -ne $userList){...}
Я предпочитаю использовать if
или foreach
по сравнению с try/catch
. Не поймите меня неправильно, я все еще много использую try/catch
. Но если я могу проверить состояние ошибки или пустой набор результатов, можно разрешить обработку исключений для истинных исключений.
Я также, как правило, проверяю наличие $null
перед индексированием значения или вызовом методов объекта. Эти два действия завершаются ошибкой для объекта $null
, поэтому я считаю, что важно сначала проверить их. Я уже рассмотрел эти сценарии ранее в этом посте.
Нет сценария результатов
Важно знать, что различные функции и команды обрабатывают сценарий отсутствия результатов по-разному. Многие команды PowerShell возвращают перечисленное значение NULL и ошибку в потоке ошибок. Но другие выбрасывают исключения или предоставляют соответствующий объект состояния. Вам по-прежнему необходимо понимать, как команды, которые вы используете, справляются в случае отсутствия результатов и возникновения ошибок.
Инициализация в $null
Одна из привычек, которую я приобрёл, — это инициализировать все мои переменные перед их использованием. Это необходимо сделать на других языках. В начале функции или по мере входа в цикл foreach
, я определяю все используемые значения.
Вот сценарий, на который я хочу, чтобы вы внимательно посмотрели. Это пример ошибки, которую мне пришлось отслеживать до этого.
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
}
}
}
Ожидается, что Get-Something
возвращает либо результат, либо перечисляемый NULL. Если возникла ошибка, мы регистрируем ее. Затем мы проверяем, чтобы убедиться, что мы получили допустимый результат перед обработкой.
Ошибка, скрытая в этом коде, заключается в том, что Get-Something
создает исключение и не назначает значение $result
. Он завершается ошибкой до назначения, поэтому мы даже не назначаем $null
переменной $result
.
$result
всё ещё содержит предыдущие валидные $result
из других итераций.
Update-Something
выполнить несколько раз на одном и том же объекте в этом примере.
Я устанавливаю $result
$null
прямо внутри цикла foreach
, прежде чем использовать его для устранения этой проблемы.
foreach ( $node in 1..6 )
{
$result = $null
try
{
...
Проблемы с масштабом
Это также помогает устранить проблемы с областью охвата. В этом примере мы присваиваем значения $result
снова и снова в цикле. Но так как PowerShell позволяет переменным значениям извне функции входить в область текущей функции, инициализация их внутри функции устраняет ошибки, которые можно ввести таким образом.
Неинициализированная переменная в вашей функции не будет равна $null
, если в родительской области ей присвоено значение.
Родительская область видимости может быть другой функцией, которая вызывает вашу функцию и использует те же имена переменных.
Если я возьму тот же пример Do-something
и удалю цикл, я получу в итоге что-то похожее на этот пример.
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
}
}
Если вызов Get-Something
вызывает исключение, то проверка $null
находит $result
из Invoke-Something
. Инициализация переменной внутри функции устраняет эту проблему.
Именование переменных является сложной задачей, и авторы часто используют одни и те же имена переменных в разных функциях. Я знаю, что я использую $node
,$result
,$data
все время. Таким образом, значения из разных областей могут легко появляться там, где это не предусмотрено.
Перенаправление выходных данных в $null
Я говорил о значениях $null
на протяжении всей этой статьи, но тема не завершена, если не упомяну перенаправление выходных данных в $null
. Иногда возникают команды, которые выводят информацию или объекты, которые нужно отключить. Перенаправление выходных данных в $null
осуществляет это действие.
Out-Null
Команда Out-Null — это встроенный способ перенаправления данных конвейера в $null
.
New-Item -Type Directory -Path $path | Out-Null
Назначить $null
Результаты команды можно присвоить переменной $null
для того же эффекта, что и с помощью Out-Null
.
$null = New-Item -Type Directory -Path $path
Так как $null
является константным значением, его нельзя перезаписать. Мне не нравится, как он выглядит в моем коде, но он часто выполняется быстрее, чем Out-Null
.
Перенаправление на $null
Можно также использовать оператор перенаправления для отправки выходных данных в $null
.
New-Item -Type Directory -Path $path > $null
Если вы работаете с исполняемыми файлами командной строки, которые выводят данные в разные потоки. Вы можете перенаправить все выходные потоки в $null
следующим образом:
git status *> $null
Сводка
Я охватил множество тем на этот раз и знаю, что эта статья более фрагментирована, чем большинство моих детальных исследований. Это связано с тем, что $null
-значения могут появляться во многих разных местах в PowerShell, и все нюансы зависят от того, где они появляются. Я надеюсь, что вы уйдете от этого с лучшим пониманием $null
и осведомленностью о более неясных сценариях, с которыми вы можете столкнуться.
PowerShell