Удаление PowerShell ForEach-Object - PullRequest
2 голосов
/ 19 февраля 2020

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

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

$Array = "A", "B", "C"
$ArrayOfArrays = (1, 2, 3), (4, 5, 6), (7, 8, 9)

Мы знаем, что трубопровод разбивает коллекцию на элементы, например:

$Array | & {process {Write-Host $_}}
$ArrayOfArrays | & {process {Write-Host $_}}

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

$ArrayOfArrays | % -process {Write-Host $_} (1)

и это:

$ArrayOfArrays | % -process {% -process {Write-Host $_}} (2)

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

Я мог бы решить это только с помощью внутреннего трубопровода:

$ArrayOfArrays | % -process {$_ | % -process {Write-Host $_}} (3)

, однако с помощью этого подхода я могу исключить ForEach-Object, конечно:

$ArrayOfArrays | & {process {$_ | & {process {Write-Host $_}}}} (4)

Итак, мои 2 вопроса:

1 ,

Как получить доступ к элементу коллекции, которая находится в коллекции в конвейере, отличной от try (3) и (4), или это единственный способ сделать это?

2 ,

Если единственный способ сделать то, что задает вопрос 1, - это попытки (3) и (4), тогда что действительный вариант использования ForEach-Object, где его нельзя устранить? Я имею в виду, что это может быть логичным случаем, но также производительность против блока скрипта Тот факт, что он лучше, чем блок сценариев с одной парой скобок, для меня просто не достаточно ...

.
РЕДАКТИРОВАТЬ после ответа Мануэля Батшинга:

Поскольку ForEach-Object возвращает элементы коллекции после ее обработки, мы можем сделать это (я позволил go Write-Host, возможно, это была нехорошая произвольная операция, поэтому пусть это будет GetType ):

$ArrayOfArrays | % -process {$_} | & {process {$_.GetType()}}

Но мы также знаем, что если что-то возвращает новый объект в конвейере, это вызовет срыв, если он будет передан дальше и если это коллекция. Таким образом, чтобы выполнить разбивку, мы можем снова исключить ForEach-Object и сделать это:

$ArrayOfArrays | & {process {$_}} | & {process {$_.GetType()}}

И эту фиктивную операцию можно синтаксически сократить, если я определю фильтр следующим образом:

Filter §
{
    param (
            [Parameter (Mandatory = $True, ValueFromPipeline = $True)]
            [Object]
            $ToBeTriggeredForBreakDown
    ) # end param

    $ToBeTriggeredForBreakDown

}

и используйте это так:

$Array | § | & {process {$_.GetType()}}
$ArrayOfArrays | § | & {process {$_.GetType()}}

$ArrayOfArraysOfArrays = ((1, 2), (3, 4)), ((5, 6), (7, 8))
$ArrayOfArraysOfArrays | § | & {process {$_.GetType()}}
$ArrayOfArraysOfArrays | § | § | & {process {$_.GetType()}}

Так что мне все еще трудно увидеть, когда я буду использовать ForEach-Object, мне кажется, это совершенно бесполезно - за исключением причин, которые я ищу в моем вопросов.

.
РЕДАКТИРОВАТЬ после исследования:

Некоторые коллекции предоставляют свои собственные методы, например, поскольку v4 arrays имеет метод ForEach, поэтому, кроме (3 ) и (4), это можно сделать (опять же фиктивная операция, но с меньшим количеством кода):

$ArrayOfArrays.ForEach{$_} | & {process {$_.GetType()}}

, поэтому это частично охватывает вопрос 1.

Ответы [ 2 ]

1 голос
/ 19 февраля 2020

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

Вы увидите это поведение со всеми следующими подходами:

$ArrayOfArrays | % -process { $_ }
$ArrayOfArrays | & { process { $_ } }
foreach ($arr in $ArrayOfArrays) { $arr }

Что разрушает развертывание в вашем примере, так это командлет Write-Host. Поскольку этот командлет пишет не в выходной поток, а в вашу консоль, он преобразует входной объект в [string]. Вот почему вы видите строковое представление внутренних массивов на вашей консоли.

Замените Write-Host на Write-Output, и внутренние массивы будут правильно развернуты:

 PS> $ArrayOfArrays | % -process { Write-Output $_ }
1
2
3
4
5
6
7
8
9

РЕДАКТИРОВАТЬ:

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

$ArrayOfArrays = (1, 2, 3), (4, 5, 6), (7, 8, 9)
$foo = $null
$foo = $ArrayOfArrays | % { Write-Output $_ }

Установите точку останова на строку $foo = $null, добавьте переменные $foo и $_ в список наблюдения, нажмите F5, чтобы запустить отладчик, и просмотрите переменные изменяются, пока вы нажимаете клавишу F11, чтобы перейти к отдельным шагам обработки.

  • $_ покажет внутренний массив, являющийся текущим элементом в конвейере.
  • $foo получит только развернутые элементы после окончания выполнения конвейера
