Выберите значения одного свойства для всех объектов массива в PowerShell
Допустим, у нас есть массив объектов $ objects. Допустим, эти объекты имеют свойство "Имя".
Это то, что я хочу сделать
$results = @()
$objects | %{ $results += $_.Name }
Это работает, но можно ли сделать это лучше?
Если я сделаю что-то вроде:
$results = objects | select Name
$results
является массивом объектов, имеющих свойство Name. Я хочу, чтобы $results содержал массив Имен.
Есть ли способ лучше?
5 ответов
Я думаю, что вы могли бы использовать ExpandProperty
параметр Select-Object
,
Например, чтобы получить список текущего каталога и просто отобразить свойство Name, нужно сделать следующее:
ls | select -Property Name
Это все еще возвращает объекты DirectoryInfo или FileInfo. Вы всегда можете проверить тип, проходящий через конвейер, отправив по конвейеру Get-Member (псевдоним gm
).
ls | select -Property Name | gm
Таким образом, чтобы расширить объект до типа свойства, на которое вы смотрите, вы можете сделать следующее:
ls | select -ExpandProperty Name
В вашем случае вы можете просто сделать следующее, чтобы переменная была массивом строк, где строки являются свойством Name:
$objects = ls | select -ExpandProperty Name
В качестве еще более простого решения вы можете просто использовать:
$results = $objects.Name
Который должен заполнить $results
с массивом всех значений свойств 'Name' элементов в $objects
,
В дополнение к уже существующим, полезные ответы с указанием того, когда использовать какой подход и сравнение производительности.
Вне трубопровода используйте:
$ объекты . название
(PSv3+), как показано в ответе rageandqq, который синтаксически проще и намного быстрее.- Доступ к свойству на уровне коллекции для получения значений его членов в виде массива называется перечислением членов и является функцией PSv3+;
- В качестве альтернативы, в PSv2 используйте
foreach
оператор, чей вывод вы также можете назначить непосредственно переменной:$ results = foreach ($ obj in $ objects) {$ obj.Name}
- Компромиссы:
- Как входная коллекция, так и выходной массив должны помещаться в память в целом.
- Если входная коллекция сама является результатом команды (конвейера) (например,
(Get-ChildItem).Name
), эта команда должна сначала выполняться до завершения, прежде чем будут доступны элементы результирующего массива.
В конвейере, где результат должен обрабатываться дальше или результаты не помещаются в память в целом, используйте:
$ объекты | Select-Object -ExpandProperty Name
- Нужда в
-ExpandProperty
объясняется в ответе Скотта Саада. - Вы получаете обычные преимущества конвейерной обработки поочередной обработки, которая обычно производит вывод сразу и поддерживает постоянное использование памяти (если вы все равно не соберете результаты в памяти).
- Обмен:
- Использование трубопровода является относительно медленным.
- Нужда в
Для небольших входных коллекций (массивов) вы, вероятно, не заметите разницы, и, особенно в командной строке, иногда возможность набрать команду легко важнее.
Вот простая в вводе альтернатива, которая, однако, является самым медленным подходом; он использует упрощенный ForEach-Object
Синтаксис вызвал оператор операции (опять же, PSv3+):; Например, следующее решение PSv3+ легко добавить к существующей команде:
$objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name }
Ради полноты: малоизвестный PSv4+ .ForEach()
Метод сбора является еще одной альтернативой:
# By property name (string):
$objects.ForEach('Name')
# By script block (much slower):
$objects.ForEach({ $_.Name })
Этот подход аналогичен перечислению членов с теми же компромиссами, за исключением того, что конвейерная логика не применяется; это немного медленнее, хотя все же заметно быстрее, чем конвейер.
Для извлечения единственного значения свойства по имени (строковый аргумент) это решение наравне с перечислением членов (хотя последнее синтаксически проще).
Вариант блока сценариев, хотя и намного медленнее, допускает произвольные преобразования; это более быстрая - все в памяти за один раз - альтернатива на основе конвейера
ForEach-Object
командлет
Сравнивая эффективность различных подходов
Вот примеры времени для различных подходов, основанные на входной коллекции 100,000
объекты, усредненные по 100 пробегам; абсолютные числа не важны и варьируются в зависимости от многих факторов, но они должны дать вам представление об относительной эффективности:
Command FriendlySecs (100-run avg.) Factor
------- --------------------------- ------
$objects.ForEach('Number') 0.078 1.00
$objects.Number 0.079 1.02
foreach($o in $objects) { $o.Number } 0.188 2.42
$objects | Select-Object -ExpandProperty Number 0.881 11.36
$objects.ForEach({ $_.Number }) 0.925 11.93
$objects | % { $_.Number } 1.564 20.16
$objects | % Number 2.974 38.35
Решение с использованием метода сбора данных, основанного на перечислении членов / свойствах, работает в 10 раз быстрее, чем самое быстрое решение на основе конвейера.
foreach
Решение оператора примерно в 2,5 раза медленнее, но все же примерно в 4-5 раз быстрее, чем самое быстрое конвейерное решение.Использование блока скрипта с решением метода сбора (
.ForEach({ ... }
) существенно замедляет процесс, так что он практически на одном уровне с самым быстрым конвейерным решением (Select-Object -ExpandProperty
).% Number
(ForEach-Object Number
), что любопытно, работает хуже, хотя% Number
является концептуальным эквивалентом% { $_.Number }
).
Исходный код для тестов:
Примечание: функция загрузки Time-Command
из этого Gist, чтобы запустить эти тесты.
$count = 1e5 # input-object count (100,000)
$runs = 100 # number of runs to average
# Create sample input objects.
$objects = 1..$count | % { [pscustomobject] @{ Number = $_ } }
# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Number },
{ $objects | % Number },
{ $objects | % { $_.Number } },
{ $objects.ForEach('Number') },
{ $objects.ForEach({ $_.Number }) },
{ $objects.Number },
{ foreach($o in $objects) { $o.Number } }
# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Command, FriendlySecs*, Factor
Внимание! Перечисление членов работает только в том случае, если в самой коллекции нет члена с таким же именем. Итак, если у вас есть массив объектов FileInfo, вы не можете получить массив длин файлов, используя
$files.length # evaluates to array length
И прежде чем вы скажете "хорошо, очевидно", подумайте об этом. Если у вас был массив объектов со свойством емкости, тогда
$objarr.capacity
будет работать нормально, ЕСЛИ $objarr на самом деле не [Array], а, например, [ArrayList]. Поэтому перед использованием перечисления членов вам, возможно, придется заглянуть в черный ящик, содержащий вашу коллекцию.
(Примечание для модераторов: это должен быть комментарий к ответу rageandqq, но у меня еще недостаточно репутации.)
Каждый день узнаю что-то новое! Спасибо тебе за это. Я пытался добиться того же. Я делал это прямо:
$ListOfGGUIDs = @()