Примечание: этот ответ дополняет полезный ответ Матиаса Р. Йессена .
Подсчет количества объектов, возвращаемых командой:
Ответ Матиаса показывает надежное, совместимое с PowerShell v2 решение на основе оператора подвыражения массива , @()
.
# @() ensures that the output of command ... is treated as an array,
# even if the command emits only *one* object.
# You can safely call .Count (or .Length) on the result to get the count.
@(...).Count
В PowerShell v3 или выше вы можете обрабатывать скаляры как коллекции , так что с использованием только (...).Count
обычно достаточно . (Скаляр - это отдельный объект, а не набор объектов).
# Even if command ... returns only *one* object, it is safe
# to call .Count on the result in PSv3+
(...).Count
Эти методы обычно , но не всегда взаимозаменяемы , как описано ниже.
Выберите @(...).Count
, если:
- Вы должны оставаться PSv2-совместимым
- вы хотите посчитать вывод из нескольких команд (разделенных
;
или символами новой строки)
- для команд, выводящих целые коллекции как один объект (что встречается редко), вы хотите считать такие коллекции как
1
object .
- в более общем случае, если вам нужно убедиться, что вывод команды возвращается как истинный массив , хотя обратите внимание, что он неизменно имеет тип
[object[]]
; если вам нужен определенный тип элемента , используйте приведение (например, [int[]]
), но учтите, что тогда вам строго не нужен @(...)
; например.,
[int[]] (...)
подойдет - если вы не хотите запретить перечисление коллекций, выводимых как отдельные объекты.
Выберите (...).Count
, если:
- только одна команда должна быть подсчитана
- для команд, которые выводят целые коллекции как один объект , вы хотите считать отдельные элементы таких коллекций; то есть
(...)
вызывает перечисление.
- для подсчета элементов вывода команд , уже сохраненных в переменной - хотя, конечно, вы можете просто опустить
(...)
и использовать $var.Count
Предостережение : из-за давней ошибки (все еще присутствует в PowerShell Core 6.2.0), доступ к .Count
в скаляре терпит неудачу, пока действует Set-StrictMode -Version 2
или выше - используйте @(...)
в этом случае, но учтите, что вам, возможно, придется форсировать перечисление.
Чтобы продемонстрировать разницу в поведении относительно (редких) команд, которые выводят коллекции как отдельные объекты :
PS> @(Write-Output -NoEnumerate (1..10)).Count
1 # Array-as-single-object was counted as *1* object
PS> (Write-Output -NoEnumerate (1..10)).Count
10 # Elements were enumerated.
Особенности производительности :
Если вывод команды напрямую считается, (...)
и @(...)
работают примерно одинаково :
$arr = 1..1e6 # Create an array of 1 million integers.
{ (Write-Output $arr).Count }, { @(Write-Output $arr).Count } | ForEach-Object {
[pscustomobject] @{
Command = "$_".Trim()
Seconds = '{0:N3}' -f (Measure-Command $_).TotalSeconds
}
}
Пример вывода с одноядерной виртуальной машины Windows 10 (абсолютные значения времени не важны, только то, что цифры практически одинаковы):
Command Seconds
------- -------
(Write-Output $arr).Count 0.352
@(Write-Output $arr).Count 0.365
В отличие от этого, для больших коллекций , уже сохраненных в переменной , @(...)
вносит существенные издержки , поскольку коллекция воссоздается как (новый) массив (как отметил, что вы можете просто $arr.Count
):
$arr = 1..1e6 # Create an array of 1 million integers.
{ ($arr).Count }, { @($arr).Count } | ForEach-Object {
[pscustomobject] @{
Command = "$_".Trim()
Seconds = '{0:N3}' -f (Measure-Command $_).TotalSeconds
}
}
Пример вывода; обратите внимание, что решение @(...)
примерно в 7 раз медленнее:
Command Seconds
------- -------
($arr).Count 0.009
@($arr).Count 0.067
Соображения в отношении стиля кодирования :
Следующее применимо в ситуациях, когда @(...)
и (...)
функционально эквивалентны (и либо выполняют то же самое, либо когда производительность вторична), т. Е. Когда вы можете выбирать какую конструкцию использовать .
Матиас рекомендует @(...).Count
, заявив в комментарии:
Есть еще одна причина явно обернуть его в этом контексте - передача намерения , т. Е. "Мы не знаем, является ли $p
скалярным или нет, следовательно, эта конструкция".
Мой голос за (...).Count
:
Как только вы поймете, что PowerShell (v3 или выше) рассматривает скаляры как коллекции с числом 1 по требованию, вы можете использовать эти знания без необходимости отражать различие между скаляром и массивом в синтаксис :
При написании кода это означает , вам не нужно беспокоиться о том, может ли данная команда ситуационно возвращать скаляр вместоcollection (что является обычным в PowerShell, где при захвате вывода команды с помощью выходного объекта single этот объект фиксируется как есть, тогда как выходные объекты 2 или более приводят к массив ).
В качестве полезного побочного эффекта код становится более кратким (а иногда и быстрее).
Пример:
# Call Get-ChildItem twice, and, via Select-Object, limit the
# number of output objects to 1 and 2, respectively.
1..2 | ForEach-Object {
# * In the 1st iteration, $var becomes a *scalar* of type [System.IO.DirectoryInfo]
# * In the 2nd iteration, $var becomes an *array* with
# 2 elements of type [System.IO.DirectoryInfo]
$var = Get-ChildItem -Directory / | Select-Object -First $_
# Treat $var as a collection, which in PSv3+ works even
# if $var is a scalar:
[pscustomobject] @{
Count = $var.Count
FirstElement = $var[0]
DataType = $var.GetType().Name
}
}
Выше приведено:
Count FirstElement DataType
----- ------------ --------
1 /Applications DirectoryInfo
2 /Applications Object[]
То есть даже скалярный объект типа System.IO.DirectoryInfo
сообщил о .Count
разумно как 1
и разрешил доступ к «своему первому элементу»th [0]
.
Подробнее об объединенной обработке скаляров и коллекций см. этот ответ .