Вопрос

В прошлый раз я запутался кстати PowerShell нетерпеливо разворачивая коллекции, Кит резюмировал свою эвристику следующим образом:

Помещение результатов (массива) в группирующее выражение (или подвыражение, например$()) снова делает его пригодным для развертывания.

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

$lhs = "{0} {1}"

filter Identity { $_ }
filter Square { ($_, $_) }
filter Wrap { (,$_) }
filter SquareAndWrap { (,($_, $_)) }

$rhs = "a" | Square        
# 1. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

$rhs = "a" | Square | Wrap       
# 2. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

$rhs = "a" | SquareAndWrap       
# 3. all succeed
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

$rhs = "a", "b" | SquareAndWrap       
# 4. all succeed by coercing the inner array to the string "System.Object[]"
$lhs -f $rhs
$lhs -f ($rhs)
$lhs -f $($rhs)
$lhs -f @($rhs)

"a" | Square | % {
    # 5. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a", "b" | Square | % {
    # 6. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a" | Square | Wrap | % {
    # 7. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a", "b" | Square | Wrap | % {
    # 8. all fail
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a" | SquareAndWrap | % {
    # 9. only @() and $() succeed
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

"a", "b" | SquareAndWrap | % {
    # 10. only $() succeeds
    $lhs -f $_
    $lhs -f ($_)
    $lhs -f @($_)
    $lhs -f $($_)            
}

Применяя те же шаблоны, которые мы видели в предыдущем вопросе, становится ясно, почему случаи, подобные # 1 и # 5, ведут себя по-разному:оператор конвейера сигнализирует движку сценариев развернуть другой уровень, в то время как оператор присваивания этого не делает.Другими словами, все, что находится между двумя |'s, обрабатывается как сгруппированное выражение, точно так же, как если бы оно было внутри ()'s .

# all of these output 2
("a" | Square).count                       # explicitly grouped
("a" | Square | measure).count             # grouped by pipes
("a" | Square | Identity).count            # pipe + ()
("a" | Square | Identity | measure).count  # pipe + pipe

По той же причине случай № 7 не является улучшением по сравнению с № 5.Любая попытка добавить дополнительный Обернуть будет немедленно разрушен дополнительной трубой.То же самое №8 против №6.Немного расстраивает, но на данный момент я полностью согласен.

Оставшиеся вопросы:

  • Почему случай № 3 не постигла та же участь, что и случай №4? $РИТ должен содержать вложенный массив (,("а", "а")) но его внешний уровень разворачивается ... где-то...
  • Что происходит с различными операторами группировки в # 9-10?Почему они ведут себя так хаотично, и зачем они вообще нужны?
  • Почему сбои в случае # 10 не ухудшаются изящно, как в случае # 4?
Это было полезно?

Решение

Ну, в этом наверняка есть какая-то ошибка.(Я только что написал страница в PoshCode Wiki на самом деле, об этом вчера, и есть ошибка при подключении).

Сначала ответы, потом еще вопросы:

Чтобы получить согласованное поведение от массивов с помощью -f форматирование строк, вам нужно будет убедиться на 100%, что они являются PSObjects.Мое предложение состоит в том, чтобы сделать это при их назначении.Это так предполагаемый должно выполняться автоматически PowerShell, но по какой-то причине не выполняется до тех пор, пока вы не получите доступ к свойству или чему-то еще (как задокументировано в этом вики - страница и ошибка).Например.( <##> это моя подсказка):

<##> $a = 1,2,3
<##> "$a"
1 2 3

<##> $OFS = "-"  # Set the Output field separator
<##> "$a"
1-2-3

<##> "{0}" -f $a
1 

<##> $a.Length
3 

<##> "{0}" -f $a
1-2-3

# You can enforce correct behavior by casting:
<##> [PSObject]$b = 1,2,3
<##> "{0}" -f $a
1-2-3

Обратите внимание, что когда вы это сделаете, они НЕ БУДУТ разворачиваться при передаче в -f, а скорее будут выводиться корректно - так, как это было бы, если бы вы поместили переменную непосредственно в строку.

Почему случай № 3 не постигла та же участь, что и случай №4?$rhs должен содержать вложенный массив (,("a", "a")), но его внешний уровень разворачивается ... где-то...

Простая версия ответа заключается в том, что ОБА # 3 и # 4 разворачиваются.Разница в том, что в 4 внутреннее содержимое представляет собой массив (даже после того, как внешний массив развернут):

$rhs = "a" | SquareAndWrap
$rhs[0].GetType()  # String

$rhs = "a","b" | SquareAndWrap
$rhs[0].GetType()  # Object[]

Что происходит с различными операторами группировки в # 9-10?Почему они ведут себя так хаотично, и зачем они вообще нужны?

Как я уже говорил ранее, массив должен учитываться как отдельный параметр формата и выводиться с использованием правил форматирования строк PowerShell (ie:разделенный $OFS) точно так же, как это было бы, если бы вы поместили $_ непосредственно в строку ...поэтому, когда PowerShell ведет себя правильно, $lhs -f $rhs завершится неудачей, если $ lhs содержит двух заполнителей.

Конечно, мы уже заметили, что в этом есть ошибка.

Однако я не вижу ничего странного:@() и $() работают одинаково для 9 и 10, насколько я могу видеть (основное различие, по сути, вызвано тем, как ForEach разворачивает массив верхнего уровня:

> $rhs = "a", "b" | SquareAndWrap
> $rhs | % { $lhs -f @($_); " hi " }
a a
 hi 
b b
 hi 

> $rhs | % { $lhs -f $($_); " hi " }
a a
 hi 
b b
 hi     

# Is the same as:
> [String]::Format( "{0} {1}", $rhs[0] ); " hi "
a a
 hi 

> [String]::Format( "{0} {1}", $rhs[1] ); " hi "
b b
 hi     

Итак, вы видите, что ошибка заключается в том, что @() или $() приведут к тому, что массив будет передан как [object[]] в вызов string format, а не как PSObject, который имеет специальные строковые значения.

Почему сбои в случае # 10 не ухудшаются изящно, как в случае # 4?

По сути, это та же ошибка, но в другом проявлении.Массивы никогда не должны отображаться как "System.Object[]" в PowerShell, если вы вручную не вызовете их собственный .ToString() метод, или передайте их в String.Форматируйте() напрямую ...причина, по которой они это делают в # 4, заключается в том, что ошибка: PowerShell не удалось расширить их как PSOjbects перед передачей их в строку.Вызов Format .

Вы можете увидеть это, если получите доступ к свойству массива перед его передачей или приведете его к PSObject, как в моих оригинальных примерах.Технически, ошибки в # 10 являются правильным выводом:вы передаете только ОДНУ вещь (массив) в string.format , когда ожидалось две вещи.Если бы вы изменили свой $ lhs просто на "{0}", вы бы увидели массив, отформатированный с помощью $ OFS


Однако мне интересно, какое поведение вы Нравится и что, по-вашему, это правильный, рассматривая мой первый пример?Я думаю, что вывод, разделенный $ OFS, правильный, в отличие от развертывания массива, как это происходит, если вы @(обернете) его или приведете к [object[]] (Кстати, обратите внимание, что произойдет, если вы приведете его к [int[]], это другой глючное поведение):

> "{0}" -f [object[]]$a
1

> "{0}, {1}" -f [object[]]$a  # just to be clear...
1,2

>  "{0}, {1}" -f [object[]]$a, "two"  # to demonstrate inconsistency
System.Object[],two

> "{0}" -f [int[]]$a
System.Int32[]

Я уверен, что многие сценарии были написаны неосознанно с использованием этой ошибки, но мне все еще кажется довольно ясным, что развертывание, которое происходит в просто для ясности пример НЕ является правильным поведением, но происходит потому, что при вызове (внутри ядра PowerShell) .Net String.Format( "{0}", a ) ... $a является object[] какая именно строка.Ожидаемый формат в качестве параметра Params ...

Я думаю, что это должно быть исправлено.Если есть какое-либо желание сохранить "функциональность" развертывания массива, это должно быть сделано с помощью оператора @ splatting, верно?

Другие советы

Ни Square, ни Wrap не будут делать то, что вы пытаетесь в # 5 и 7.Независимо от того, помещаете ли вы массив в группирующее выражение (), как вы делаете в Square, или используете оператор запятой, как вы делаете в Wrap, при использовании этих функций в конвейере их выходные данные развертываются по мере их передачи на следующий этап конвейера по очереди.Аналогично в 6 и 8, не имеет значения, что вы передаете по каналу несколько объектов, как Square, так и Wrap будут передавать их по одному на ваш этап foreach.

Случаи 9 и 10, похоже, указывают на ошибку в PowerShell.Возьмите этот измененный фрагмент и попробуйте его:

"a" | SquareAndWrap | % {    
    # 9. only @() and $() succeed  
    $_.GetType().FullName
    $_.Length
    $lhs -f [object[]]$_
    $lhs -f [object[]]($_)    
    $lhs -f @($_)   
    $lhs -f $($_)            
}

Это работает.Это также показывает, что foreach уже получает object[] размером 2, так что $_ должно работать без приведения к [object[]] или переноса в подвыражение или подвыражение массива.Мы видели некоторые ошибки версии V2, связанные с неправильным развертыванием psobjects, и это, похоже, еще один пример этого.Если вы развернете psobject вручную, это сработает, например $_.psobject.baseobject.

Я "думаю", что то, ради чего вы снимаете в Wrap, - это:

function Wrap2 { Begin {$coll = @();} Process {$coll += $_} End {,$coll} }

Это позволит аккумулировать все входные данные конвейера, а затем вывести их в виде единого массива.Это будет работать для случая 8, но вам все равно нужно привести к [object[]] при первых двух использованиях -f оператор.

Кстати, скобки как в Square, так и в Wrap, а также внешние скобки в SquareAndWrap не нужны.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top