sourcecode

LINQ Any()와 동등한 PowerShell?

codebag 2023. 8. 21. 21:14
반응형

LINQ Any()와 동등한 PowerShell?

하위 버전에 저장된 스크립트의 위치에서 최상위 수준의 모든 디렉터리를 찾고 싶습니다.

C#에서는 다음과 같습니다.

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

PowerShell에서 "Any()"에 해당하는 항목을 찾는 데 약간 어려움이 있으며 확장 메서드를 호출하는 어색함을 겪고 싶지 않습니다.

지금까지 알아낸 건 다음과 같습니다.

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

이것은 나를 발견합니다.svn디렉터리 자체는 있지만 상위 디렉터리는 아닙니다. 이것이 제가 원하는 것입니다.를 .

 | Select-Object {$_.Directory}

명령 목록의 끝에는 일련의 빈 줄만 표시됩니다.

PowerShell v3+ 솔루션으로 즉각적인 질문에 답변하려면 다음과 같이 하십시오.

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

-Directory 항목을 합니다.-Recurse -Depth 23단계, , 단3계(자손, 자, 증)까지 재귀,Include여러 개의 (임의 구성 요소) 필터를 지정할 수 있습니다..Parent.FullName멤버 액세스 열거를 사용하여 일치하는 dirs의 상위 dirs.의 전체 경로를 반환합니다(즉, 집합의 요소 속성에 액세스함).

질문에 : 스질경우의문보:select-object {$_.Directory}에 의해 반환된 인스턴스 때문에 작동하지 않습니다.Get-ChildItem.Directory 성, 만, 속, 만.Parent 속성;;Select-Object -ExpandProperty Parent사용했어야 합니다.

이자의 재산가액만 반환하는 것 외에,-ExpandProperty속성의 존재도 강제 적용합니다. 적으로대조,,Select-Object {$_.Directory}문자 그대로 이름이 지정된 속성을 가진 사용자 지정 개체를 반환합니다.$_.Directory이 값치가있인$null에 입력객없고음때가 없다는 것을 할 때.Directory이 재산; 이들것들$null값이 콘솔에서 빈 줄로 인쇄됩니다.


지정된 열거형(집합)에 주어진 조건을 만족하는 요소가 있는지 여부를 나타내는 LINQ방법동등한 PowerShell에 대한 보다 일반적인 질문의 경우:

기본적으로 PowerShell은 이러한 기능제공하지 않지만 동작은 에뮬레이트할 수 있습니다.


PowerShell v4+ 고유.Where() 방법 사용:

주의: 이렇게 하려면 메모리에서 전체 입력 컬렉션을 먼저 수집해야 합니다. 이는 대량 수집 및/또는 장시간 실행되는 입력 명령에서 문제가 될 수 있습니다.

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

...관심 관심명나타니다냅을령다▁theents▁of니▁repres,를 나타냅니다.$_ ...객체에 은 PowerShell의 PowerShell에서 사용됩니다.$_에 있는 . variable은 입력 객체입니다.'First'첫 번째 일치 항목이 발견되면 메서드가 반환됩니다.

예:

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

파이프라인 사용:명령이 하나 이상의 출력 개체를 생성했는지 여부 테스트 [조건 일치]:

파이프라인 기반 솔루션의 장점메모리의 전체 출력을 먼저 수집할 필요 없이 명령이 생성될 때마다 하나씩 명령출력작용할 수 있다는 것입니다.

  • 만약 당신이 모든 물체가 열거되어 있는 것에 대해 개의않는다면 - 적어도 하나가 있는 것에만 관심이 있더라도 - 파올로 테데스코의 도움이 되는 확장자를 자레드 파의 도움이 되는 답변에 사용하세요.이 접근 방식의 단점은 논리적으로 첫 번째 개체를 수신하는 즉시 출력 개체가 있는지 여부를 결정할 수 있더라도 (잠재적으로 장시간 실행) 명령이 모든 출력 개체 생성을 완료할 때까지 항상 기다려야 한다는 것입니다.

  • 하나의 [일치하는] 개체가 발견되는 즉시 파이프라인종료하려면 다음 두 가지 옵션이 있습니다.

    • [Ad-hoc : 이해하기 쉽지만 구현하기 번거로운] 파이프라인을 더미 루프로 둘러싸서 파이프라인과 그 루프를 이탈할 때 사용합니다...., "는 " " " 입니다.$_ ...조건과 일치):

       # Exit on first input object.
       [bool] $haveAny = do { ... | % { $true; break } } while ($false)
      
       # Exit on first input object that matches a condition.
       [bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)
      
    • [PowerShell v3+ 자체 포함 유틸리티 기능을 사용하여 구현] 아래 기능 구현을 참조하십시오.스크립트에 추가하거나 대화형 세션에서 사용하기 위해$PROFILEjava.


