如何创建一个从管道和命令行接受多种参数类型的函数?

Posted

技术标签:

【中文标题】如何创建一个从管道和命令行接受多种参数类型的函数?【英文标题】:How do I create a function that accepts multiple argument types from pipeline and command line? 【发布时间】:2012-04-10 08:09:25 【问题描述】:

我正在尝试编写一个带有多个参数的函数,这些参数可以来自命令行,也可以来自管道。参数可以是字符串或目录对象。这个想法是以下任何调用都应该起作用:

Test-VEnv '.\MyPath', '.\AnotherPath'
Test-VEnv (dir)
'MyPath', 'AnotherPath' | Test-VEnv
dir | Test-VEnv

以下代码几乎有效:

function Test-VEnv 
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, Position=0,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$true)]
        [Alias('FullName')]
        [String[]]$Path
    )

    process 
        foreach ($P in $Path) 
            ...
        
    

它处理来自管道和命令参数的字符串,并处理来自管道的目录对象(通过 ValueFromPipelineByPropertyName 和 FullName 别名)。但它不处理命令行上的目录对象,所以

dir | Where-Object  Test-VEnv $_ 

失败,因为它将目录对象转换为字符串,它使用 Name 属性而不是 FullName,并且后续代码失败。

谁能告诉我如何实现我想要的?

我知道,即使我可以让它工作,它也可能不是一个特别好的设计。但据我所知,这是内置的 Test-Path 的工作方式,所以我想在发明自己的之前尝试遵循标准行为......

【问题讨论】:

【参考方案1】:

由于您的参数类型是string,当您不使用管道 Test-VEnv $_ 时,它会将文件系统信息对象强制转换为字符串。如果你调用 System.IO.FileInfoSystem.IO.DirectoryInfo 对象的 ToString() 方法,你会看到这个。当您使用管道时,它会绑定全名别名,为您提供完整路径。

您可以看到 PowerShell 正在做什么来使用 Trace-Command 绑定输入对象。以下是如何使用它的示例:

trace-command -name parameterbinding -expression (dir C:\)[0] | ? Test-VEnv $_ -pshost

这是输出的重要部分:

BIND arg [PerfLogs] to parameter [Path]
    Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
        result returned from DATA GENERATION: System.String[]
    COERCE arg to [System.String[]]
        Parameter and arg types the same, no coercion is needed.
    BIND arg [System.String[]] to param [Path] SUCCESSFUL

Test-Path 做同样的事情。看看这三个例子:

PS C:\Users\Andy> Test-Path (dir C:\)[0]
False
PS C:\Users\Andy> (dir C:\)[0] | Test-Path
True
PS C:\> Test-Path (dir C:\)[0]
True

    由于我的 PWD 不是 C:\,我得到 FALSE,因为 DirectoryInfo 对象被转换为字符串 (ToString()),它只给出文件夹名称。这是因为没有使用管道。

    由于使用了管道,它可以工作,因为它使用此参数绑定到 PsPath:

    [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
    [Alias('PSPath')]
    [string[]]
    $LiteralPath,
    

    由于目录包含文件夹,因此文件夹的名称存在。

您可以尝试使用别名 PsPath 进行绑定。这是Test-Path 使用的:

param (
    [Parameter(Mandatory=$true, Position=0,
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$true)]
    [Alias('PsPath')]
    [String[]] $Path
)

process 
    foreach ($P in $Path) 
        Get-Item $p
    

一些测试:

Set-Location C:\
Write-Host 1
    Test-VEnv '.\Windows', '.\Program Files'
Write-Host 2
    Test-VEnv (dir)
Write-Host 3
    'Windows', 'Program Files' | Test-VEnv
Write-Host 4
    dir | Test-VEnv

输出:

1
    Directory: C:\
Mode                LastWriteTime     Length Name                                                       
----                -------------     ------ ----                                                       
d----         3/14/2012   3:41 AM            Windows                                                    
d-r--         3/24/2012   7:46 PM            Program Files                                              

2
d----         2/18/2012   4:32 AM            PerfLogs                                                   
d-r--         3/24/2012   7:46 PM            Program Files                                              
d-r--         3/25/2012   4:49 PM            Program Files (x86)                                        
d----          3/9/2012   9:57 PM            Python27                                                   
d-r--          3/4/2012   8:11 PM            Users                                                      
d----         3/14/2012   3:41 AM            Windows                                                    
-a---          3/4/2012   8:45 PM       1024 .rnd                                                       

3
d----         3/14/2012   3:41 AM            Windows                                                    
d-r--         3/24/2012   7:46 PM            Program Files                                              

4
d----         2/18/2012   4:32 AM            PerfLogs                                                   
d-r--         3/24/2012   7:46 PM            Program Files                                              
d-r--         3/25/2012   4:49 PM            Program Files (x86)                                        
d----          3/9/2012   9:57 PM            Python27                                                   
d-r--          3/4/2012   8:11 PM            Users                                                      
d----         3/14/2012   3:41 AM            Windows                                                    
-a---          3/4/2012   8:45 PM       1024 .rnd  

【讨论】:

好的,所以你说的是我的函数确实像测试路径。抱歉,我的测试似乎有点混乱 - 感谢您为我澄清。那么这是否意味着不可能达到我想要的结果? (至少在代码中没有杂乱的显式类型检查) @PaulMoore 无论是否使用管道,您是否希望将完整路径绑定为$Path 的字符串? @PaulMoore 尝试使用PsPath 这是Test-Path 使用的。我用一些例子更新了我的答案。 是的,我期待一个完整的路径名。 PSPath 的工作方式与 FullName 大致相同(适用于除“Test-VEnv (dir)”之外的所有情况)。在实践中,PSPath 对我来说并不那么好,因为它包含我不需要的提供程序,但这是一个小问题。关键是,正如您在上面指出的那样,参数输入被强制转换为字符串,并且没有“按属性名称进行参数”选项。最简单的方法可能是代码中的属性检查或强制。或者不要太担心,反正这是一个相当晦涩的案例:-)【参考方案2】:

