Почему PowerShell применяет предикат «Где» к пустому списку

Если я запущу это в 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

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

Во-первых, = не -eq! Второй вариант «работает», потому что Where никогда не оценивается (коллекция пуста). Замените "[]" на "[{}]" для большего понимания. Что касается того, почему то же самое не относится к первому варианту (т.е. почему есть конвейер и к нему применяется Where) - это более интересно и, вероятно, связано с тонкостями ConvertFrom-Json...

Jeroen Mostert 08.04.2019 16:24

Голосование за закрытие как опечатка. Проблема в =, больше ни в чем.

Maximilian Burszley 08.04.2019 16:36

@TheIncorrigible1 Опечатка исправлена. Также остановился на настройке строгого режима, что я пропустил. Проблема сохраняется.

ledneb 08.04.2019 16:43

В строгом режиме возникает исключение, если вы пытаетесь получить доступ к несуществующему свойству. Если вы хотите избежать этого, вам следует использовать один из других наборов параметров, таких как: | ? Name -eq Baz

Maximilian Burszley 08.04.2019 16:45

Чтобы устранить возможную путаницу в отношении того, о чем этот вопрос: вопрос заключается в том, почему ввод JSON, который становится пустой массив в PowerShell через ConvertFrom-Json, неожиданно по-прежнему отправляет объект через конвейер и выполняет блок сценария Where-Object, тогда как он не делает этого, если вы использовать пустой массив напрямую (@() | Where ...).

mklement0 09.04.2019 16:36
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
2
5
796
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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 не пытаться перечислить его вывод?

ledneb 08.04.2019 16:53

Спасибо за обновленный ответ! Почему это было бы желательно? Запрет, похоже, нарушает конвейер, но я предполагаю, что есть веская причина, по которой мы не будет хотим, чтобы что-то перечислялось позже в конвейере?

ledneb 08.04.2019 16:59

... Я полагаю, что если бы я хотел передать результирующий объект, декодированный json, во что-то еще, было бы больно, если бы он был разложен на несколько объектов, когда он просто оказался массивом. Это приведет к множественным вызовам следующего этапа конвейера, хотя обычно мы ожидаем один. Хорошо, еще раз спасибо, Матиас, кричи, если я ошибаюсь :)

ledneb 08.04.2019 17:04

Удивительное «не похожее на PowerShell» поведение ConvertFrom-Json по умолчанию обсуждается в github.com/PowerShell/PowerShell/issues/3424, наряду с возможным введением переключателя, такого как -[No]Enumerate, чтобы обеспечить контроль над поведением перечисления.

mklement0 08.04.2019 18:45

Придираюсь: Where-Object никогда не знает, как предыдущий сегмент конвейера произвел свой вывод — он просто работает с входными данными, как указано: один объект, который оказывается массивом с прямым ConvertFrom-Json вызовом, и принудительное перечисление этого массива, когда оно заключено в (...).

mklement0 08.04.2019 18:50

@ mklement0 да, вот почему я пояснил, что это «вызывает перечисление время выполнения в нет [...] во время привязки параметров к нижестоящему командлету», но для первоначального ответа TLDR / ELI5 я думаю, что справедливо будет сказать, что « Где-Объект не знает" :)

Mathias R. Jessen 09.04.2019 16:16

Но вверху все еще есть фраза Потому что ConvertFrom-Json сообщает Where-Object не пытаться перечислять свой вывод, которая вводит в заблуждение, потому что первая ничего не говорит второй. Во всяком случае, первый говорит трубопровод не перечислять.

mklement0 09.04.2019 16:20

Я полностью согласен, более технически правильной фразой будет Поскольку ConvertFrom-Json указывает среде выполнения не пытаться перечислять свои выходные данные, прежде чем решить, как связать их с входными параметрами, предоставляемыми Where-Object., а еще более правильной версией будет «Когда вы нажимаете ввод, сигнал отправляется на шину аппаратного сканера, к которой подключена ваша клавиатура, что, в свою очередь, вызывает [436802 слов позже . ..] и consolhostv2.dll, наконец, вызывает средство визуализации GFX..." - я утверждаю, что мой упрощение подходит для обсуждаемого нами уровня абстракции :)

Mathias R. Jessen 09.04.2019 16:37

Игнорируя необоснованный сарказм: для меня это не упрощение, это ложная абстракция, которая активно предполагает несуществующие отношения, о чем свидетельствует вопрос @ledneb.

mklement0 10.04.2019 00:48

С пустой массив в качестве прямого ввода конвейера, ничего отправляется через конвейер, потому что массив перечисленный, и так как нечего перечислять - потому что пустой массив не имеет элементов - блок скрипта 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 (автор Джеффри Сновер, не меньше).

Другие вопросы по теме