7

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
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 1
    Does `"Test".SubString(0,1)` work for you? – Ryan Bemrose Mar 06 '15 at 23:46
  • It does. Sometimes, though, piping seems like the best tool. – Shaun Luttin Mar 06 '15 at 23:48
  • 2
    The pipeline is designed for a stream of objects. It's perfectly natural to use foreach to iterate over them. If you wrote a function which assumed that there was only one object in the pipeline, what would it do if the previous command put more than one item in? – Ryan Bemrose Mar 06 '15 at 23:53
  • 1
    To add to what Ryan says, the performance of 'test'.substring(0,1) is much better than using the pipeline. – Keith Hill Mar 08 '15 at 04:36
  • @RyanBemrose That's a good point. The thing is, I'm not write a function. I'm writing a series of piped expressions (is that the right terminology?) – Shaun Luttin Mar 09 '15 at 01:21

1 Answers1

6

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
#
Ryan Bemrose
  • 9,018
  • 1
  • 41
  • 54