関数ドライブ
前回はPowerShell Coreの関数がどのスコープに所属するかを説明した。関数は関数が作成された場所のスコープに所属するのがデフォルトの動作。ただし、スコープ指定を行って作成をすればグローバルスコープなど、本来のスコープを超えるスコープに登録させることもできる。
もうひとつ、PowerShell Coreの関数は関数ドライブという点からアクセスする方法も用意されている。PowerShell Coreでは作成した関数は関数ドライブに所属するようになる。関数ドライブはC:やD:のようなドライブ表記と同じで、Function:のようにコロンを指定した表記でアクセスできる。たとえば、次のようにGet-ChildItemを実行すると関数ドライブに登録されている関数を表示させることができる。
関数ドライブに登録されている関数を一覧表示
PS /Users/daichi> Get-ChildItem function:
CommandType Name Version Source
----------- ---- ------- ------
Function cd..
Function cd\
Function Clear-Host
Function help
Function oss
Function Pause
Function prompt
Function PSConsoleHostReadLine 2.0.0 PSReadLine
Function TabExpansion2
PS /Users/daichi>
関数ドライブに登録されている関数には「Function:関数名」といった表記でアクセスできる。さらにDefinitionを使えばどのような関数として定義されているのかの内容も確認することができる。さきほど表示された関数のうち、次の関数はシェルでいえばエイリアスに相当するくらい簡単な置き換えになっていることがわかる。
関数: cd..
PS /Users/daichi> (Get-ChildItem function:cd..).Definition
Set-Location ..
PS /Users/daichi>
関数: cd\
PS /Users/daichi> (Get-ChildItem function:cd\).Definition
Set-Location \
PS /Users/daichi>
関数: Pause
PS /Users/daichi> (Get-ChildItem function:Pause).Definition
$null = Read-Host 'Press Enter to continue...'
PS /Users/daichi>
関数: prompt
PS /Users/daichi> (Get-ChildItem function:prompt).Definition
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
# .Link
# https://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml
PS /Users/daichi>
関数: PSConsoleHostReadLine
PS /Users/daichi> (Get-ChildItem function:PSConsoleHostReadLine).Definition
Microsoft.PowerShell.Core\Set-StrictMode -Off
[Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext)
PS /Users/daichi>
関数: Clear-Host
PS /Users/daichi> (Get-ChildItem function:Clear-Host).Definition
& (Get-Command -CommandType Application clear | Select-Object -First 1).Definition
# .Link
# https://go.microsoft.com/fwlink/?LinkID=225747
# .ExternalHelp System.Management.Automation.dll-help.xml
PS /Users/daichi>
次の関数はそれなりに作り込まれたものになっている。まず関数helpだが、注目すべきはparam()の使い方だ。パラメータが多くなってきた場合にどのようにparam()を記述すればよいのかのよいサンプルになっている。パラメータの書き方、デフォルト値のセットの方法、引数候補の列挙など、知っておきたいものばかりだ。
関数: help
PS /Users/daichi> (Get-ChildItem function:help).Definition
<#
.FORWARDHELPTARGETNAME Get-Help
.FORWARDHELPCATEGORY Cmdlet
#>
[CmdletBinding(DefaultParameterSetName='AllUsersView', HelpUri='https://go.microsoft.com/fwlink/?LinkID=113316')]
param(
[Parameter(Position=0, ValueFromPipelineByPropertyName=$true)]
[string]
${Name},
[string]
${Path},
[ValidateSet('Alias','Cmdlet','Provider','General','FAQ','Glossary','HelpFile','ScriptCommand','Function','Filter','ExternalScript','All','DefaultHelp','Workflow','DscResource','Class','Configuration')]
[string[]]
${Category},
[Parameter(ParameterSetName='DetailedView', Mandatory=$true)]
[switch]
${Detailed},
[Parameter(ParameterSetName='AllUsersView')]
[switch]
${Full},
[Parameter(ParameterSetName='Examples', Mandatory=$true)]
[switch]
${Examples},
[Parameter(ParameterSetName='Parameters', Mandatory=$true)]
[string]
${Parameter},
[string[]]
${Component},
[string[]]
${Functionality},
[string[]]
${Role},
[Parameter(ParameterSetName='Online', Mandatory=$true)]
[switch]
${Online},
[Parameter(ParameterSetName='ShowWindow', Mandatory=$true)]
[switch]
${ShowWindow})
# Display the full help topic by default but only for the AllUsersView parameter set.
if (($psCmdlet.ParameterSetName -eq 'AllUsersView') -and !$Full) {
$PSBoundParameters['Full'] = $true
}
# Nano needs to use Unicode, but Windows and Linux need the default
$OutputEncoding = if ([System.Management.Automation.Platform]::IsNanoServer -or [System.Management.Automation.Platform]::IsIoT) {
[System.Text.Encoding]::Unicode
} else {
[System.Console]::OutputEncoding
}
$help = Get-Help @PSBoundParameters
# If a list of help is returned, don't pipe to more
if (($help | Select-Object -First 1).PSTypeNames -Contains 'HelpInfoShort')
{
$help
}
else
{
# Respect PAGER, use more on Windows, and use less on Linux
$moreCommand,$moreArgs = $env:PAGER -split '\s+'
if ($moreCommand) {
$help | & $moreCommand $moreArgs
} elseif ($IsWindows) {
$help | more.com
} else {
$help | less
}
}
PS /Users/daichi>
ossは頻用される機能だが、この機能が関数で実装されていることもこれでわかる。内容を見てみるとParam()、Begin{}、Process{}、End{}、try-catchが使われた関数の見本のような内容になっている。どのように関数を記述すればよいのかの見本としても利用できる。
関数: oss
PS /Users/daichi> (Get-ChildItem function:oss).Definition
[CmdletBinding()]
param(
[ValidateRange(2, 2147483647)]
[int]
${Width},
[Parameter(ValueFromPipeline=$true)]
[psobject]
${InputObject})
begin
{
try {
$PSBoundParameters['Stream'] = $true
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Out-String',[System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Out-String
.ForwardHelpCategory Cmdlet
#>
PS /Users/daichi>
最後は関数TabExpansion2だ。この関数はBegin{}とProcess{}を使わず、End{}だけを使う場合の関数として興味深い。
関数: TabExpansions2
PS /Users/daichi> (Get-ChildItem function:TabExpansion2).Definition
<# Options include:
RelativeFilePaths - [bool]
Always resolve file paths using Resolve-Path -Relative.
The default is to use some heuristics to guess if relative or absolute is better.
To customize your own custom options, pass a hashtable to CompleteInput, e.g.
return [System.Management.Automation.CommandCompletion]::CompleteInput($inputScript, $cursorColumn,
@{ RelativeFilePaths=$false }
#>
[CmdletBinding(DefaultParameterSetName = 'ScriptInputSet')]
Param(
[Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 0)]
[string] $inputScript,
[Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 1)]
[int] $cursorColumn,
[Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 0)]
[System.Management.Automation.Language.Ast] $ast,
[Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 1)]
[System.Management.Automation.Language.Token[]] $tokens,
[Parameter(ParameterSetName = 'AstInputSet', Mandatory = $true, Position = 2)]
[System.Management.Automation.Language.IScriptPosition] $positionOfCursor,
[Parameter(ParameterSetName = 'ScriptInputSet', Position = 2)]
[Parameter(ParameterSetName = 'AstInputSet', Position = 3)]
[Hashtable] $options = $null
)
End
{
if ($psCmdlet.ParameterSetName -eq 'ScriptInputSet')
{
return [System.Management.Automation.CommandCompletion]::CompleteInput(
<#inputScript#> $inputScript,
<#cursorColumn#> $cursorColumn,
<#options#> $options)
}
else
{
return [System.Management.Automation.CommandCompletion]::CompleteInput(
<#ast#> $ast,
<#tokens#> $tokens,
<#positionOfCursor#> $positionOfCursor,
<#options#> $options)
}
}
PS /Users/daichi>
このように、PowerShell Coreでは関数が関数ドライブに登録されるという仕組みだ。PowrShell Coreのエイリアスはシェルのエイリアスと比較すると名前だけの置き換えになっており、パラメータまで含めたエイリアスということになってくると、PowerShellにおいては関数がその役割も担っていることがわかる。
関数は作成しても基本的にPowerShell Coreが終了すると同時に消えてしまう。PowerShell Coreを起動するごとに関数を定義したい場合には、プロファイルに定義を書いておけばよい。起動するごとに定義した関数を利用できるようになる。