在PowerShell中实现子命令模式

cra*_*aig 5 powershell powershell-3.0

是否可以在PowerShell中实现子命令模式?就像是:

command [subcommand] [options] [files]
Run Code Online (Sandbox Code Playgroud)

示例:Git,svn,Homebrew

一般的架构是什么?将实际工作委托给脚本块的单个函数?每个子命令都在自己的PS1文件中隔离,主要脚本是点源?PowerShell的各种元数据功能(例如Get-Command)是否能够"检查"子命令?

Rom*_*min 8

我想到了这种模式,并找到了两种方法.我在实践中没有找到真正的应用,所以这项研究相当学术化.但是下面的脚本运行正常.

实现这种模式(以自己的方式)的现有工具是 独家新闻.


pattern 子命令实现了经典的命令行界面

app <command> [parameters]
Run Code Online (Sandbox Code Playgroud)

此模式引入了一个脚本app.ps1,该脚本提供命令,而不是在脚本库或模块中提供多个脚本或函数.每个命令都是特殊子目录中的脚本,例如./Command.

获取可用命令

app
Run Code Online (Sandbox Code Playgroud)

调用命令

app c1 [parameters of Command\c1.ps1]
Run Code Online (Sandbox Code Playgroud)

获取命令帮助

app c1 -?     # works with splatting approach
app c1 -help  # works with dynamic parameters
Run Code Online (Sandbox Code Playgroud)

脚本app.ps1可能包含命令使用的常用函数.


splat.ps1(如此app.ps1) - splatting模式

优点:

  • 最小代码和开销.
  • 位置参数有效.
  • -? 尽力帮助(简短的帮助).

缺点:

  • PowerShell v3 +,splatting在v2中很有趣.

dynamic.ps1(此类app.ps1) -与动态参数图案

优点:

  • PowerShell v2 +.
  • TabExpansion适用于参数.

缺点:

  • 更多代码,更多运行时工作.
  • 只有命名参数.
  • 帮助-help.

脚本

splat.ps1

#requires -Version 3

param(
    $Command
)

if (!$Command) {
    foreach($_ in Get-ChildItem $PSScriptRoot\Command -Name) {
        [System.IO.Path]::GetFileNameWithoutExtension($_)
    }
    return
}

& "$PSScriptRoot\Command\$Command.ps1" @args
Run Code Online (Sandbox Code Playgroud)

dynamic.ps1

param(
    [Parameter()]$Command,
    [switch]$Help
)
dynamicparam {
    ${private:*pn} = 'Verbose', 'Debug', 'ErrorAction', 'WarningAction', 'ErrorVariable', 'WarningVariable', 'OutVariable', 'OutBuffer', 'PipelineVariable'
    $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Definition

    $Command = $PSBoundParameters['Command']
    if (!$Command) {return}

    $_ = Get-Command -Name "$PSScriptRoot\Command\$Command.ps1" -CommandType ExternalScript -ErrorAction 1
    if (!($_ = $_.Parameters) -or !$_.Count) {return}

    ${private:*r} = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    (${private:*a} = New-Object System.Collections.ObjectModel.Collection[Attribute]).Add((New-Object System.Management.Automation.ParameterAttribute))
    foreach($_ in $_.Values) {
        if (${*pn} -notcontains $_.Name) {
            ${*r}.Add($_.Name, (New-Object System.Management.Automation.RuntimeDefinedParameter $_.Name, $_.ParameterType, ${*a}))
        }
    }
    ${*r}
}
end {
    if (!$Command) {
        foreach($_ in Get-ChildItem $PSScriptRoot\Command -Name) {
            [System.IO.Path]::GetFileNameWithoutExtension($_)
        }
        return
    }

    if ($Help) {
        Get-Help "$PSScriptRoot\Command\$Command.ps1" -Full
        return
    }

    $null = $PSBoundParameters.Remove('Command')
    & "$PSScriptRoot\Command\$Command.ps1" @PSBoundParameters
}
Run Code Online (Sandbox Code Playgroud)