foo.exe
我理解为什么 PowerShell 要求我在运行命令时在命令前加上前缀.\foo.exe
。因此,如果我输入命令fo
并点击Tab
,自动完成功能会将我输入的内容更改为.\foo.exe
。我可以接受这一点。
问题是当foo.exe
使用命令行参数时,其中一个参数是文件bar.dat
。如果我输入.\foo.exe ba
并点击Tab
,我想获取.\foo.exe bar.dat
。我会不是想要获得.\foo.exe .\bar.dat
。这不仅非常烦人(更不用说丑陋),它实际上破坏了我的某些命令,这些命令不允许在参数中指定任何目录(甚至是当前目录)。
如何关闭此 PowerShell 怪癖,即.\
在当前目录中的每次自动完成中添加 ?就我而言,我希望它在所有地方都关闭 — 我可以记得在初始命令中键入.\foo.exe
而不是。foo.exe
如果没有办法关闭此功能,我将抛弃 PowerShell 并返回到cmd
。
我已经打开了PowerShell 问题 #18180。
答案1
从 PowerShell 7.3.1 开始,PowerShell总是自动完成文件和子目录的名称在当前目录中前面有.\
(./
在类 Unix 平台上)
为了程式(可执行文件)- 即第一的声明中的标记 - 这是一个必须,因为 PowerShell 的设计要求您发出执行位于当前的目录明确。
对于程序参数- 后续标记 - 这通常是不是需要 - 但是通常没有伤害。
如果你希望 PowerShell 在后一种情况下省略.\
/./
前缀,我鼓励你在PowerShell 存储库。 [更新:你已经打开了GitHub 问题 #18180]
解决方法:
为了全部命令及其参数,无论它们是 PowerShell 原生程序还是外部程序:
重新定义内置
TabExpansion2
函数[1]为了.\
在事后从完成结果中删除前缀:请参阅此评论在你创建的 GitHub 问题上,由@MartinGC94这里有一个适应性处理
./
,以便也支持类 Unix 平台;将它放在你的$PROFILE
文件以使其在所有未来的会话中默认可用。
# Adapted from: https://github.com/PowerShell/PowerShell/issues/18180#issuecomment-1261140386
# Credit belongs to MartinGC94 (https://github.com/MartinGC94)
function TabExpansion2 {
<# 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')]
[OutputType([System.Management.Automation.CommandCompletion])]
Param
(
[Parameter(ParameterSetName = 'ScriptInputSet', Mandatory = $true, Position = 0)]
[string] $inputScript,
[Parameter(ParameterSetName = 'ScriptInputSet', Position = 1)]
[int] $cursorColumn = $inputScript.Length,
[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
)
Set-StrictMode -Version 1
# The original TabExpansion2 code.
$completionOutput =
if ($psCmdlet.ParameterSetName -eq 'ScriptInputSet') {
[System.Management.Automation.CommandCompletion]::CompleteInput(
<#inputScript#> $inputScript,
<#cursorColumn#> $cursorColumn,
<#options#> $options)
}
else {
[System.Management.Automation.CommandCompletion]::CompleteInput(
<#ast#> $ast,
<#tokens#> $tokens,
<#positionOfCursor#> $positionOfCursor,
<#options#> $options)
}
# Custom post-processing that removes a .\ or ./ prefix, if present.
$completionOutput.CompletionMatches = @(
foreach ($item in $CompletionOutput.CompletionMatches) {
$isFileOrDirArg =
if ($item.ResultType -in [System.Management.Automation.CompletionResultType]::ProviderContainer, [System.Management.Automation.CompletionResultType]::ProviderItem) {
# A file / directory (provider item / container) path,
# though we must rule out that it acts *as a command*,
# given that the "./" \ "\." prefix is *required* when invoking files located in the current dir.
# Tokenize the part of the input command line up to the cursor position.
$pstokens = [System.Management.Automation.PSParser]::Tokenize($inputScript.Substring(0, $cursorColumn), [ref] $null)
# Determine if the last token is an argument.
switch ($pstokens[-1].Type) {
'CommandArgument' {
# An unquoted argument.
$true; break
}
{ $_ -in 'String', 'Number' } {
# Either:
# * A quoted string, which may be an argument or command;
# Note that in the end it is only treated as a command if preceded by '.' or '&'
# * A number which matches a filename that starts with a number, e.g. "7z.cmd"
switch ($pstokens[-2].Type) {
$null { $false; break } # Token at hand is first token? -> command.
'Operator' { $pstokens[-2].Content -notin '.', '&', '|', '&&', '||'; break } # If preceded by a call operator or an operator that starts a new command -> command
'GroupStart' { $false; break } # Preceded by "{", "(", "$(", or "@(" -> command.
'StatementSeparator' { $false; break } # Preceded by ";", i.e. the end of the previous statement -> command
Default { $true } # Everything else: assume an argument.
}
}
}
}
if ($isFileOrDirArg) {
# A file / directory (provider item / container) path acting as an *argument*
[System.Management.Automation.CompletionResult]::new(($item.CompletionText -replace '^([''"]?)\.[/\\]', '$1'), $item.ListItemText, $item.ResultType, $item.ToolTip)
}
else {
# Otherwise, pass through.
$item
}
}
)
return $completionOutput
}
为了参数的具体的外部程序:
您可以使用
Register-ArgumentCompleter
实现风俗给定命令的制表符补全(可能是外部可执行文件,用 表示-Native
)。然而,只有明确列举命令可以安装自定义完成器,因此你必须逐一列出在
-CommandName
下面的论点中。以下命令实现仅文件名补全
foo.exe
- 如果你将它放在$PROFILE
文件,它也将在未来的会话中提供:# Note: If foo.exe is located in the *current* directory, replace # 'foo' with '.\foo' Register-ArgumentCompleter -Native -CommandName foo -ScriptBlock { param($wordToComplete) Get-ChildItem -Name -File $wordToComplete* }
- 注意:这是一个简单的实现,假设你输入的是文件姓名仅;如果您输入相对路径,它还会仅使用 Tab 键完成名称,因此路径部分会丢失。需要做更多工作来处理这种情况。
[1] PowerShell 使用此内置函数获取制表符补全结果。重写它允许您自定义行为。显示的自定义函数合并了内置函数的代码(您可以使用 检查其主体$function:TabExpansion2
),并对默认结果执行后处理以删除.\
/./
前缀。源代码链接到撰写本文时为止的函数定义。