Если я запущу это в 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
Я нахожу это весьма неудовлетворительным, но это отступление от фактического вопроса.
Голосование за закрытие как опечатка. Проблема в =
, больше ни в чем.
@TheIncorrigible1 Опечатка исправлена. Также остановился на настройке строгого режима, что я пропустил. Проблема сохраняется.
В строгом режиме возникает исключение, если вы пытаетесь получить доступ к несуществующему свойству. Если вы хотите избежать этого, вам следует использовать один из других наборов параметров, таких как: | ? Name -eq Baz
Чтобы устранить возможную путаницу в отношении того, о чем этот вопрос: вопрос заключается в том, почему ввод JSON, который становится пустой массив в PowerShell через ConvertFrom-Json
, неожиданно по-прежнему отправляет объект через конвейер и выполняет блок сценария Where-Object
, тогда как он не делает этого, если вы использовать пустой массив напрямую (@() | Where ...
).
Why is PowerShell applying the predicate of a
Where
to an empty list?
Потому что 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
)
Why would this be desireable?
В конкретном контексте синтаксического анализа JSON такое поведение действительно может быть желательным:
$data = '[]', '[]', '[]', '[]' |ConvertFrom-Json
Разве я не должен ожидать ровно 5 объектов от ConvertFrom-Json
теперь, когда я передал ему 5 действительных документов JSON? :-)
Интересно! Как ConvertFrom-Json
«говорит» Where-Object
не пытаться перечислить его вывод?
Спасибо за обновленный ответ! Почему это было бы желательно? Запрет, похоже, нарушает конвейер, но я предполагаю, что есть веская причина, по которой мы не будет хотим, чтобы что-то перечислялось позже в конвейере?
... Я полагаю, что если бы я хотел передать результирующий объект, декодированный json, во что-то еще, было бы больно, если бы он был разложен на несколько объектов, когда он просто оказался массивом. Это приведет к множественным вызовам следующего этапа конвейера, хотя обычно мы ожидаем один. Хорошо, еще раз спасибо, Матиас, кричи, если я ошибаюсь :)
Удивительное «не похожее на PowerShell» поведение ConvertFrom-Json
по умолчанию обсуждается в github.com/PowerShell/PowerShell/issues/3424, наряду с возможным введением переключателя, такого как -[No]Enumerate
, чтобы обеспечить контроль над поведением перечисления.
Придираюсь: Where-Object
никогда не знает, как предыдущий сегмент конвейера произвел свой вывод — он просто работает с входными данными, как указано: один объект, который оказывается массивом с прямым ConvertFrom-Json
вызовом, и принудительное перечисление этого массива, когда оно заключено в (...)
.
@ mklement0 да, вот почему я пояснил, что это «вызывает перечисление время выполнения в нет [...] во время привязки параметров к нижестоящему командлету», но для первоначального ответа TLDR / ELI5 я думаю, что справедливо будет сказать, что « Где-Объект не знает" :)
Но вверху все еще есть фраза Потому что ConvertFrom-Json сообщает Where-Object не пытаться перечислять свой вывод, которая вводит в заблуждение, потому что первая ничего не говорит второй. Во всяком случае, первый говорит трубопровод не перечислять.
Я полностью согласен, более технически правильной фразой будет Поскольку ConvertFrom-Json указывает среде выполнения не пытаться перечислять свои выходные данные, прежде чем решить, как связать их с входными параметрами, предоставляемыми Where-Object., а еще более правильной версией будет «Когда вы нажимаете ввод, сигнал отправляется на шину аппаратного сканера, к которой подключена ваша клавиатура, что, в свою очередь, вызывает [436802 слов позже . ..] и consolhostv2.dll
, наконец, вызывает средство визуализации GFX..." - я утверждаю, что мой упрощение подходит для обсуждаемого нами уровня абстракции :)
Игнорируя необоснованный сарказм: для меня это не упрощение, это ложная абстракция, которая активно предполагает несуществующие отношения, о чем свидетельствует вопрос @ledneb.
С пустой массив в качестве прямого ввода конвейера, ничего отправляется через конвейер, потому что массив перечисленный, и так как нечего перечислять - потому что пустой массив не имеет элементов - блок скрипта Where
(Where-Object
) никогда не выполняется:
Set-StrictMode -Version Latest
# The empty array is enumerated, and since there's nothing to enumerate,
# the Where[-Object] script block is never invoked.
@() | Where { $_.name -eq "Baz" }
Напротив, в версиях PowerShell до v6.x"[]" | ConvertFrom-Json
создает пустой массив как единый объект вывода, а не перечисляет его (несуществующие) элементы, потому что ConvertFrom-Json
в этих версиях не перечисляет элементы массивов, которые он выводит; это эквивалент:
Set-StrictMode -Version Latest
# Empty array is sent as a single object through the pipeline.
# The Where script block is invoked once and sees $_ as that empty array.
# Since strict mode is in effect and arrays have no .name property
# an error occurs.
Write-Output -NoEnumerate @() | Where { $_.name -eq "Baz" }
ConvertFrom-Json
ведет себя как удивительный в контексте PowerShell. — командлеты обычно перечислить с несколькими выходами — но можно защитить в контексте синтаксического анализа JSON; в конце концов, информация будет потерянный, если ConvertFrom-Json
перечислит пустой массив, учитывая, что тогда вы не сможете отличить его от пустой ввод JSON ("" | ConvertFrom-Json
).
Консенсус заключался в том, что варианты использования обе являются законными и что пользователи должны иметь выбор между двумя видами поведения — перечислением или нет — посредством выключатель (см. соответствующее обсуждение в эта проблема GitHub).
Следовательно, начиная с PowerShell [Core] 7.0:
Перечисление является теперь выполняется по умолчанию.
Подтверждение поведения Старый доступно с помощью нового переключателя -NoEnumerate
.
В PowerShell 6.x-, если требуется перечисление, то - неясный - обходной путь - это принудительное перечисление, просто заключив вызов 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
, но, точнее, «нулевой массив со значением массива», который является синглтоном [System.Management.Automation.Internal.AutomationNull]::Value
, который указывает на отсутствие вывода из команды. (В большинстве контекстов последний обрабатывается так же, как $null
, хотя, в частности, не при использовании в качестве входных данных конвейера.)
[System.Management.Automation.Internal.AutomationNull]::Value
не имеет свойства .Count
, поэтому при действии Set-StrictMode -Version 2
или выше вы получите ошибку The property 'count' cannot be found on this object.
.
Оборачивая весь конвейер в @(...)
, оператор подвыражения массива, вы обеспечиваете обработку вывода как множество, который с нулевым выводом массива создает пустой массив, делает которого имеет свойство .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] 7.0), следует рассматривать как ошибка., как сообщается в эта проблема GitHub (автор Джеффри Сновер, не меньше).
Во-первых,
=
не-eq
! Второй вариант «работает», потому чтоWhere
никогда не оценивается (коллекция пуста). Замените"[]"
на"[{}]"
для большего понимания. Что касается того, почему то же самое не относится к первому варианту (т.е. почему есть конвейер и к нему применяетсяWhere
) - это более интересно и, вероятно, связано с тонкостямиConvertFrom-Json
...