about_Functions_Argument_Completion

about_Functions_Argument_Completion (함수 인수 완성)

PowerShell 함수에 인수 완성(argument completer)을 붙이는 방법을 정리한 문서예요. 인수 완성은 Tab 키로 매개변수 값을 자동 완성해서 입력 속도를 높여주는 기능이에요. 이번 글에서 ValidateSet, ArgumentCompletions, ArgumentCompleter 세 가지 방식과 클래스 기반 완성기, Register-ArgumentCompleter까지 차근차근 살펴볼게요.

출처: Microsoft Learn — about_Functions_Argument_Completion / 원문 GitHub 소스

본문

짧은 설명 (Short description)

인수 완성은 힌트를 제공하고, 값을 발견하기 쉽게 해주고, 인수 값을 입력하는 속도를 높여주는 PowerShell 기능이에요.

긴 설명 (Long description)

이 문서는 PowerShell 함수에서 인수 완성기를 구현하는 여러 가지 방법을 설명해요. 인수 완성기는 매개변수에 넣을 수 있는 값을 미리 제시해주는 역할을 해요. 사용자가 매개변수 이름 뒤에서 Tab 키를 누르면, 그 시점에 계산된 가능한 값들이 표시돼요. 매개변수에 인수 완성기를 정의하는 방법은 여러 가지가 있어요.

참고: Tab 키는 Windows에서 기본 키 바인딩이에요. 이 키 바인딩은 PSReadLine 모듈이나 PowerShell을 호스팅하는 애플리케이션이 바꿀 수 있어요. 그리고 Windows가 아닌 플랫폼에서는 키 바인딩이 달라요. 자세한 내용은 about_PSReadLine을 참고해요.

ValidateSet 특성

ValidateSet 특성은 매개변수나 변수에 넣을 수 있는 유효한 값의 집합을 지정하고, Tab 완성을 활성화해요. 매개변수나 변수 값이 집합 안의 값과 일치하지 않으면 PowerShell이 오류를 만들어요. 아래 예시에서 Fruit 매개변수 값은 Apple, Banana, Pear 중 하나만 될 수 있어요.

param (
    [Parameter(Mandatory=$true)]
    [ValidateSet('Apple', 'Banana', 'Pear')]
    [string[]]$Fruit
)

다음 예시에서는 $flavor 변수의 값이 Chocolate, Strawberry, Vanilla 중 하나여야 해요. ValidateSet 특성은 매개변수뿐 아니라 어떤 변수에도 쓸 수 있어요.

[ValidateSet('Chocolate', 'Strawberry', 'Vanilla')]
[string]$Flavor = 'Strawberry'

이 검증은 스크립트 안에서 그 변수에 값을 할당할 때마다 일어나요.

param (
    [ValidateSet('hello', 'world')]
    [string]$Message
)

$Message = 'bye'

이 예시는 런타임에 다음과 같은 오류를 돌려줘요.

MetadataError: The attribute cannot be added because variable Message with
value bye would no longer be valid.

Tab 확장에 대한 자세한 내용은 about_Tab_Expansion을 참고해요.

클래스를 이용한 동적 ValidateSet 값

클래스를 쓰면 ValidateSet 값을 런타임에 동적으로 만들어낼 수 있어요. 아래 예시에서는 $Sound 변수에 넣을 수 있는 값들을 SoundNames라는 클래스가 생성해요. 이 클래스는 세 개의 파일 시스템 경로에서 사용 가능한 사운드 파일을 확인해요.

class SoundNames : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        $SoundPaths = '/System/Library/Sounds/',
                      '/Library/Sounds',
                      '~/Library/Sounds'
        $SoundNames = foreach ($SoundPath in $SoundPaths) {
            if (Test-Path $SoundPath) {
                (Get-ChildItem $SoundPath).BaseName
            }
        }
        return [string[]] $SoundNames
    }
}

그리고 [SoundNames] 클래스를 다음과 같이 동적 ValidateSet 값으로 구현해요.

param (
    [ValidateSet([SoundNames])]
    [string]$Sound
)