0 голосов
/ 04 марта 2020

В PowerShell 7 Foreach-Object имеет переключатель -Parallel для параллельного выполнения. Это не обязательно быстро для всех типов обработки. Вам придется поэкспериментировать с этим.

Foreach-Object Параметр -Process принимает массив блоков скриптов. Таким образом, вы можете технически выполнять различные сценарии обработки для каждого переданного по конвейеру объекта.

1,2,3 | Foreach-Object -begin {"First loop iteration"} -process {$_ + 1},{$_ + 2},{$_ + 3} -End {"Last loop iteration"}
First loop iteration
2
3
4
3
4
5
4
5
6
Last loop iteration

# Example of already having script blocks defined
$sb1,$sb2,$sb3 = { $_ + 1 },{$_ + 2},{$_ + 3}
1,2,3 | Foreach-Object -begin {"Starting the loop"} -process $sb1,$sb2,$sb3 -end {"the loop finished"}
Starting the loop
2
3
4
3
4
5
4
5
6
the loop finished

Foreach-Object также поддерживает операторы операций. Технически вам не нужно ничего делать, но 1,2,3 | Foreach ToString возможно более читабельно, чем 1,2,3 | & { process { $_.ToString() }}.

Foreach-Object также имеет параметр -InputObject, где вы можете обработать весь объект как один элемент. Это способ предотвратить развертывание массива, которое вы видите в конвейере. Вы можете сделать это с помощью вашего метода, но перед отправкой по конвейеру вы должны сделать заворачивание массива как ,@(1,2,3).

# Single pipeline object
$count = 1
ForEach-Object -InputObject 1,2,3 -Process {"Iteration Number: $count"; $_; $count++}
Iteration Number: 1
1
2
3

# array unwrapping down pipeline

$count = 1
1,2,3 | ForEach-Object -Process {"Iteration Number: $count"; $_; $count++}
Iteration Number: 1
1
Iteration Number: 2
2
Iteration Number: 3
3

Поскольку Foreach-Object является командлетом, вы получаете доступ к Общим параметрам . Таким образом, вы можете использовать -PipelineVariable, например, чтобы использовать вывод этой команды для команды в более глубоком конвейере.

# Using OutVariable
1,2,3 | Foreach-Object {$_ + 100} -OutVariable numbers |
    Foreach-Object -process { "Current Number: $_"; "Numbers Processed So Far: $numbers" }
Current Number: 101
Numbers Processed So Far: 101
Current Number: 102
Numbers Processed So Far: 101 102
Current Number: 103
Numbers Processed So Far: 101 102 103

# Using PipeLineVariable
1,2,3 | Foreach-Object {$_ + 100} -PipeLineVariable first |
    Foreach-Object {$_ * 2} -PipelineVariablesecond |
        Foreach-Object {"First number is $first"; "second number is $second"; "final calculation is $($_*3)" }
First number is 101
second number is 202
final calculation is 606
First number is 102
second number is 204
final calculation is 612
First number is 103
second number is 206
final calculation is 618

Мои тестовые примеры показывают, что метод data | & { process {}} быстрее, чем data | foreach-object -process {}. Таким образом, это выглядит компромиссом относительно того, что вы хотите получить от него.

Measure-Command {1..100000 | & { process {$_}}}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 107
Ticks             : 1074665
TotalDays         : 1.24382523148148E-06
TotalHours        : 2.98518055555556E-05
TotalMinutes      : 0.00179110833333333
TotalSeconds      : 0.1074665
TotalMilliseconds : 107.4665


Measure-Command {1..100000 | Foreach-Object {$_}}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 768
Ticks             : 7686545
TotalDays         : 8.89646412037037E-06
TotalHours        : 0.000213515138888889
TotalMinutes      : 0.0128109083333333
TotalSeconds      : 0.7686545
TotalMilliseconds : 768.6545

При запуске Foreach-Object весь код запускается в области действия текущего вызывающего, включая содержимое блока сценария. & запускает код в дочерней области, и все, что изменилось в этой области, может не отражаться при возврате в родительскую область (вызывающая область). Вам нужно будет использовать . для вызова в текущей области.

# Notice $a outputs nothing outside of the loop
PS > 1,2,3 | & { begin {$a = 100} process { $_ } end {$a}}
1
2
3
100
PS > $a

PS >

# Notice with . $a is updated
PS > 1,2,3 | . { begin {$a = 100} process { $_ } end {$a}}
1
2
3
100
PS > $a
100
PS >

# foreach updates current scope (used a different variable, because  
# $a was already added by the previous command)
PS > 1,2,3 | foreach-object -begin {$b = 333} -process {$_} -end {$b}
1
2
3
333
PS > $b
333
PS >
...