PowerShell v3+: 최적화된 유틸리티 기능

PowerShell(Core) v7.2.x의 경우 파이프라인을 조기에 종료할 수 있는 직접적인 방법없으므로기반한 해결 방법이 있습니다.현재 NET 반영 및 개인 유형이 필요합니다.

이러한 기능이 있어야 한다는 것에 동의한다면 GitHub #3821호의 대화에 참여하십시오.

#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지정되지 않았으며, 그렇지 않으면 개체가 있는 필터(스크립트 블록)를 평가합니다.

    • ForEach-Object는 반드시 " "라는 것을 해야 합니다.$_여기에 있는 PetSerAl의 유용한 답변에서 설명한 것처럼 모든 시나리오에서 현재 파이프라인 개체에 바인딩됩니다.
  • (Add-Type ...하여 C# 코드로 하여 C# 코드와 를 던집니다.Select-Object -First합니다. 즉, (PowerShell v3+)는 다음과 같습니다.[System.Management.Automation.StopUpstreamCommandsException] PowerShell v5에서는 여전히 개인 유형입니다.여기 배경: http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspxA 은 이 코드를 댓글에 기여해 준 PetSerAl에 큰 감사를 표합니다.

예:

  • PS> @() | Test-Any false

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

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


배경 정보

재러드 파의 도움이 되는 답변과 파올로 테데스코의 도움이 되는 연장은 한 가지 측면에서 부족합니다. 그들은 한 번 일치하는 것이 발견되면 파이프라인을 빠져나가지 않으며, 이는 중요한 최적화가 될 수 있습니다.

안타깝게도 PowerShell v5의 경우에도 파이프라인을 조기에 종료할 수 있는 직접적인 방법은 없습니다.이러한 기능이 있어야 한다는 것에 동의한다면 GitHub #3821호의 대화에 참여하십시오.

JaredPar의 답변순진하게 최적화하면 실제로 코드가 짧아집니다.

# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
  • process파이프라인에 요소가 하나 이상 있는 경우에만 블록을 입력합니다.

    • 작은 주의사항:설계상 파이프라인이 전혀 없는 경우 블록은 로 설정된 상태로 계속 입력되므로 호출합니다.Test-Any 파이프라인 밖에서 도움이 되지 않는 반품.$true사이를 구분하는 것$null | Test-Any그리고.Test-Any합니다.$MyInvocation.ExpectingInput,어느 것이$true파이프라인에서만: function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
  • $true적어도 하나의 객체가 발견되었다는 신호를 출력 스트림에 기록합니다.

  • break그런 다음 파이프라인을 종료하여 추가 개체의 불필요한 처리를 방지합니다.그러나 모든 엔클로저 루프도 종료합니다. 파이프라인Thanks, PetSerAl 종료하도록 설계되지 않았습니다.

    • 파이프라인을 종료하는 명령이 있으면 이 명령이 실행될 것입니다.
    • :return다음 입력 개체로 이동할 수 있습니다.
  • process 실행합니다.break,end블록은 다음과 같은 경우에만 도달합니다.process블록을 입력하지 않았습니다. 즉, 빈 파이프라인을 의미합니다.$false는 출력 스트림에 기록되어 이를 나타냅니다.

안타깝게도 PowerShell에는 이와 동등한 기능이 없습니다.저는 범용 테스트-Any 함수/필터 제안과 함께 이에 대한 블로그 게시물을 작성했습니다.

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

블로그 게시물:파이프라인 안에 뭐가 있나요?

@대한으로, @JaredPar의 @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

LINQ 래원 LINQ 수있습다니용을 할 수 있습니다.Any:

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

실제로 매우 간단합니다. 먼저 $true(명확하게 포맷됨)를 선택하면 됩니다.

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

다른 방법:

($source `
        | where { <predicate> } `
        | foreach { $true } `
        | select -first 1)

이제 제 접근 방식은 다음과 같습니다.

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") }
        }

저는 결국 그것을 세고 말았습니다.

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

이것을 좀 더 조일 수 있습니다.

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

참고 - $_. 전달중첩된 gci에 대한 이름은 필요하지 않습니다.$_만 건네주면 충분합니다.

다음 솔루션을 권장합니다.

<#
.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

이 방법은 지금까지 찾은 것 중 가장 좋은 방법입니다(이미 참이 발견된 경우 모든 요소에 대해 반복하지 않으며 파이프라인을 손상시키지 않습니다).

LINQ에서 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
}

여기서 가장 좋은 대답은 @JaredPar가 제안한 기능이라고 생각합니다만, 저처럼 한 줄을 좋아하신다면 한 줄을 제안하고 싶습니다.

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

%{ $match = $false }{ $match = $match -or YOUR_CONDITION }{ $match }하나 이상의 항목이 조건과 일치하는지 확인합니다.

한 가지 참고 - 일반적으로 Any 작업은 조건과 일치하는 첫 번째 항목을 찾을 때까지 배열을 평가합니다.하지만 이 코드는 모든 항목을 평가합니다.

덧붙이자면, 원라이너가 되도록 쉽게 조정할 수 있습니다.

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

%{ $match = $false }{ $match = $match -and YOUR_CONDITION }{ $match }모든 항목이 조건과 일치하는지 확인합니다.

need Any you need (필요한 모든 것)을 선택하려면 -or need를 선택하려면 All need를 선택합니다.-and.

저는 좀 더 linq 스타일의 접근법을 취했습니다.

저는 이 질문이 아마도 매우 오래된 것이라는 것을 압니다.이를 통해 필요한 사항을 충족할 수 있었습니다.

PS> $searchData = "unn"
PS> $StringData = @("unn", "dew", "tri", "peswar", "pymp")
PS> $delegate =  [Func[string,bool]]{ param($d); return $d -eq $searchData }
PS> [Linq.Enumerable]::Any([string[]]$StringData, $delegate)

여기서 찍은 사진:

https://www.red-gate.com/simple-talk/dotnet/net-framework/high-performance-powershell-linq/ #post-71022-_Toc482783751

mklement0의 유용한 답변의 구현이 cmdlet으로 바뀌었습니다. 이 답변은 윈도우즈 PowerShell 5.1 및 PowerShell 7+와 호환되어야 합니다(이전 버전에서는 작동하지 않을 수 있음).

여기서 가장 큰 장점은StopUpstreamCommandsException는 됩니다.static필드, 즉, 우리는 세션당 한 번만 반영을 통해 그것을 얻으면 됩니다.

Add-Type -TypeDefinition '
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;

namespace TestAny
{
    [Cmdlet(VerbsDiagnostic.Test, "Any")]
    public sealed class TestAnyCommand : PSCmdlet
    {
        private static Exception s_exception;

        private static readonly List<PSVariable> list = new List<PSVariable>(1);

        private Exception StopUpstreamException
        {
            get
            {
                return s_exception ?? (s_exception = GetException());
            }
        }

        [Parameter(Mandatory = true, ValueFromPipeline = true)]
        public PSObject InputObject { get; set; }

        [Parameter(Mandatory = true, Position = 0)]
        public ScriptBlock Filter { get; set; }

        protected override void ProcessRecord()
        {
            list.Clear();
            list.Add(new PSVariable("_", InputObject));
            bool condition = LanguagePrimitives.ConvertTo<bool>(
                Filter.InvokeWithContext(null, list)
                    .FirstOrDefault());

            if (condition)
            {
                WriteObject(true);
                throw StopUpstreamException;
            }
        }

        protected override void EndProcessing()
        {
            WriteObject(false);
        }

        private Exception GetException()
        {
            return (Exception)Activator.CreateInstance(
                typeof(Cmdlet).Assembly
                    .GetType("System.Management.Automation.StopUpstreamCommandsException"), this);
        }
    }
}' -PassThru | Import-Module -Assembly { $_.Assembly }

0..1mb | Test-Any { $_ -gt 10 }

언급URL : https://stackoverflow.com/questions/1499466/powershell-equivalent-of-linq-any

반응형