PowerShell 提供程序相对路径制表符完成问题

标签 powershell relative-path tab-completion powershell-cmdlet powershell-provider

我实现了一个简单的 PowerShell NavigationCmdletProvider

对于那些不知道的人来说,这意味着我可以使用 cmdlet 创建一个管理单元,它实际上是一个虚拟文件系统驱动器;可以像任何普通文件夹一样从 PowerShell 安装并导航到该驱动器。针对驱动器的每个操作(例如,检查路径是否指向有效项目、获取文件夹中子项目的名称列表等)都映射到从 NavigationCmdletProvider 继承的 .NET 类的方法 类。

我遇到制表符补全问题,希望找到解决方案。我发现使用相对路径时,制表符补全会给出不正确的结果。对于绝对路径,它工作得很好。

对于那些不知道的人来说,NavigationCmdletProvider 的 Tab 键补全功能是通过 PowerShell 调用 GetChildNames 方法来实现的,该方法由 NavigationCmdletProvider 重写> 类。

--问题演示--

假设我有一个提供程序“TEST”,具有以下文件夹层次结构:

TEST::child1
TEST::child1\child1a
TEST::child1\child1b
TEST::child2
TEST::child2\child2a
TEST::child2\child2b
TEST::child3
TEST::child3\child3a
TEST::child3\child3b

绝对路径:

如果我输入“dir TEST::child1\”并按tab几次,它会给我预期的结果:

> dir TEST::child1\child1a
> dir TEST::child1\child1b

相对路径:

首先,我导航到“TEST::child1”:

> cd TEST::child1

然后,如果我输入“dirspace”并按tab几次,它会给出错误的结果:

> dir .\child1\child1a
> dir .\child1\child1b

我希望看到这些:

> dir .\child1a
> dir .\child1b

这是 PowerShell 中的错误,还是我做错了什么?

这是提供程序的完整、独立的代码:

[CmdletProvider("TEST", ProviderCapabilities.None)]
public class MyTestProvider : NavigationCmdletProvider
{
    private Node m_Root;
    private void ConstructTestHierarchy()
    {
        //
        // Create the nodes
        //
        Node root = new Node("");
            Node child1 = new Node("child1");
                Node child1a = new Node("child1a");
                Node child1b = new Node("child1b");
            Node child2 = new Node("child2");
                Node child2a = new Node("child2a");
                Node child2b = new Node("child2b");
            Node child3 = new Node("child3");
                Node child3a = new Node("child3a");
                Node child3b = new Node("child3b");

        //
        // Construct node hierarchy
        //
        m_Root = root;
            root.AddChild(child1);
                child1.AddChild(child1a);
                child1.AddChild(child1b);
            root.AddChild(child2);
                child2.AddChild(child2a);
                child2.AddChild(child2b);
            root.AddChild(child3);
                child3.AddChild(child3a);
                child3.AddChild(child3b);
    }

    public MyTestProvider()
    {
        ConstructTestHierarchy();
    }

    protected override bool IsValidPath(string path)
    {
        return m_Root.ItemExistsAtPath(path);
    }

    protected override bool ItemExists(string path)
    {
        return m_Root.ItemExistsAtPath(path);
    }

    protected override void GetChildNames(string path, ReturnContainers returnContainers)
    {
        var children = m_Root.GetItemAtPath(path).Children;
        foreach (var child in children)
        {
            WriteItemObject(child.Name, child.Name, true);
        }
    }
    protected override bool IsItemContainer(string path)
    {
        return true;
    }
    protected override void GetChildItems(string path, bool recurse)
    {
        var children = m_Root.GetItemAtPath(path).Children;
        foreach (var child in children)
        {
            WriteItemObject(child.Name, child.Name, true);
        }
    }
}

/// <summary>
/// This is a node used to represent a folder inside a PowerShell provider
/// </summary>
public class Node
{
    private string m_Name;
    private List<Node> m_Children;

    public string Name { get { return m_Name; } }
    public ICollection<Node> Children { get { return m_Children; } }

    public Node(string name)
    {
        m_Name = name;
        m_Children = new List<Node>();
    }

    /// <summary>
    /// Adds a node to this node's list of children
    /// </summary>
    public void AddChild(Node node)
    {
        m_Children.Add(node);
    }
    /// <summary>
    /// Test whether a string matches a wildcard string ('*' must be at end of wildcardstring)
    /// </summary>
    private bool WildcardMatch(string basestring, string wildcardstring)
    {
        //
        // If wildcardstring has no *, just do a string comparison
        //
        if (!wildcardstring.Contains('*'))
        {
            return String.Equals(basestring, wildcardstring);
        }
        else
        {
            //
            // If wildcardstring is really just '*', then any name works
            //
            if (String.Equals(wildcardstring, "*"))
                return true;

            //
            // Given the wildcardstring "abc*", we just need to test if basestring starts with "abc"
            //
            string leftOfAsterisk = wildcardstring.Split(new char[] { '*' })[0];
            return basestring.StartsWith(leftOfAsterisk);

        }
    }

