Почему PowerShell применяет предикат `где` к пустому списку - PullRequest
2 голосов
/ 08 апреля 2019

Если я запускаю это в PowerShell, я ожидаю увидеть вывод 0 (ноль):

Set-StrictMode -Version Latest

$x = "[]" | ConvertFrom-Json | Where { $_.name -eq "Baz" }
Write-Host $x.Count

Вместо этого я получаю эту ошибку:

The property 'name' cannot be found on this object. Verify that the     property exists and can be set.
At line:1 char:44
+     $x = "[]" | ConvertFrom-Json | Where { $_.name -eq "Baz" }
+                                            ~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException

Если я поставлю скобки вокруг "[]" | ConvertFrom-Json, то получится:

$y = ("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }
Write-Host $y.Count

И тогда это "работает".

Что не так перед вводом скобок?

Чтобы объяснить кавычки вокруг «работ» - установка строгого режима Set-StrictMode -Version Latest указывает, что я вызываю .Count для $null объекта. Это решается упаковкой в ​​@():

$z = @(("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" })
Write-Host $z.Count

Я нахожу это довольно неудовлетворительным, но это не совсем актуальный вопрос.

Ответы [ 2 ]

4 голосов
/ 08 апреля 2019

Почему PowerShell применяет предикат Where к пустому списку?

Поскольку ConvertFrom-Json говорит Where-Object не пытаться перечислить свои выходные данные.

Поэтому PowerShell пытается получить доступ к свойству name в самом пустом массиве, как если бы мы делали:

$emptyArray = New-Object object[] 0
$emptyArray.name

Когда вы заключаете ConvertFrom-Json в скобки, powershell интерпретирует его как отдельный конвейер, который выполняется и заканчивается до , любой вывод может быть отправлен на Where-Object, и поэтому Where-Object не может знать, что ConvertFrom-Json хотел, чтобы он обрабатывал массив как таковой.


Мы можем воссоздать это поведение в powershell, явно вызвав Write-Output с установленным параметром переключателя -NoEnumerate:

# create a function that outputs an empty array with -NoEnumerate
function Convert-Stuff 
{
  Write-Output @() -NoEnumerate
}

# Invoke with `Where-Object` as the downstream cmdlet in its pipeline
Convert-Stuff | Where-Object {
  # this fails
  $_.nonexistingproperty = 'fail'
}

# Invoke in separate pipeline, pass result to `Where-Object` subsequently
$stuff = Convert-Stuff
$stuff | Where-Object { 
  # nothing happens
  $_.nonexistingproperty = 'meh'
}

Write-Output -NoEnumerate внутренне вызывает Cmdlet.WriteObject(arg, false),что, в свою очередь, приводит к тому, что время выполнения не перечисляет значение arg во время привязки параметра к нижестоящему командлету (в вашем случае Where-Object)


Зачем этобыть желанным?

ВВ конкретном контексте синтаксического анализа JSON такое поведение действительно может быть желательным:

$data = '[]', '[]', '[]', '[]' |ConvertFrom-Json

Разве я не должен ожидать ровно 5 объектов из ConvertFrom-Json теперь, когда я передал в него 5 действительных документов JSON?: -)

2 голосов
/ 08 апреля 2019

При пустом массиве в качестве прямого конвейерного ввода ничего отправляется по конвейеру , потому что массив имеет перечисление и поскольку перечислять нечего - пустой массив не имеет элементов - блок скрипта Where никогда не выполняется:

# The empty array is enumerated, and since there's nothing to enumerate,
# the Where[-Object] script block is never invoked.
@() | Where { $_.name -eq "Baz" } 

В отличие от этого, "[]" | ConvertFrom-Json создает пустой массив в качестве единственного выходного объекта , а не перечисляет его (несуществующие) элементы, поскольку ConvertFrom-Json по типу не делает не перечислять элементы массивов, которые он выводит ; это эквивалент:

# Empty array is sent as a single object through the pipeline.
# The Where script block is invoked once and sees $_ as that empty array.
Write-Output -NoEnumerate @() | Where { $_.name -eq "Baz" }
Поведение

ConvertFrom-Json удивительно в контексте PowerShell - командлеты обычно перечисляют несколько выходов - но имеет смысл в контексте анализа JSON ; в конце концов, информация будет потеряна , если ConvertFrom-Json перечислит пустой массив, учитывая, что вы не сможете отличить это от пустого ввода JSON ("" | ConvertFrom-Json).

Это напряжение обсуждается в этом выпуске GitHub .

Консенсус заключается в том, что оба варианта использования являются законными и что пользователи должны иметь выбор между двумя вариантами поведения - перечислением или нет - с помощью переключателя ; Начиная с PowerShell Core 6.2.0 формальное решение не было принято, но если необходимо сохранить обратную совместимость, это должно быть поведение перечисления, которое будет opt-in (например, -Enumerate) .

Если требуется перечисление, то пока что - неясный - обходной путь - принудительное перечисление , просто заключив вызов ConvertFrom-Json в (...) (который преобразует его в выражение , а выражения всегда перечисляют выходные данные команды при использовании в конвейере):

# (...) around the ConvertFrom-Json call forces enumeration of its output.
# The empty array has nothing to enumerate, so the Where script block is never invoked.
("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }

Что касается того, что вы пытались : ваша попытка получить доступ к свойству .Count и ваше использование @(...):

$y = ("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }
$y.Count # Fails with Set-StrictMode -Version 2 or higher

С вызовом ConvertFrom-Json, заключенным в (...), ваша общая команда возвращает «ничто»: свободно говоря, $null, но, точнее, «null-значение со значением массива», которое является [System.Management.Automation.Internal.AutomationNull]::Value singleton это указывает на отсутствие вывода команды. (В большинстве случаев последний обрабатывается так же, как $null, хотя, в частности, не используется в качестве входных данных конвейера.)

[System.Management.Automation.Internal.AutomationNull]::Value не имеет свойства .Count, поэтому при действии Set-StrictMode -Version 2 или выше вы получите ошибку The property 'count' cannot be found on this object..

Оборачивая весь конвейер в @(...), оператор подвыражения массива, вы гарантируете обработку выходных данных как array , который, с помощью [null output с массивом, создает пустой массив - который имеет свойство .Count.

Обратите внимание, что вы должны иметь возможность вызывать .Count на $null и [System.Management.Automation.Internal.AutomationNull]::Value, учитывая, что PowerShell добавляет свойство .Count к каждому объект, если он еще не существует, включая скаляры, в похвальном стремлении унифицировать обработку коллекций и скаляров.

То есть с Set-StrictMode, установленным на -Off (по умолчанию) или на -Version 1, следующее работает и работает - разумно - возвращает 0:

# With Set-StrictMode set to -Off (the default) or -Version 1:

# $null sensibly has a count of 0.
PS> $null.Count
0

# So does the "array-valued null", [System.Management.Automation.Internal.AutomationNull]::Value 
# `. {}` is a simple way to produce it.
PS> (. {}).Count # `. {}` outputs 
0

То, что вышеупомянутое в настоящее время не не работает с Set-StrictMode -Version 2 или выше (по состоянию на PowerShell Core 6.2.0), должно рассматриваться как ошибка , как сообщалось в этом выпуске GitHub (не менее Джеффри Сновером).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...