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

Полнолуние эквивалентно LINQ Any()?

Я хотел бы найти все каталоги на верхнем уровне из местоположения script, которые хранятся в subversion.

В С# это будет что-то вроде этого

Directory.GetDirectories(".")
  .Where(d=>Directories.GetDirectories(d)
     .Any(x => x == "_svn" || ".svn"));

Мне сложно найти эквивалент "Any()" в powershell, и я не хочу проходить через неловкость вызова метода расширения.

Пока у меня есть это:

 gci | ? {$_.PsIsContainer} | gci -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"

Это находит мне сами svn-каталоги, но не их родительские каталоги - это то, что я хочу. Бонусные баллы, если вы можете сказать мне, почему добавление

 | select-object {$_.Directory}

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

4b9b3361

Ответ 1

Чтобы ответить на ближайший вопрос с помощью решения PSv3 +:

(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName

-Directory ограничивает совпадения с каталогами, -Recurse -Depth 2 рекурсирует до 3 уровней (дети, внуки, правнуки), Include позволяет указать несколько фильтров (filename-component), а .Parent.FullName возвращает полный путь родительских директорий. из сопоставленных dirs., используя перечисление членов (неявно доступ к свойствам элемента коллекции).

Что касается вопроса о бонусе: select-object {$_.Directory} не работает,  потому что [System.IO.DirectoryInfo] экземпляры, возвращенные Get-ChildItem, не имеют свойства .Directory, а только свойство .Parent; Select-Object -ExpandProperty Parent должен был использоваться.
В дополнение к возврату интересующей стоимости свойства -ExpandProperty также обеспечивает существование свойства. Напротив, select-object {$_.Directory} возвращает пользовательский объект с свойством, буквально называемым $_.Directory, значение которого $null, учитывая, что входные объекты не имеют свойства .Directory; эти значения $null печатаются как пустые строки в консоли.


Что касается более общего вопроса о PowerShell, эквивалентном LINQ .Any() method, который указывает [с булевым результатом], имеет ли данный перечислимый (набор) какие-либо элементы/любые элементы, удовлетворяющие заданному условию:

Намеренно PowerShell не предлагает такого эквивалента, но поведение может быть эмулировано:


Использование оператора коллекции PSv4 + .Where():

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

(...).Where({ $_ ... }, 'First').Count -gt 0

... представляет интересующую команду и $_ ... условие интереса, применяемое к каждому входному объекту, где переменная PowerShell automatic $_ относится к входному объекту под рукой; Аргумент 'First' гарантирует, что метод вернется после первого совпадения.

Например:

> @(1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0 # see if there at least 1 value > 1
True

Использование конвейера: проверка того, была ли команда произведена, по крайней мере, с одним объектом вывода [с условием]:

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

  • Если вы не возражаете, что все объекты перечислены - хотя вам все равно, если есть хотя бы один - используйте Paolo Tedesco полезное расширение в полезный ответ JaredPar.
    Нижняя сторона этого подхода заключается в том, что вам всегда нужно ждать (потенциально долговременной) команды для завершения создания всех выходных объектов, хотя логически - определение того, есть ли какие-либо выходные объекты, можно сделать, как только первый объект получен.

  • Если вы хотите выйти из конвейера, как только один объект [соответствующий] был встречен, у вас есть две возможности:

    • [Ad-hoc: легко понять, но громоздко реализовать]
      Включите конвейер в фиктивном контуре и используйте break для выхода из конвейера, и этот цикл (... представляет команду, выход которой для тестирования, и $_ ... соответствует условию):
      [bool] $haveAny = do { ... | % { $true; break } } while ($false) # unconditional
      [bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)

    • [Использовать автономную функцию утилиты PSv3 +, которая нетривиальна для реализации]
      См. Реализацию функции Test-Any ниже.
      Он может быть добавлен в скрипты или ваш $PROFILE.


PSv3 +: оптимизированная служебная функция Test-Any

Функция нетривиальна, поскольку , как и Windows PowerShell v5.1, PowerShell Core v6, нет прямого способа выйти из конвейера преждевременно, поэтому обходной путь, основанный на анализе .NET, и частный тип в настоящее время необходим.
Если вы согласны с тем, что должна быть такая функция, примите участие в беседе на GitHub.

#requires -version 3
Function Test-Any {

    [CmdletBinding()]
    param(
        [ScriptBlock] $Filter,
        [Parameter(ValueFromPipeline = $true)] $InputObject
    )

    process {
      if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
          $true # Signal that at least 1 [matching] object was found
          # Now that we have our result, stop the upstream commands in the
          # pipeline so that they don't create more, no-longer-needed input.
          (Add-Type -Passthru -TypeDefinition '
            using System.Management.Automation;
            namespace net.same2u.PowerShell {
              public static class CustomPipelineStopper {
                public static void Stop(Cmdlet cmdlet) {
                  throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
                }
              }
            }')::Stop($PSCmdlet)
      }
    }
    end { $false }
}
  • if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) по умолчанию используется значение true, если $Filter не указано, и в противном случае оценивает фильтр (блок script) с объектом под рукой.

    • Использование ForEach-Object для оценки блока script гарантирует, что $_ связывается с текущим объектом конвейера во всех сценариях, как показано в PetSerAl " > полезный ответ здесь.
  • Оператор (Add-Type ... использует тип ad-hoc, созданный с кодом С#, который использует отражение, чтобы генерировать одно и то же исключение, которое Select-Object -First (PS v3 +) использует внутри, чтобы остановить конвейер, а именно [System.Management.Automation.StopUpstreamCommandsException], который с PS v5 по-прежнему является частным типом. Справочная информация здесь: http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
    Большое спасибо PetSerAl за внесение этого кода в комментарии.

<сильные > Примеры:

  • > @() | Test-Any false

  • > Get-EventLog Application | Test-Any # should return *right away* true

  • > 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true


Фоновая информация

полезный ответ JaredPar и полезное расширение Paolo Tedesco не соответствуют одному: они не выходят конвейер, как только совпадение найдено, что может быть важной оптимизацией.

ОБНОВЛЕНИЕ: К сожалению, даже с PowerShell v5 нет прямого способа выйти из конвейера преждевременно. Если вы согласны с тем, что должна быть такая функция, примите участие в беседе на GitHub.

A наивная оптимизация ответ JaredPar фактически сокращает код:

# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
  • Блок process вводится только в том случае, если в конвейере есть не менее 1 элемента.
    • Небольшое предостережение: по дизайну, если нет конвейера вообще, блок process все еще введен, а $_ установлен на $null, поэтому вызов Test-Any вне конвейера бесполезно возвращает $true. Чтобы различать между $null | Test-Any и Test-Any, проверьте $MyInvocation.ExpectingInput, который $true только в конвейере: Спасибо, PetSerAl
      function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
  • $true, записанный в выходной поток, сигнализирует о том, что найдено не менее 1 объекта.
  • break затем завершает работу конвейера и, таким образом, предотвращает излишнюю обработку дополнительных объектов. ОДНАКО, И ТАКЖЕ ВЫХОДИТ ЛЮБОЙ ЗАКРЫВАЮЩИЙ КОНТУР - break НЕ предназначен для выхода из PIPELINE Спасибо, PetSerAl SUP > .

    • Если бы была команда выхода из конвейера, это куда-то.
    • Обратите внимание, что return просто перейдет к следующему объекту ввода.
  • Поскольку блок process безоговорочно выполняет break, блок end достигается, только если блок process никогда не был введен, что подразумевает пустой конвейер, поэтому $false записывается в выходной поток, чтобы сигнализировать об этом.


В отсутствие надлежащей поддержки для выхода из конвейера, за исключением реализации нетривиальной функции Test-Any выше, существует обходной путь:

  • Заключить любой конвейер, в котором вы используете указанную выше функцию Test-Any в манекене только один раз - очевидно, это неудобно и легко забыть:

    # The dummy loop ensures that the `break` inside Test-Any()
    # only breaks out of *it*.
    do { ... | Test-Any } while ($false)
    

Ответ 2

К сожалению, в PowerShell нет эквивалента. Я написал сообщение в блоге об этом с предложением для универсальной функции Test-Any/filter.

function Test-Any() {
    begin {
        $any = $false
    }
    process {
        $any = $true
    }
    end {
        $any
    }
}

Сообщение в блоге: http://blogs.msdn.com/jaredpar/archive/2008/06/12/is-there-anything-in-that-pipeline.aspx

Ответ 3

Вариант ответа @JaredPar, чтобы включить тест в фильтр Test-Any:

function Test-Any {
    [CmdletBinding()]
    param($EvaluateCondition,
        [Parameter(ValueFromPipeline = $true)] $ObjectToTest)
    begin {
        $any = $false
    }
    process {
        if (-not $any -and (& $EvaluateCondition $ObjectToTest)) {
            $any = $true
        }
    }
    end {
        $any
    }
}

Теперь я могу написать "любые" тесты, например

> 1..4 | Test-Any { $_ -gt 3 }
True

> 1..4 | Test-Any { $_ -gt 5 }
False

Ответ 4

Теперь мой подход:

gci -r -force `
    | ? { $_.PSIsContainer -and $_.Name -match "^[._]svn$" } `
    | select Parent -Unique

Причина, по которой

select-object {$_.Directory}

не возвращает ничего полезного, так как такого свойства нет в объекте DirectoryInfo. По крайней мере, не в моей PowerShell.


Чтобы разработать собственный ответ: PowerShell может обрабатывать большинство непустых коллекций как $true, поэтому вы можете просто сделать:

$svnDirs = gci `
    | ? {$_.PsIsContainer} `
    | ? {
        gci $_.Name -Force `
            | ? {$_.PSIsContainer -and ($_.Name -eq "_svn" -or $_.Name -eq ".svn") }
        }

Ответ 5

Вы можете использовать оригинальный LINQ any:

[Linq.Enumerable]::Any($list)

Ответ 6

Закончено делать это со счетом:

$directoryContainsSvn = {
    (gci $_.Name -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"} | Measure-Object).Count -eq 1
}
$svnDirs = gci | ? {$_.PsIsContainer} | ? $directoryContainsSvn

Ответ 7

Это на самом деле довольно просто - просто выберите сначала $true (отформатирован для ясности):

[bool] ($source `
        | foreach { [bool] (<predicate>) } `
        | where { $_ } `
        | select -first 1)

Ответ 8

Вы можете немного затянуть это:

gci -fo | ?{$_.PSIsContainer -and `
            (gci $_ -r -fo | ?{$_.PSIsContainer -and $_ -match '[_.]svn$'})}

Примечание - передача $__. Имя для вложенного gci не требуется. Передача его $_ является достаточной.

Ответ 9

Я рекомендую следующее решение:

<#
.SYNOPSIS 
   Tests if any object in an array matches the expression

.EXAMPLE
    @( "red", "blue" ) | Where-Any { $_ -eq "blue" } | Write-Host
#>
function Where-Any 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $True)]
        $Condition,

        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        $Item
    )

    begin {
        [bool]$isMatch = $False
    }

    process {
      if (& $Condition $Item) {
          [bool]$isMatch = $true
      }
    }

    end {
        Write-Output $isMatch
    }
}

# optional alias
New-Alias any Where-Any

Ответ 10

Это лучший метод, который я нашел до сих пор (не перебирает все элементы, если уже нашел true и не нарушает конвейер):

Из https://chrisseroka.wordpress.com/2011/04/06/linq-any-equivalent-in-powershell

Возможно использовать встроенную переменную $input, содержащую весь конвейер в объеме функции. Таким образом, желаемый код может выглядеть следующим образом:

function Test-Any( [scriptBlock] $scriptBlock = {$true}, [scriptBlock] $debugOut = $null )
{
    if($debugOut) 
    { 
        Write-Host( "{0} | % {{{1}}}" -f $input, $scriptBlock ) 
    }

    $_ret = $false; 
    $_input = ( $input -as [Collections.IEnumerator] )

    if( $_input )
    {
        while( $_input.MoveNext() )
        {
            $_ = $_input.Current;

            Write-Host $_

            if( $debugOut ) 
            { 
                Write-Host( "Tested: [{0}]" -f ( &$debugOut ) ) 
            }

            if( &$scriptBlock )
            {
                if( $debugOut ) 
                {
                    Write-Host( "Matched: [{0}]" -f ( &$debugOut ) )
                }

                $_ret = $true
                break
            }
        }
    }

    $_ret
}

Ответ 11

Я думаю, что лучший ответ здесь - это функция, предложенная @JaredPar, но если вам нравится один-лайнер, как я, я бы предложил предложить следующий Any однострочный:

# Any item is greater than 5
$result = $arr | %{ $match = $false }{ $match = $match -or $_ -gt 5 }{ $match }

%{ $match = $false }{ $match = $match -or YOUR_CONDITION }{ $match } проверяет, что по крайней мере одно условие соответствия элемента.

Одно примечание - обычно любая операция оценивает массив, пока не найдет условие соответствия первого элемента. Но этот код оценивает все элементы.

Чтобы просто упомянуть, вы можете легко настроить его, чтобы стать All одним слоем:

# All items are greater than zero
$result = $arr | %{ $match = $false }{ $match = $match -and $_ -gt 0 }{ $match }

%{ $match = $false }{ $match = $match -and YOUR_CONDITION }{ $match } проверяет, что все элементы соответствуют условию.

Обратите внимание, что для проверки Any вам нужно -or и проверить все, что вам нужно -and.

Ответ 12

Используя функцию @Paolo Tedesco, теперь я могу проверить, есть ли какие-либо значения в хеш-таблице: $true или $false, без прохождения через каждое значение:

[HashTable]$myHash = @{a=$true;b=$false;c=$true}
$myHash.get_Values()| Test-Any { $_ -eq $true } #Returns $true
$myHash.get_Values()| Test-Any { $_ -eq $False }#Returns $true

[HashTable]$myHash = @{a=$true;b=$true;c=$true}   
$myHash.get_Values()| Test-Any { $_ -eq $true } #Returns $true
$myHash.get_Values()| Test-Any { $_ -eq $False }#Returns $False

Спасибо!!!