Подтвердить что ты не робот

Трубные полные массивы-объекты вместо элементов массива по одному за раз?

Как вы отправляете вывод из одного CmdLet в следующий в конвейере как полный массив-объект вместо отдельных элементов в массиве по одному за раз?

Проблема - Общее описание
Как видно из справки about_pipelines (help pipeline), powershell отправляет объекты по одному вниз по конвейеру¹. Таким образом, Get-Process -Name notepad | Stop-Process отправляет один процесс во время работы по трубе.

Предположим, что у нас есть сторонний CmdLet (Do-SomeStuff), который нельзя каким-либо образом изменить или изменить. Do-SomeStuff выполняет разные действия, если ему передан массив строк или передан один строковый объект.

Do-SomeStuff является лишь примером, его можно заменить на ForEach-Object, Select-Object, Write-Host (или любой другой CmdLet, принимающий вход конвейера)

Do-SomeStuff будет в этом примере обрабатывать отдельные элементы в массиве в то время.

$theArray = @("A", "B", "C")
$theArray | Do-SomeStuff

Если мы хотим отправить полный массив как один объект в Do-SomeStuff, вы можете попробовать что-то вроде этого

@($theArray) | Do-SomeStuff

Но это не приводит к ожидаемому результату, поскольку PowerShell "игнорирует" новый массив с одним элементом.

Итак, как вы "принудительно" $theArray передаваться по каналу как единый массив-объект, а не элементы контента в то время?


Проблема - практический пример
Как показано ниже, вывод Write-Host отличается, если передан массив или если он передал отдельные элементы в массиве по одному в тот момент.

PS C:\> $theArray = @("A", "B", "C")
PS C:\> Write-Host $theArray
A B C
PS C:\> $theArray | foreach{Write-Host $_}
A
B
C
PS C:\> @($theArray) | foreach{Write-Host $_}
A
B
C

Как сделать, чтобы $theArray | foreach{Write-Host $_} выдал тот же результат, что и Write-Host $theArray?




СНОСКИ

  • Обработка трубопроводов в Powershell

Обычный массив строк

PS C:\> @("A", "B", "C").GetType().FullName
System.Object[]


Обычный массив строк, переданных в Foreach-Object

PS C:\> @("A", "B", "C") | foreach{$_.GetType().FullName}
System.String
System.String
System.String

Каждая строка в массиве обрабатывается по одному в тот момент, когда объект-объект CmdLet ForEach-Object.


Массив массивов, где "внутренние" массивы представляют собой массивы строк.

PS C:\> @(@("A", "B", "C"), @("D", "E", "F"), @("G", "H", "I")) | foreach{$_.GetType().FullName}
System.Object[]
System.Object[]
System.Object[]

Каждый массив в массиве обрабатывается по одному в тот момент, когда объект CmdLet ForEach-Object, и содержимое каждого подматрица от входа обрабатывается как один объект, даже если это массив.

4b9b3361

Ответ 1

Короткий ответ: использовать оператор унарного массива ,:

,$theArray | foreach{Write-Host $_}

Длинный ответ: есть одна вещь, которую вы должны понимать о операторе @(): он всегда интерпретирует свой контент как инструкцию, даже если контент - это просто выражение. Рассмотрим этот код:

$a='A','B','C'
[email protected]($a;)
[email protected]($b;)

Здесь я добавляю явный конец выражения ;, хотя PowerShell позволяет его опустить. $a - это массив из трех элементов. Каков результат инструкции $a;? $a - это коллекция, поэтому сбор должен быть перечислим, и каждый отдельный элемент должен быть передан по конвейеру. Таким образом, результат оператора $a; - это три элемента, записанных в конвейер. @($a;) увидеть три элемента, но не исходный массив, и создать из них массив, поэтому $b - это массив из трех элементов. Точно так же $c - массив из трех элементов. Поэтому, когда вы пишете @($collection), вы создаете массив, который копирует элементы $collection, а не массив одного элемента.

Ответ 2

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

Это полезно, если вам нужно оценить состояние нескольких элементов в массиве.

Используя следующую функцию

function funTest {
    param (
        [parameter(Position=1, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [alias ("Target")]
        [array]$Targets 
        ) # end param
    begin {}
    process {
        $RandomSeed = $( 1..1000 | Get-Random )
        foreach ($Target in $Targets) {
            Write-Host "$RandomSeed - $Target"
            } # next target
        } # end process
    end {}
    } # end function 

Рассмотрим следующие примеры:

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

PS C:\> @(1,2,3,4,5) | funTest
153 - 1
87 - 2
96 - 3
96 - 4
986 - 5

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

PS C:\> , 1,2,3,4,5 | funTest
1000 - 1
84 - 2
813 - 3
156 - 4
928 - 5

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

PS C:\> , @( 1,2,3,4,5) | funTest
883 - 1
883 - 2
883 - 3
883 - 4
883 - 5

Ответ 3

Там есть решение для старой школы, если вы не возражаете, что ваш процесс является функцией.

Пример. Вы хотите, чтобы массив скопировался в буфер обмена таким образом, чтобы вы могли его снова создать на другой системе без каких-либо подключений PSRemoting. Поэтому вам нужен массив, содержащий "A", "B" и "C" для преобразования в строку: @( "А", "В", "С" ) ... вместо литерального массива.

Итак, вы строите это (что не является оптимальным по другим причинам, но оставайтесь на теме):

# Serialize-List

param 
(
    [Parameter(Mandatory, ValueFromPipeline)]
    [string[]]$list
)
    $output = "@(";

    foreach ($element in $list)
    {
        $output += "`"$element`","
    }

    $output = $output.Substring(0, $output.Length - 1)
    $output += ")"
    $output

и он работает, когда вы указываете массив как параметр напрямую:

Сериализованный список $list

@( "А", "В", "С" )

... но не так много, когда вы передаете его по конвейеру:

$list | Сериализация-List

@( "С" )

Но реорганизуйте свою функцию с помощью начальных, технологических и конечных блоков:

# Serialize-List

param 
(
    [Parameter(Mandatory, ValueFromPipeline)]
    [string[]]$list
)

begin
{
    $output = "@(";
}

process
{
    foreach ($element in $list)
    {
        $output += "`"$element`","
    }
}

end
{
    $output = $output.Substring(0, $output.Length - 1)
    $output += ")"
    $output
}

... и вы получите желаемый результат в обоих направлениях.

Сериализованный список $list

@( "А", "В", "С" )

$list | Сериализация-List

@( "А", "В", "С" )