참고: IValidateSetValuesGenerator 클래스는 PowerShell 6.0에서 도입됐어요.

ArgumentCompletions 특성

ArgumentCompletions 특성은 특정 매개변수에 Tab 완성 값을 추가해줘요. Tab 완성이 필요한 매개변수마다 ArgumentCompletions 특성을 정의해야 해요. ArgumentCompletions 특성은 ValidateSet과 비슷해요. 두 특성 모두 매개변수 이름 뒤에서 사용자가 Tab 키를 누를 때 보여줄 값 목록을 받아요. 다만 ValidateSet과 달리 이 값들을 검증하지 않고, 제안(suggestion)처럼 취급해요. 그래서 사용자는 목록에 있는 값이 아니라 어떤 값이든 넣을 수 있어요.

ArgumentCompletions 특성은 옵션을 정의하려면 스크립트블록이 필요한 ArgumentCompleter 특성과 혼동하면 안 돼요. 지정된 값들이 그대로 제공돼요.

문법은 다음과 같아요.

function Test-ArgumentCompletions {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ArgumentCompletions('Fruits', 'Vegetables')]
        $Type,

        [Parameter()]
        [ArgumentCompletions('Apple', 'Banana', 'Orange')]
        $Fruit,

        [Parameter()]
        [ArgumentCompletions('Onion', 'Carrot', 'Lettuce')]
        $Vegetable
    )
}

각 매개변수는 Tab 완성을 켜기 위해 ArgumentCompletions 특성에 옵션 목록을 제공받아요.

이 특성은 PowerShell 6.0에서 도입됐어요.

ArgumentCompleter 특성

ArgumentCompleter 특성은 특정 매개변수에 Tab 완성 값을 추가해줘요. Tab 완성이 필요한 매개변수마다 ArgumentCompleter 특성을 정의해야 해요.

ArgumentCompleter 특성을 추가하려면 값을 정하는 스크립트블록을 정의해야 해요. 스크립트블록은 아래 지정된 순서대로 다음 매개변수를 받아야 해요. 값들은 위치(positional)로 전달되기 때문에 매개변수 이름은 중요하지 않아요.

문법은 다음과 같아요.

function MyArgumentCompleter {
    param (
        [Parameter(Mandatory)]
        [ArgumentCompleter( {
            param ( $commandName,
                    $parameterName,
                    $wordToComplete,
                    $commandAst,
                    $fakeBoundParameters )
            # Perform calculation of tab completed values here.
        } )]
        $ParamName
    )
}

ArgumentCompleter 스크립트블록

스크립트블록의 매개변수들은 다음과 같은 값으로 설정돼요.

  • $commandName (위치 0) — 스크립트블록이 Tab 완성을 제공하는 대상 명령(command)의 이름으로 설정돼요.
  • $parameterName (위치 1) — Tab 완성이 필요한 값의 매개변수로 설정돼요.
  • $wordToComplete (위치 2) — 사용자가 Tab 키를 누르기 전에 입력한 값으로 설정돼요. 스크립트블록은 이 값을 사용해서 Tab 완성 값을 정해요.
  • $commandAst (위치 3) — 현재 입력 줄의 추상 구문 트리(AST, Abstract Syntax Tree)로 설정돼요. 자세한 내용은 AST 형식 문서를 참고해요.
  • $fakeBoundParameters (위치 4) — 사용자가 Tab 키를 누르기 전 cmdlet의 $PSBoundParameters를 담은 해시테이블로 설정돼요. 자세한 내용은 about_Automatic_Variables를 참고해요.

ArgumentCompleter 스크립트블록은 파이프라인을 통해 값을 펼쳐야(unroll) 해요. ForEach-Object나 Where-Object처럼 적당한 방법을 쓰면 돼요. 값 배열을 돌려주면 PowerShell이 배열 전체를 하나의 Tab 완성 값으로 취급해버려요.