如果你把$path的类型从String[]改成[System.IO.DirectoryInfo[]]能用吗?

【讨论】:

不清楚 OP 是否要同时处理 FileInfos 和 DirectoryInfos。由于该示例使用的是路径,因此我的猜测都在使用。 你可能是对的。我添加了这个基于:'参数可以是字符串或目录对象'。 如果Test-VEnv 应该只对目录进行操作,这就是我要做的。 Test-VEnv 可以传递 FileInfo 对象,但无论如何总是会失败(venv 是一种具有特定结构的目录)。 如果我指定了 DirectoryInfo 参数类型,Test-VEnv 会失败,无法处理参数“路径”上的参数转换。无法将类型“System.IO.FileInfo”的 xxx 值转换为类型“System.IO.DirectoryInfo”。【参考方案3】:

@Andy 提供了一些很好的信息,专门针对您的问题中的要点。考虑到更广泛的影响,我在这里的回答更像是一种补充。它可能只应该成为评论,但长度和我包含的图片阻止我将其发布为评论......

我最近研究了 Powershell 中管道与直接输入的问题,其具体目标是使这些输入流相对于所有输入类别和应用的默认值对称。据我估计,有六个等价类的输入需要考虑:

没有输入 空 空 标量 正常值列表 混合值列表(即一些空值或空值)

当这些输入中的每一个被发送到一个函数时,人们通常期望的是这个对应的列表:

默认值 空 空 标量 正常值列表 混合值列表(即一些空值或空值)

也就是说,在没有提供输入的情况下使用默认值;否则使用给定的值。这听起来几乎是微不足道的,实际上是同义反复,但有一些微妙之处。例如,考虑通过管道提供无输入意味着什么?它是 null 还是空集合?我认为后者,除其他原因外,它允许我上面提到的流之间的对称性。此外,您如何编写 both 函数签名 函数体有时会对带有一个或另一个输入流的这些输入类中的一些或全部产生令人惊讶的影响。因此,我进一步认为,这种“微不足道”的考虑比乍一看要多得多。如此之多,以至于我在文章中写了很多关于它的内容 Down the Rabbit Hole- A Study in PowerShell Pipelines, Functions, and Parameters, 发表在 Simple-Talk.com 上。文章中包含一个挂图,其中显示了六个等价输入类的表格,以及使用不同的函数模板获得的每个类。这是挂图的缩略图:

【讨论】:

【参考方案4】:
function Install-PathTransformation 

    [CmdletBinding()]
    param()

    if (-not $script:my_pathtransformation_types) 
      $script:my_pathtransformation_types = Add-Type -TypeDefinition @"
        using System;
        using System.IO;
        using System.Management.Automation;

        public class ValidPathTransformationAttribute : ArgumentTransformationAttribute 
            public bool Resolve 
                get;
                set;
            

            public override Object Transform(EngineIntrinsics engineIntrinsics, Object inputObject) 
                PSObject psobj = inputObject as PSObject;
                if (psobj != null)
                    inputObject = psobj.BaseObject;
                if (inputObject == null)
                    return inputObject;

                FileSystemInfo test1 = inputObject as FileSystemInfo;
                if (test1 != null)
                    return test1.FullName; // no need for further checks, path shoul de qualified!

                PathInfo test2 = inputObject as PathInfo;
                if (test2 != null)
                    return test2.Path;     // no need for further checks, path shoul de qualified!

                string test3 = inputObject as string;
                if (test3 == null)
                    test3 = (string)LanguagePrimitives.ConvertTo(inputObject, typeof(string));
                if (Resolve)
                    test3 = engineIntrinsics.SessionState.Path.GetUnresolvedProviderPathFromPSPath(test3);
                else if (!engineIntrinsics.SessionState.Path.IsValid(test3))
                    throw new ArgumentTransformationMetadataException("Invalid path value: " + test3);
                return test3;
            
        
"@
    
    return $script:my_pathtransformation_types



Install-PathTransformation

function A(
    [parameter(Mandatory=$false, ValueFromPipeline=$true)]
    [ValidPathTransformation(Resolve=$true)]
    [string] # optional, transformation returns always string
    $z)  
  Process 
    Write-Host $("0: 1" -f $z.GetType().FullName, $z)
  


& 
    'mumu', 10, 10.5, ""
    dir $env:Temp | select -First 5
 | A

它是如何工作的: 1) 创建一个转换属性来处理参数值。 2) 在转换过程中,如果 Value 是 FileSystemInfo 或 PathInfo,我们取其中的值,如果不是,我们将值转换为字符串并确保“路径”有效(并在需要时解析路径)。 3) 应用时,Transformation的结果总是字符串。

【讨论】:

以上是关于如何创建一个从管道和命令行接受多种参数类型的函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何从PowerShell命令行转义管道字符以传递到非PowerShell命令

是否有可能使函数接受给定参数的多种数据类型?

在java中接受日期类型命令行参数

接受多种类型的方法参数

springMVC怎么接受前台传过来的多种类型参数?(集合实体单个参数)

如何使用类型提示为参数指定多种类型? [复制]