    /// <summary>
    /// Recursively check if "child1\child2\child3" exists
    /// </summary>
    public bool ItemExistsAtPath(string path)
    {
        //
        // If path is self, return self
        //
        if (String.Equals(path, "")) return true;

        //
        // If path has no slashes, test if it matches the child name
        //
        if(!path.Contains(@"\"))
        {
            //
            // See if any children have this name
            //
            foreach (var child in m_Children)
            {
                if (WildcardMatch(child.Name, path))
                    return true;
            }
            return false;
        }
        else
        {
            //
            // Split the path
            //
            string[] pathChunks = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);

            //
            // Take out the first chunk; this is the child we're going to search
            //
            string nextChild = pathChunks[0];

            //
            // Combine the rest of the path; this is the path we're going to provide to the child
            //
            string nextPath = String.Join(@"\", pathChunks.Skip(1).ToArray());

            //
            // Recurse into child
            //
            foreach (var child in m_Children)
            {
                if (String.Equals(child.Name, nextChild))
                    return child.ItemExistsAtPath(nextPath);
            }
            return false;
        }
    }

    /// <summary>
    /// Recursively fetch "child1\child2\child3" 
    /// </summary>
    public Node GetItemAtPath(string path)
    {
        //
        // If path is self, return self
        //
        if (String.Equals(path, "")) return this;

        //
        // If path has no slashes, test if it matches the child name
        //
        if (!path.Contains(@"\"))
        {
            //
            // See if any children have this name
            //
            foreach (var child in m_Children)
            {
                if (WildcardMatch(child.Name, path))
                    return child;
            }
            throw new ApplicationException("Child doesn't exist!");
        }
        else
        {
            //
            // Split the path
            //
            string[] pathChunks = path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);

            //
            // Take out the first chunk; this is the child we're going to search
            //
            string nextChild = pathChunks[0];

            //
            // Combine the rest of the path; this is the path we're going to provide to the child
            //
            string nextPath = String.Join(@"\", pathChunks.Skip(1).ToArray());

            //
            // Recurse into child
            //
            foreach (var child in m_Children)
            {
                if (String.Equals(child.Name, nextChild))
                    return child.GetItemAtPath(nextPath);
            }
            throw new ApplicationException("Child doesn't exist!");
        }
    }
}

最佳答案

不确定这是一个错误,I found this workaround这似乎“完成了工作”。 (小更新,事实证明,当我沿着多个级别工作时,我的原始代码会“出错”。

''' <summary>
''' Joins two strings with a provider specific path separator.
''' </summary>
''' <param name="parent">The parent segment of a path to be joined with the child.</param>
''' <param name="child">The child segment of a path to be joined with the parent.</param>
''' <returns>A string that contains the parent and child segments of the path joined by a path separator.</returns>
''' <remarks></remarks>
Protected Overrides Function MakePath(parent As String, child As String) As String
    Trace.WriteLine("::MakePath(parent:=" & parent & ",child:=" & child & ")")
    Dim res As String = MyBase.MakePath(parent, child)
    Trace.WriteLine("::MakePath(parent:=" & parent & ",child:=" & child & ") " & res)
    If parent = "." Then
        'res = ".\" & child.Split("\").Last
        If String.IsNullOrEmpty(Me.SessionState.Path.CurrentLocation.ProviderPath) Then
        res = parent & PATH_SEPARATOR & child
        Else
        res = parent & PATH_SEPARATOR & child.Substring(Me.SessionState.Path.CurrentLocation.ProviderPath.Length + 1)
        'res = parent & PATH_SEPARATOR & child.Replace(Me.SessionState.Path.CurrentLocation.ProviderPath & PATH_SEPARATOR, String.Empty)
        End If
        Trace.WriteLine("::**** TRANSFORM: " & res)
    End If
    Return res
End Function

关于PowerShell 提供程序相对路径制表符完成问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9726725/

相关文章:

regex - 跨多行替换两个单词之间的文件文本

Python Twisted 与 Cmd 模块的集成

bash - 如何自动完成嵌套的多级子命令?

powershell - 使用Docker在Nano Server中进行Powershell静默安装

powershell - 如何在 PowerShell 中继续暂停批处理文件

c# - 我包含的外部可执行文件的路径是什么?

java - 如何在java中获取 "../../dir/file.ext"的完整真实文件路径?

playframework-2.0 - 生产环境中playframework 2.3.6中的相对路径

powershell - 使用 PowerShell Write-Output 创建后损坏的 Cmd 脚本