防止 PowerShell 自动完成添加点前缀

防止 PowerShell 自动完成添加点前缀

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),并对默认结果执行后处理以删除.\/./前缀。源代码链接到撰写本文时为止的函数定义。

相关内容