Sort-Object -inputObject

Я могу отсортировать массив с помощью конвейера, например $array | Sort-objectТем не менее, я обнаружил, что трубопровод МЕДЛЕННЫЙ, и в основном имеет смысл использовать быстрые одинарные прокладки. Но при написании программы или даже быстрого сценария я считаю, что лучше не использовать конвейер как по причинам удобочитаемости, так и по причинам производительности. Итак, я пошел на SS64 и нашел эту страницу с упоминанием этого
параметра. И я бы подумал, что Sort-Object -InputObject:$arrayбудет то же самое. Но нет, на этой странице конкретно говорится о

Сортируемые объекты.

Когда параметр используется для отправки коллекции элементов, получает один объект, представляющий коллекцию. Поскольку один объект не может быть отсортирован, возвращает всю коллекцию без изменений.

Чтобы отсортировать объекты, выделите их по конвейеру.

Итак, что на земле -InputObjectтогда? Похоже, что единственный возможный вариант использования - тот, который не работает. И есть ли способ использовать Sort-Object на существующем массиве, не прибегая к конвейеру?

3 ответа

Полезный ответ Тео показывает быстрый способ сортировки на месте массива, который уже находится в памяти и хранится в переменной, с помощью .NET API.

Итак, что же тогда такое -InputObject?

К сожалению, для большинства командлетов этот параметр является лишь деталью реализации : его цель - разрешить ввод через конвейер, а его прямое использование с массивами (коллекциями) бессмысленно, например, в случае Sort-Object.

  • Проблема GitHub № 4242 требует, чтобы они были четко задокументированы как таковые, а также содержали список командлетов, которые действительно поддерживают прямое использование, однако не в качестве альтернативы конвейерному вводу, а с другой семантикой , работающей с массивом (коллекцией) в целом, когда используется; например, 1, 'foo' | Get-Member работает (значимо) иначе, чем Get-Member -InputObject (1, 'foo'): Прежние отчеты типов массива элементов , последний тип самого массива .

  • Среди командлетов обработки данных (в отличие от командлетов форматирования ) это эффективно только Write-Output и Out-String(который частично также является командлетом форматирования), которые поддерживают прямое использование с массивами; например:

            # Both commands produce the same output.
    1, 2 | Write-Output
    Write-Output -InputObject 1, 2
    
    • Однако даже здесь поведение вложенных массивов отличается , поскольку глубина перечисления у этих двух методов различается:

                # NOT the same, due to nesting.
      1, (2, (3, 4)) | Write-Output # -> 1, 2, (3, 4)
      Write-Output -InputObject 1, (2, (3, 4)) # -> 1, (2, (3, 4))
      

Отсутствие поддержки прямого аргумента с массивом значений вызывает сожаление, поскольку оно может значительно ускорить работу, особенно с данными, уже находящимися в памяти полностью:

Обход последовательной потоковой передачи, которая неизменно происходит в конвейере (требуя своего рода рукопожатия между отправляющей и принимающей командой для каждого объекта ), может значительно повысить производительность.

Примером может служить параметр Set-ContentКомандлет, который делает принимать прямые аргументы массива многозначных вместо ввода трубопровода, и используя -Value напрямую значительно ускоряет работу.

      # Write 100,000 (1e5) numbers to a file:
# Via the pipeline.
1..1e5 | Set-Content temp.txt
# Via -Value - this is much, much faster.
# E.g. on my macOS machine with PowerShell 7.1, about 100(!) times faster.
Set-Content temp.txt -Value (1..1e5)

Возможные улучшения :

Примечание:

  • Теоретически командлет может свободно реализовать поддержку собственного массива, но это (а) громоздко из-за необходимости дополнительной логики и (б) требует либо объявления параметра как типа массива (что неэффективно, потому что даже отдельные объекты полученные по конвейеру, затем упаковываются в массивы) или объявляют его как object, что потенциально снижает безопасность типов.

В идеале сама PowerShell должна обеспечивать такую ​​поддержку в следующих направлениях:

  • Расширить [Parameter]атрибут с новым логическим свойством, которое можно комбинировать с существующим ValueFromPipeline и ValueFromPipelineByPropertyName properties, которые, если задано значение, будут указывать PowerShell:

    • неявно принимать массивы указанного (скалярного) типа параметра с прямым использованием параметра, например, [int[]] для [int]-типированный параметр

    • и для перечисления этих массивов так же, как в конвейере , вызывая блок командлета (командлеты (расширенные функции), реализованные в PowerShell) / метод (двоичные командлеты) для каждого перечисляемого объекта.

Гипотетический (надуманный) пример:

      function ConvertTo-Long {

  [CmdletBinding()]
  param(
    # WISHFUL THINKING: implicit array support for direct -InputObject arguments
    [Parameter(ValueFromPipeline, EnumerateArgument)]
    [int] $InputObject
  )

  process {
    [long] $InputObject   
  }

}

# The following calls would then be equivalent:
1, 2, 3 | ConvertTo-Long
ConvertTo-Long -InputObject 1, 2, 3

Примечание:

  • Улучшение будет доступно для любого параметра привязки конвейера (не только).

  • Возможно, EnumerateArgument должно быть $true по умолчанию , и что те редкие командлеты, в которых передача массива в качестве аргумента имеет другое значение , например Get-Member, следует отказаться - однако это будет проблемой для обратной совместимости .

  • Поскольку предлагаемое усовершенствование по-прежнему будет включать вызов process блок / .ProcessRecord()метода для каждого пронумерованного объекта , ускорение не будет таким значительным, как с пользовательской реализацией, которая сама выполняет перечисление за один вызов. Однако для меня перспектива унификации поведения между конвейером и прямым -InputObject одно только использование делает это улучшение стоящим.

В зависимости от того, что находится внутри вашего массива $, вы, вероятно, можете использовать .Net Array.Sort() для повышения производительности при сортировке элементов в одномерном массиве.

      [array]::Sort($array)

Тип [array]::Sort без скобок и нажмите Enter, чтобы увидеть все доступные OverloadDefinitions.

предназначен для использования из конвейера, а не для прямого аргумента. Используйте
так:

      1, 5, 10, 3, 2 | Sort-Object # ==> 1, 2, 3, 5, 10

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

Когда вы вызываете аргумент напрямую, коллекция не разворачивается. поэтому в случае
, когда несколько элементов сортируются по отдельности, он видит один объект и возвращает только этот объект. Да, это коллекция, но командлет предназначен для использования механизма развертывания конвейера PowerShell, а не для развертывания самой коллекции.

Это может немного сбивать с толку, и, чтобы прояснить ситуацию, многие командлеты работают нормально, когда напрямую вызывается аргумент конвейера (часто, но также используются другие имена). Но как правило, если -InputObject (или что-то еще ValueFromPipeline параметр имеет имя) принимает коллекцию, я вызываю ее с помощью конвейера.


Если вам нужен более быстрый метод сортировки, вы можете использовать Array.Sort вместо этого статический метод:

      [Array]::Sort($array)

Array.ForEach также работает быстрее, чем ForEach-Object или foreach заявление, и Array.Where работает лучше, чем Where-Object. Компромисс с использованием Arrayстатические методы - у вас есть щель в цепочке конвейера, поскольку вы не можете передавать данные в метод .NET.

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