Выберите значения одного свойства для всех объектов массива в PowerShell - PullRequest
104 голосов
/ 03 марта 2011

Допустим, у нас есть массив объектов $ objects. Допустим, эти объекты имеют свойство «Имя».

Это то, что я хочу сделать

 $results = @()
 $objects | %{ $results += $_.Name }

Это работает, но можно ли сделать это лучше?

Если я сделаю что-то вроде:

 $results = objects | select Name

$results - массив объектов, имеющих свойство Name. Я хочу, чтобы $ results содержал массив имен.

Есть ли лучший способ?

Ответы [ 3 ]

181 голосов
/ 03 марта 2011

Я думаю, что вы можете использовать параметр 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
57 голосов
/ 24 июля 2014

В качестве еще более простого решения вы можете просто использовать:

$results = $objects.Name

Который должен заполнять $results массивом всех значений свойств 'Name' элементов в $objects.

18 голосов
/ 20 февраля 2018

В дополнение к уже существующим, полезным ответам с указанием , когда использовать, какой подход и сравнение производительности .

  • За пределами конвейера используйте: $objects<a href="https://blogs.msdn.microsoft.com/powershell/2012/06/13/new-v3-language-features/" rel="nofollow noreferrer">.</a>Name (PSv3 +) , как показано в ответе rageandqq , который и синтаксически проще, и намного быстрее .

    • Доступ к свойству на уровне collection для получения значений членов в виде массива называется перечислением члена и PSv3 + функция .
    • В качестве альтернативы, в PSv2 используйте оператор foreach , выход которого также можно назначить непосредственно переменной:
      $results = foreach ($obj in $objects) { $obj.Name }
    • Компромисс :
      • И входной массив , и массив вывода должны помещаться в память в целом .
      • Если входная коллекция сама является результатом команды (конвейера) (например, (Get-ChildItem).Name), эта команда должна сначала выполняться до завершения , прежде чем к элементам результирующего массива можно будет получить доступ.
  • В конвейере , где результат должен обрабатываться дальше или результаты не помещаются в память в целом, использовать: $objects | <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-object" rel="nofollow noreferrer">Select-Object</a> -ExpandProperty Name</strong>

    • Необходимость -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 })
  • Этот подход аналогичен перечислению членов , с теми же компромиссами, за исключением того, что конвейерная логика не применяется; это немного медленнее , хотя все же заметно быстрее, чем конвейер.

  • Для извлечения единственного значения свойства по name ( string аргумент) это решение наравне с перечислением членов (хотя последнее синтаксически проще).

  • Блок сценариев вариант , хотя и намного медленнее, допускает произвольные преобразования ; это быстрее - все в оперативной памяти - альтернатива командлету 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({ ... }) значительно замедляет работу, так что он практически соответствует быстрейшему конвейерному решению (* 1192) *).

  • % Number (ForEach-Object Number), что любопытно, работает хуже, хотя % Number является концептуальным эквивалентом % { $_.Number }).


Исходный код для тестов :

Примечание. Для запуска этих тестов загрузите функцию Time-Command из this 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...