Как мне создать функцию, которая принимает несколько типов аргументов из конвейера и командной строки? - PullRequest
11 голосов
/ 26 марта 2012

Я пытаюсь написать функцию, которая принимает несколько аргументов, которые могут поступать либо из командной строки, либо из конвейера. Аргументы могут быть строками или объектами каталога. Идея состоит в том, что любой из следующих вызовов должен работать:

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, поэтому я хочу попробовать следовать стандартному поведению, прежде чем изобретать свой собственный ...

Ответы [ 4 ]

9 голосов
/ 27 марта 2012

Поскольку ваш тип параметра string, он преобразует объект информации файловой системы в строку, когда вы не используете конвейер { Test-VEnv $_ }. Если вы вызовете метод ToString() объекта System.IO.FileInfo или System.IO.DirectoryInfo, вы увидите это. Когда вы используете конвейер, он связывает псевдоним полного имени, давая вам полный путь.

Вы можете увидеть, что делает 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
  1. Поскольку мой PWD не C:\, я получаю FALSE, потому что объект DirectoryInfo преобразуется в строку (ToString()), которая дает только имя папки. Это потому, что конвейер не использовался.

  2. Поскольку конвейер используется, он работает, потому что связывается с PsPath с помощью этого параметра:

    [Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
    [Alias('PSPath')]
    [string[]]
    ${LiteralPath},
    
  3. Поскольку каталог содержит папку, имя папки существует.

Вы можете попробовать псевдоним 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  
7 голосов
/ 28 марта 2012

@ Энди дает отличную информацию, конкретно касающуюся пунктов вашего вопроса.Мой ответ здесь - скорее дополнение, учитывая более широкие последствия.Это, вероятно, только заслуживает того, чтобы быть комментарием, но длина и мое включенное изображение мешают мне публиковать это как просто комментарий ...

Я недавно рассмотрел вопрос конвейера против прямого ввода в Powershell сконкретная цель - сделать эти входные потоки симметричными по отношению ко всем классам входных данных и по отношению к применяемым значениям по умолчанию.На мой взгляд, существует шесть классов эквивалентности входных данных, которые необходимо учитывать:

  • без ввода
  • ноль
  • пусто
  • скаляр
  • список нормальных значений
  • список смешанных значений (т. Е. Некоторые нулевые или пустые)

Чего можно ожидатькогда каждый из этих входов отправляется в функцию, будет следующий соответствующий список:

  • значение по умолчанию
  • null
  • пусто
  • скаляр
  • список нормальных значений
  • список смешанных значений (т. Е. Некоторых нулевых или пустых)

То есть свход не предоставлен, используется значение по умолчанию;в противном случае используется данное значение.Это звучит почти тривиально, практически тавтология, но есть некоторые тонкости.Рассмотрим, например, что означает поставка без ввода по конвейеру?Это ноль или пустая коллекция?Я утверждаю, что последний, среди прочих причин, допускает симметрию между потоками, которые я упомянул выше.Кроме того, как вы пишете и свою подпись функции и ваше тело функции иногда оказывает удивительное влияние на некоторые или все эти входные классы с одним или другим входным потоком.Таким образом, я также утверждаю, что в этом «тривиальном» рассмотрении гораздо больше, чем кажется на первый взгляд.Настолько, что я много об этом писал в статье «Вниз по кроличьей норе» - исследование по конвейерам, функциям и параметрам PowerShell , опубликованной на Simple-Talk.com.В эту статью включена настенная диаграмма, на которой показана таблица из шести входных классов эквивалентности и того, что вы получаете для каждого с различными шаблонами функций.Вот эскиз настенной диаграммы:

enter image description here

0 голосов
/ 01 июля 2014
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) При применении результат преобразования всегда является строкой.

0 голосов
/ 27 марта 2012

Работает ли это, если вы измените тип $ path с String [] на [System.IO.DirectoryInfo []]?

...