다음 예시는 Value 매개변수에 Tab 완성을 추가해요. Value 매개변수만 지정하면 Value에 넣을 수 있는 모든 값(인수)이 표시돼요. Type 매개변수를 지정하면 Value 매개변수는 그 타입에 해당하는 값만 보여줘요.

추가로 -like 연산자를 쓰면, 사용자가 다음 명령을 입력하고 Tab 완성을 쓰면 Apple만 돌려받게 돼요.

Test-ArgumentCompleter -Type Fruits -Value A
function MyArgumentCompleter{
    param ( $commandName,
            $parameterName,
            $wordToComplete,
            $commandAst,
            $fakeBoundParameters )

    $possibleValues = @{
        Fruits = @('Apple', 'Orange', 'Banana')
        Vegetables = @('Onion', 'Carrot', 'Lettuce')
    }

    if ($fakeBoundParameters.ContainsKey('Type')) {
        $possibleValues[$fakeBoundParameters.Type] | Where-Object {
            $_ -like "$wordToComplete*"
        }
    } else {
        $possibleValues.Values | ForEach-Object {$_}
    }
}

function Test-ArgumentCompleter {
[CmdletBinding()]
 param (
        [Parameter(Mandatory=$true)]
        [ValidateSet('Fruits', 'Vegetables')]
        $Type,

        [Parameter(Mandatory=$true)]
        [ArgumentCompleter({ MyArgumentCompleter @args })]
        $Value
      )
}

클래스 기반 인수 완성기

PowerShell 7.2부터 매개변수를 받는 인수 완성기를 더 범용적으로 정의할 수 있는 새 기능이 추가됐어요.

ArgumentCompleterAttribute에서 파생하면 재사용 가능한 범용 완성기를 만들 수 있어요. 예를 들면 아래처럼요.

[DirectoryCompleter(ContainingFile="pwsh.exe", Depth=2)]

[DateCompleter(WeekDay='Monday', From="LastYear")]

[GitCommits(Branch='release')]

파생된 특성들은 IArgumentCompleterFactory 인터페이스를 구현하고, 속성 값을 사용해서 전문화된 완성기를 만들어야 해요.

using namespace System.Collections
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Management.Automation.Language

class NumberCompleter : IArgumentCompleter {

    [int] $From
    [int] $To
    [int] $Step

    NumberCompleter([int] $from, [int] $to, [int] $step) {
        if ($from -gt $to) {
            throw [ArgumentOutOfRangeException]::new("from")
        }
        $this.From = $from
        $this.To = $to
        $this.Step = $step -lt 1 ? 1 : $step
    }

    [IEnumerable[CompletionResult]] CompleteArgument(
        [string] $CommandName,
        [string] $parameterName,
        [string] $wordToComplete,
        [CommandAst] $commandAst,
        [IDictionary] $fakeBoundParameters) {

        $resultList = [List[CompletionResult]]::new()
        $Local:to = $this.To
        $Local:step = $this.Step
        for ($i = $this.From; $i -lt $to; $i += $step) {
            $resultList.Add([CompletionResult]::new($i.ToString()))
        }

        return $resultList
    }
}

class NumberCompletionsAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory {
    [int] $From
    [int] $To
    [int] $Step

    NumberCompletionsAttribute([int] $from, [int] $to, [int] $step) {
        $this.From = $from
        $this.To = $to
        $this.Step = $step
    }

    [IArgumentCompleter] Create() { return [NumberCompleter]::new($this.From, $this.To, $this.Step) }
}

그러면 PowerShell에서 이렇게 사용할 수 있어요.

function Add{
    param(
       [NumberCompletions(0, 100, 5)]
       [int] $X,

       [NumberCompletions(0, 100, 5)]
       [int] $Y
    )
    $X + $Y
}

Register-ArgumentCompleter

Register-ArgumentCompleter cmdlet은 사용자 지정 인수 완성기를 등록해요. 인수 완성기는 지정한 아무 명령에 대해서도 런타임에 동적 Tab 완성을 제공할 수 있게 해줘요.

자세한 내용은 Register-ArgumentCompleter를 참고해요.

더 알아보기