IttayD
IttayD

Reputation: 29123

how to write a (unix like) function that accepts pipe or file name input?

E.g. I want something like head that either accepts an array piped to it and then does select-object -first, or, receives a list of file names as parameters and outputs the first lines of each. So cat $filename | head should work like head $filename

Here's what I've tried so far:

Function head( )
{
    param (
      [switch] $help = $false,
      [parameter(mandatory=$false,ValueFromPipeline=$true)] [object[]] $inputs,
      [parameter(ValueFromRemainingArguments=$true)] [String[]] $files
    )

    If( $help )
    {
        Write-Host "usage: $($MyInvocation.MYCommand) [<file>] <numLines>"
        return
    }


    $lines = 0
    if ($files -and [int]::TryParse($files[0], [ref]$lines)) {
      $null,$files = $files
    } else {
      $lines = 10
    }

    $input | select-object -First $lines
    if ($files) {get-content -TotalCount $lines $files}
}

But this causes the function to ignore the first parameter:

C:\Users\idror.TLV-WPVAJ> head C:\temp\now.ps1
C:\Users\idror.TLV-WPVAJ> head C:\temp\now.ps1 C:\temp\now.ps1
Function head( )
{
    param (
          [switch] $help = $false,
          [parameter(mandatory=$false,ValueFromPipeline=$true)] [object[]] $input,
          [parameter(ValueFromRemainingArguments=$true)] [String[]] $files
        )

        If( $help )
        {
C:\Users\idror.TLV-WPVAJ> $a | head
1
2
3
C:\Users\idror.TLV-WPVAJ> $a | head 1
head : The input object cannot be bound to any parameters for the command either because the command does not take
pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:6
+ $a | head 1
+      ~~~~~~
    + CategoryInfo          : InvalidArgument: (1:Int32) [head], ParameterBindingException
    + FullyQualifiedErrorId : InputObjectNotBound,head

Upvotes: 2

Views: 63

Answers (1)

briantist
briantist

Reputation: 47792

You can achieve this using Parameter Sets:

function head {
[CmdletBinding()]
param(
    [Parameter(
        ParameterSetName = 'Content',
        ValueFromPipeline = $true,
        Mandatory = $true
    )]
    [String[]]
    $Content ,

    [Parameter(
        ParameterSetName = 'File',
        ValueFromRemainingArguments = $true ,
        Mandatory = $true
    )]
    [String[]]
    $Path,

    [Parameter()]
    [uint64]
    $Count = 5
)

    Begin {
        Write-Verbose "Parameter Set Name: $($PSCmdlet.ParameterSetName)"
    }

    Process {
        Write-Verbose "Content: $Content"
        Write-Verbose "Path: $Path"
        Write-Verbose "Count: $Count"
    }
}

First, take a look at the help output of this by running Get-Help head:

NAME
    head

SYNTAX
   head -Content <string[]> [-Count <uint64>]  [<CommonParameters>]

   head -Path <string[]> [-Count <uint64>]  [<CommonParameters>]

You can see that it interprets 2 different sets, and each parameter is mandatory in its own set.

You can also call it with -Verbose to see a demonstration of how this is working:

# Content by pipeline
'one','two','three' | head -Verbose

# Files as an array
head -Verbose 'file1','file2','file3'

# Files as remaining arguments
head -Verbose 'file1' 'file2' 'file3'

Upvotes: 1

Related Questions