Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pipe a single object and process it without For-EachObject

Original Question

I a piping a single string and processing it with For-EachObject as follows:

 "Test" | % { $_.Substring(0,1) }

It seems wrong to process a single piped item using For-EachObject, partly because it's misleading to future code maintainers. I don't know any other way, though, to capture the string while saying "it's just a single item." For instance, this doesn't work.

"Test" | $_.Substring(0,1)
"Test" | { $_.Substring(0,1) }

How can I process a single object while indicating that I expect only one?

Edit: Add the actual use case

The above is a simplified version of what I'm actually trying to accomplish. I am getting the first paragraph of a Wikipedia article, which is part of a larger function that saves the result to a file.

curl "www.wikipedia.org/wiki/Hope,_British_Columbia" | 
    select -expand allelements | 
    ? { $_.id -eq "mw-content-text" } | 
    select -expand innerHTML | 
    % { 
        $i = $_.IndexOf("<P>"); 
        $j = $_.IndexOf("</P>"); 
        $_.Substring($i, $j - $i) -replace '<[^>]*>'
     } 

The part that needs to process a single object follows the select -expand innerHtml expression. Piping is my preferred way because putting multiple parenthesis around the curl part seems ugly.

Aliases

  • curl is Invoke-WebRequest
  • select is Select-Object
  • -expand is ExplandProperty
  • ? is Where-Object
  • % is For-EachObject
like image 698
Shaun Luttin Avatar asked Mar 06 '15 23:03

Shaun Luttin


1 Answers

If you are creating single-purpose code where you control both the input and the output, and there will always be only one object, then using the pipeline is overkill and not really appropriate. Just pass the string as a parameter to your function.

function f([String]$s) {
  $s.Substring(0,1)
}
PS> f "Test"
T

If you're building a general-purpose function to take input from the pipeline, your function needs to account for more than one object in the stream. Fortunately PowerShell has a natural way to do this using the Process{} block, which is executed once for each item in the input pipeline.

function f {
  param(
    [Parameter(ValueFromPipeline=$true)]
    [String]$item
  )
  process {
    $item.Substring(0,1)
  }
}
PS> '123','abc','#%@' | f
1
a
#

This is a common enough function that PowerShell has a shorthand for writing a function that takes one parameter from the pipeline and only contains a process block.

filter f {
  $_.SubString(0,1)
}
PS> '123','abc','#%@' | f
1
a
#
like image 61
Ryan Bemrose Avatar answered Oct 17 '22 13:10

Ryan Bemrose